"""Freie Filmsuche über TMDB. Der Katalog deckt nur Netflix und den Kanon ab. Für den Filmabend braucht es auch Titel, die nirgends davon stehen — die kommen live von TMDB und landen nicht in der Datenbank. """ import httpx from .config import LANGUAGE, TMDB_BASE, TMDB_TOKEN from .tmdb import DETAIL_PARAMS, pick_backdrops, pick_overview, pick_trailer HEADERS = {"Authorization": f"Bearer {TMDB_TOKEN}", "accept": "application/json"} def client(): return httpx.AsyncClient(base_url=TMDB_BASE, headers=HEADERS, timeout=30.0) def as_result(item): release = item.get("release_date") or "" return { "id": item["id"], "title": item.get("title") or item.get("original_title") or "Ohne Titel", "original_title": item.get("original_title"), "year": int(release[:4]) if release[:4].isdigit() else None, "release_date": release or None, "overview": item.get("overview") or "", "poster_path": item.get("poster_path"), "vote_average": item.get("vote_average"), "vote_count": item.get("vote_count"), } async def search(query, limit=24): async with client() as http: resp = await http.get( "/search/movie", params={"query": query, "language": LANGUAGE, "include_adult": "false"}, ) resp.raise_for_status() results = resp.json().get("results", []) # Ohne Poster taugt eine Kachel nichts, und Namenlose sind fast immer # Dubletten oder Making-ofs usable = [r for r in results if r.get("poster_path")] usable.sort(key=lambda r: -(r.get("popularity") or 0)) return [as_result(r) for r in usable[:limit]] async def detail(movie_id): """Vollbild-Daten für einen Treffer, der nicht im Katalog steht.""" async with client() as http: resp = await http.get(f"/movie/{movie_id}", params=DETAIL_PARAMS) resp.raise_for_status() data = resp.json() release = data.get("release_date") or "" collection = data.get("belongs_to_collection") or {} return { "id": data["id"], "title": data.get("title") or data.get("original_title") or "Ohne Titel", "original_title": data.get("original_title"), "release_date": release or None, "year": int(release[:4]) if release[:4].isdigit() else None, "overview": pick_overview(data), "teaser": "", "poster_path": data.get("poster_path"), "backdrop_path": data.get("backdrop_path"), "vote_average": data.get("vote_average"), "vote_count": data.get("vote_count"), "runtime": data.get("runtime"), "genres": [g["name"] for g in data.get("genres", [])], "images": pick_backdrops(data)[:12], "trailer_key": pick_trailer(data), "imdb_id": data.get("imdb_id"), "netflix_id": None, "netflix_url": None, "on_netflix": 0, "canon_group": None, "canon_pos": None, "collection_id": collection.get("id"), "collection_name": collection.get("name"), }