Files
horror/backend/app/search.py
2026-08-29 11:28:25 +02:00

85 lines
3.0 KiB
Python

"""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"),
}