update
This commit is contained in:
@@ -7,6 +7,7 @@ landen nicht in der Datenbank.
|
||||
|
||||
import asyncio
|
||||
import random
|
||||
import time
|
||||
import unicodedata
|
||||
|
||||
import httpx
|
||||
@@ -16,6 +17,7 @@ from .tmdb import (
|
||||
DETAIL_PARAMS,
|
||||
pick_backdrops,
|
||||
pick_overview,
|
||||
pick_rentable,
|
||||
pick_title,
|
||||
pick_trailer,
|
||||
)
|
||||
@@ -67,7 +69,15 @@ async def details_nachladen(http, treffer, je_film=4):
|
||||
Das alles steckt in der Detailansicht, ein Abruf je Film reicht.
|
||||
"""
|
||||
|
||||
# Nicht alle auf einmal: bei einer langen Filmografie wären das
|
||||
# über hundert gleichzeitige Abrufe
|
||||
bremse = asyncio.Semaphore(8)
|
||||
|
||||
async def hole(film):
|
||||
async with bremse:
|
||||
await hole_einen(film)
|
||||
|
||||
async def hole_einen(film):
|
||||
try:
|
||||
resp = await http.get(
|
||||
f"/movie/{film['id']}",
|
||||
@@ -106,6 +116,27 @@ SORTIERUNG = {
|
||||
|
||||
# Weiter hinten liegen nur noch Filme, die kaum jemand kennt
|
||||
MAX_SEITE = 20
|
||||
# TMDB liefert zwanzig je Seite; bis hundert sind es fünf Abrufe
|
||||
MAX_TREFFER = 100
|
||||
JE_SEITE = 20
|
||||
|
||||
|
||||
async def seiten(http, pfad, params, limit, start=1):
|
||||
"""So viele Seiten, wie das Limit braucht — alle auf einmal. Was
|
||||
fehlschlägt, fehlt eben; die Reihenfolge der Seiten bleibt."""
|
||||
anzahl = -(-limit // JE_SEITE)
|
||||
|
||||
async def eine(nummer):
|
||||
try:
|
||||
resp = await http.get(pfad, params={**params, "page": nummer})
|
||||
resp.raise_for_status()
|
||||
return resp.json()
|
||||
except httpx.HTTPError:
|
||||
return {}
|
||||
|
||||
antworten = await asyncio.gather(*(eine(start + i) for i in range(anzahl)))
|
||||
results = [r for a in antworten for r in a.get("results", [])]
|
||||
return results, antworten[0] if antworten else {}
|
||||
|
||||
|
||||
async def discover(filter_, limit=40):
|
||||
@@ -149,22 +180,16 @@ async def discover(filter_, limit=40):
|
||||
params["primary_release_date.lte"] = f"{filter_['jahr_max']}-12-31"
|
||||
|
||||
async with client() as http:
|
||||
resp = await http.get("/discover/movie", params=params)
|
||||
resp.raise_for_status()
|
||||
daten = resp.json()
|
||||
results, erste = await seiten(http, "/discover/movie", params, limit)
|
||||
|
||||
# Bei "Zufällig" nochmal von einer beliebigen Seite holen — sonst
|
||||
# stehen bei jedem Aufruf dieselben zwanzig Filme oben
|
||||
if filter_.get("sort") == "random":
|
||||
seiten = min(daten.get("total_pages") or 1, MAX_SEITE)
|
||||
if seiten > 1:
|
||||
params["page"] = random.randint(1, seiten)
|
||||
resp = await http.get("/discover/movie", params=params)
|
||||
resp.raise_for_status()
|
||||
daten = resp.json()
|
||||
|
||||
results = daten.get("results", [])
|
||||
# Bei "Zufällig" nochmal von einer beliebigen Stelle holen — sonst
|
||||
# stehen bei jedem Aufruf dieselben Filme oben
|
||||
if filter_.get("sort") == "random":
|
||||
gesamt = min(erste.get("total_pages") or 1, MAX_SEITE)
|
||||
noetig = -(-limit // JE_SEITE)
|
||||
if gesamt > noetig:
|
||||
start = random.randint(1, gesamt - noetig + 1)
|
||||
results, _ = await seiten(http, "/discover/movie", params, limit, start)
|
||||
random.shuffle(results)
|
||||
|
||||
usable = [r for r in results if r.get("poster_path")]
|
||||
@@ -187,19 +212,13 @@ async def search(query, limit=24):
|
||||
begriffe = [query] if schlicht == query else [query, schlicht]
|
||||
|
||||
async def einmal(begriff):
|
||||
try:
|
||||
resp = await http.get(
|
||||
"/search/movie",
|
||||
params={
|
||||
"query": begriff,
|
||||
"language": LANGUAGE,
|
||||
"include_adult": "false",
|
||||
},
|
||||
)
|
||||
resp.raise_for_status()
|
||||
return resp.json().get("results", [])
|
||||
except httpx.HTTPError:
|
||||
return []
|
||||
results, _ = await seiten(
|
||||
http,
|
||||
"/search/movie",
|
||||
{"query": begriff, "language": LANGUAGE, "include_adult": "false"},
|
||||
limit,
|
||||
)
|
||||
return results
|
||||
|
||||
async with client() as http:
|
||||
results = []
|
||||
@@ -343,6 +362,114 @@ async def aehnliche(movie_id, limit=12, voll=False, filter_=None):
|
||||
return treffer
|
||||
|
||||
|
||||
# ── Personen: Schauspieler und Regisseure ────────────────────────────────
|
||||
MAX_PERSONEN = 12
|
||||
# Laufzeit und Szenenbilder kosten je Film einen Abruf — bei einer langen
|
||||
# Filmografie ein paar Sekunden. Einmal geholt, bleibt es einen Tag im
|
||||
# Speicher; so oft ändert sich eine Filmografie nicht.
|
||||
PERSONEN_CACHE = {}
|
||||
PERSONEN_FRIST = 24 * 3600
|
||||
|
||||
FACH = {
|
||||
"Acting": "Schauspiel",
|
||||
"Directing": "Regie",
|
||||
"Writing": "Drehbuch",
|
||||
"Production": "Produktion",
|
||||
}
|
||||
|
||||
|
||||
async def personen(query):
|
||||
"""Leute suchen. Wer ein Foto hat, steht vorn — sonst die Bekannten."""
|
||||
async with client() as http:
|
||||
try:
|
||||
resp = await http.get(
|
||||
"/search/person",
|
||||
params={"query": query, "language": LANGUAGE, "include_adult": "false"},
|
||||
)
|
||||
resp.raise_for_status()
|
||||
roh = resp.json().get("results", [])
|
||||
except httpx.HTTPError:
|
||||
return []
|
||||
roh.sort(key=lambda p: (not p.get("profile_path"), -(p.get("popularity") or 0)))
|
||||
return [
|
||||
{
|
||||
"id": p["id"],
|
||||
"name": p.get("name"),
|
||||
"fach": FACH.get(p.get("known_for_department"), p.get("known_for_department") or ""),
|
||||
"profile_path": p.get("profile_path"),
|
||||
"bekannt_fuer": [
|
||||
k.get("title") or k.get("name")
|
||||
for k in p.get("known_for", [])
|
||||
if k.get("title") or k.get("name")
|
||||
][:3],
|
||||
}
|
||||
for p in roh[:MAX_PERSONEN]
|
||||
]
|
||||
|
||||
|
||||
def credits_aufbereiten(daten):
|
||||
"""Aus den movie_credits zwei Listen: was jemand gespielt und wo er
|
||||
Regie geführt hat. Ohne Poster oder doppelt fliegt raus, die
|
||||
bekanntesten Filme stehen vorn. Kurzfilme und Making-ofs bleiben
|
||||
drin — die sortiert die Filterleiste aus, und zwar sichtbar."""
|
||||
|
||||
def liste(eintraege):
|
||||
raus, dabei = [], set()
|
||||
for e in sorted(eintraege, key=lambda e: -(e.get("popularity") or 0)):
|
||||
if not e.get("poster_path") or e["id"] in dabei:
|
||||
continue
|
||||
dabei.add(e["id"])
|
||||
raus.append(as_result(e))
|
||||
return raus
|
||||
|
||||
return {
|
||||
"gespielt": liste(daten.get("cast", [])),
|
||||
"regie": liste([c for c in daten.get("crew", []) if c.get("job") == "Director"]),
|
||||
}
|
||||
|
||||
|
||||
# Mehr Namen passen nicht in die Filmansicht, und ab da sind es Nebenrollen
|
||||
MAX_CAST = 8
|
||||
|
||||
|
||||
async def credits(movie_id):
|
||||
"""Regie und die wichtigsten Darsteller — als Sprungbrett zur
|
||||
Personensuche. Fehlt etwas, bleiben die Listen leer."""
|
||||
async with client() as http:
|
||||
try:
|
||||
# Ohne Sprache: auf Deutsch kämen russische Namen in Kyrillisch
|
||||
# und eingedeutschte Umschriften wie "Ilja Wiktorowitsch"
|
||||
resp = await http.get(f"/movie/{movie_id}/credits", params={"language": "en-US"})
|
||||
resp.raise_for_status()
|
||||
daten = resp.json()
|
||||
except httpx.HTTPError:
|
||||
return {"regie": [], "cast": []}
|
||||
|
||||
def person(p, fach):
|
||||
return {"id": p["id"], "name": p.get("name"), "fach": fach, "profile_path": p.get("profile_path")}
|
||||
|
||||
regie, dabei = [], set()
|
||||
for c in daten.get("crew", []):
|
||||
if c.get("job") == "Director" and c["id"] not in dabei:
|
||||
dabei.add(c["id"])
|
||||
regie.append(person(c, "Regie"))
|
||||
cast = sorted(daten.get("cast", []), key=lambda c: c.get("order", 999))[:MAX_CAST]
|
||||
return {"regie": regie, "cast": [person(c, "Schauspiel") for c in cast]}
|
||||
|
||||
|
||||
async def person_filme(person_id):
|
||||
gemerkt = PERSONEN_CACHE.get(person_id)
|
||||
if gemerkt and time.monotonic() - gemerkt[0] < PERSONEN_FRIST:
|
||||
return gemerkt[1]
|
||||
async with client() as http:
|
||||
resp = await http.get(f"/person/{person_id}/movie_credits", params={"language": LANGUAGE})
|
||||
resp.raise_for_status()
|
||||
rollen = credits_aufbereiten(resp.json())
|
||||
await details_nachladen(http, rollen["gespielt"] + rollen["regie"])
|
||||
PERSONEN_CACHE[person_id] = (time.monotonic(), rollen)
|
||||
return rollen
|
||||
|
||||
|
||||
async def titel_richten(http, film):
|
||||
"""Originalschrift durch den englischen Titel ersetzen."""
|
||||
if not any(
|
||||
@@ -387,6 +514,7 @@ async def detail(movie_id):
|
||||
"images": pick_backdrops(data)[:12],
|
||||
"trailer_key": pick_trailer(data),
|
||||
"imdb_id": data.get("imdb_id"),
|
||||
"rentable": pick_rentable(data),
|
||||
"netflix_id": None,
|
||||
"netflix_url": None,
|
||||
"on_netflix": 0,
|
||||
|
||||
Reference in New Issue
Block a user