Nightly: refactor readability, progress callbacks, and resource handling
This commit is contained in:
167
addon/tmdb.py
167
addon/tmdb.py
@@ -14,6 +14,7 @@ except ImportError: # pragma: no cover
|
||||
|
||||
TMDB_API_BASE = "https://api.themoviedb.org/3"
|
||||
TMDB_IMAGE_BASE = "https://image.tmdb.org/t/p"
|
||||
MAX_CAST_MEMBERS = 30
|
||||
_TMDB_THREAD_LOCAL = threading.local()
|
||||
|
||||
|
||||
@@ -73,53 +74,17 @@ def _fetch_credits(
|
||||
return []
|
||||
params = {"api_key": api_key, "language": (language or "de-DE").strip()}
|
||||
url = f"{TMDB_API_BASE}/{kind}/{tmdb_id}/credits?{urlencode(params)}"
|
||||
if callable(log):
|
||||
log(f"TMDB GET {url}")
|
||||
try:
|
||||
response = requests.get(url, timeout=timeout)
|
||||
except Exception as exc: # pragma: no cover
|
||||
if callable(log):
|
||||
log(f"TMDB ERROR /{kind}/{{id}}/credits request_failed error={exc!r}")
|
||||
return []
|
||||
status = getattr(response, "status_code", None)
|
||||
status, payload, body_text = _tmdb_get_json(url=url, timeout=timeout, log=log, log_responses=log_responses)
|
||||
if callable(log):
|
||||
log(f"TMDB RESPONSE /{kind}/{{id}}/credits status={status}")
|
||||
if status != 200:
|
||||
if log_responses and payload is None and body_text:
|
||||
log(f"TMDB RESPONSE_BODY /{kind}/{{id}}/credits body={body_text[:2000]}")
|
||||
if status != 200 or not isinstance(payload, dict):
|
||||
return []
|
||||
try:
|
||||
payload = response.json() or {}
|
||||
except Exception:
|
||||
return []
|
||||
if callable(log) and log_responses:
|
||||
try:
|
||||
dumped = json.dumps(payload, ensure_ascii=False)
|
||||
except Exception:
|
||||
dumped = str(payload)
|
||||
log(f"TMDB RESPONSE_BODY /{kind}/{{id}}/credits body={dumped[:2000]}")
|
||||
|
||||
cast_payload = payload.get("cast") or []
|
||||
if callable(log):
|
||||
log(f"TMDB CREDITS /{kind}/{{id}}/credits cast={len(cast_payload)}")
|
||||
with_images: List[TmdbCastMember] = []
|
||||
without_images: List[TmdbCastMember] = []
|
||||
for entry in cast_payload:
|
||||
name = (entry.get("name") or "").strip()
|
||||
role = (entry.get("character") or "").strip()
|
||||
thumb = _image_url(entry.get("profile_path") or "", size="w185")
|
||||
if not name:
|
||||
continue
|
||||
member = TmdbCastMember(name=name, role=role, thumb=thumb)
|
||||
if thumb:
|
||||
with_images.append(member)
|
||||
else:
|
||||
without_images.append(member)
|
||||
|
||||
# Viele Kodi-Skins zeigen bei fehlendem Thumbnail Platzhalter-Köpfe.
|
||||
# Bevorzugt daher Cast-Einträge mit Bild; nur wenn gar keine Bilder existieren,
|
||||
# geben wir Namen ohne Bild zurück.
|
||||
if with_images:
|
||||
return with_images[:30]
|
||||
return without_images[:30]
|
||||
return _parse_cast_payload(cast_payload)
|
||||
|
||||
|
||||
def _parse_cast_payload(cast_payload: object) -> List[TmdbCastMember]:
|
||||
@@ -141,8 +106,8 @@ def _parse_cast_payload(cast_payload: object) -> List[TmdbCastMember]:
|
||||
else:
|
||||
without_images.append(member)
|
||||
if with_images:
|
||||
return with_images[:30]
|
||||
return without_images[:30]
|
||||
return with_images[:MAX_CAST_MEMBERS]
|
||||
return without_images[:MAX_CAST_MEMBERS]
|
||||
|
||||
|
||||
def _tmdb_get_json(
|
||||
@@ -163,23 +128,29 @@ def _tmdb_get_json(
|
||||
if callable(log):
|
||||
log(f"TMDB GET {url}")
|
||||
sess = session or _get_tmdb_session() or requests.Session()
|
||||
response = None
|
||||
try:
|
||||
response = sess.get(url, timeout=timeout)
|
||||
status = getattr(response, "status_code", None)
|
||||
payload: object | None = None
|
||||
body_text = ""
|
||||
try:
|
||||
payload = response.json()
|
||||
except Exception:
|
||||
try:
|
||||
body_text = (response.text or "").strip()
|
||||
except Exception:
|
||||
body_text = ""
|
||||
except Exception as exc: # pragma: no cover
|
||||
if callable(log):
|
||||
log(f"TMDB ERROR request_failed url={url} error={exc!r}")
|
||||
return None, None, ""
|
||||
|
||||
status = getattr(response, "status_code", None)
|
||||
payload: object | None = None
|
||||
body_text = ""
|
||||
try:
|
||||
payload = response.json()
|
||||
except Exception:
|
||||
try:
|
||||
body_text = (response.text or "").strip()
|
||||
except Exception:
|
||||
body_text = ""
|
||||
finally:
|
||||
if response is not None:
|
||||
try:
|
||||
response.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if callable(log):
|
||||
log(f"TMDB RESPONSE status={status} url={url}")
|
||||
@@ -214,49 +185,17 @@ def fetch_tv_episode_credits(
|
||||
return []
|
||||
params = {"api_key": api_key, "language": (language or "de-DE").strip()}
|
||||
url = f"{TMDB_API_BASE}/tv/{tmdb_id}/season/{season_number}/episode/{episode_number}/credits?{urlencode(params)}"
|
||||
if callable(log):
|
||||
log(f"TMDB GET {url}")
|
||||
try:
|
||||
response = requests.get(url, timeout=timeout)
|
||||
except Exception as exc: # pragma: no cover
|
||||
if callable(log):
|
||||
log(f"TMDB ERROR /tv/{{id}}/season/{{n}}/episode/{{e}}/credits request_failed error={exc!r}")
|
||||
return []
|
||||
status = getattr(response, "status_code", None)
|
||||
status, payload, body_text = _tmdb_get_json(url=url, timeout=timeout, log=log, log_responses=log_responses)
|
||||
if callable(log):
|
||||
log(f"TMDB RESPONSE /tv/{{id}}/season/{{n}}/episode/{{e}}/credits status={status}")
|
||||
if status != 200:
|
||||
if log_responses and payload is None and body_text:
|
||||
log(f"TMDB RESPONSE_BODY /tv/{{id}}/season/{{n}}/episode/{{e}}/credits body={body_text[:2000]}")
|
||||
if status != 200 or not isinstance(payload, dict):
|
||||
return []
|
||||
try:
|
||||
payload = response.json() or {}
|
||||
except Exception:
|
||||
return []
|
||||
if callable(log) and log_responses:
|
||||
try:
|
||||
dumped = json.dumps(payload, ensure_ascii=False)
|
||||
except Exception:
|
||||
dumped = str(payload)
|
||||
log(f"TMDB RESPONSE_BODY /tv/{{id}}/season/{{n}}/episode/{{e}}/credits body={dumped[:2000]}")
|
||||
|
||||
cast_payload = payload.get("cast") or []
|
||||
if callable(log):
|
||||
log(f"TMDB CREDITS /tv/{{id}}/season/{{n}}/episode/{{e}}/credits cast={len(cast_payload)}")
|
||||
with_images: List[TmdbCastMember] = []
|
||||
without_images: List[TmdbCastMember] = []
|
||||
for entry in cast_payload:
|
||||
name = (entry.get("name") or "").strip()
|
||||
role = (entry.get("character") or "").strip()
|
||||
thumb = _image_url(entry.get("profile_path") or "", size="w185")
|
||||
if not name:
|
||||
continue
|
||||
member = TmdbCastMember(name=name, role=role, thumb=thumb)
|
||||
if thumb:
|
||||
with_images.append(member)
|
||||
else:
|
||||
without_images.append(member)
|
||||
if with_images:
|
||||
return with_images[:30]
|
||||
return without_images[:30]
|
||||
return _parse_cast_payload(cast_payload)
|
||||
|
||||
|
||||
def lookup_tv_show(
|
||||
@@ -546,27 +485,13 @@ def lookup_tv_season_summary(
|
||||
|
||||
params = {"api_key": api_key, "language": (language or "de-DE").strip()}
|
||||
url = f"{TMDB_API_BASE}/tv/{tmdb_id}/season/{season_number}?{urlencode(params)}"
|
||||
if callable(log):
|
||||
log(f"TMDB GET {url}")
|
||||
try:
|
||||
response = requests.get(url, timeout=timeout)
|
||||
except Exception:
|
||||
return None
|
||||
status = getattr(response, "status_code", None)
|
||||
status, payload, body_text = _tmdb_get_json(url=url, timeout=timeout, log=log, log_responses=log_responses)
|
||||
if callable(log):
|
||||
log(f"TMDB RESPONSE /tv/{{id}}/season/{{n}} status={status}")
|
||||
if status != 200:
|
||||
if log_responses and payload is None and body_text:
|
||||
log(f"TMDB RESPONSE_BODY /tv/{{id}}/season/{{n}} body={body_text[:2000]}")
|
||||
if status != 200 or not isinstance(payload, dict):
|
||||
return None
|
||||
try:
|
||||
payload = response.json() or {}
|
||||
except Exception:
|
||||
return None
|
||||
if callable(log) and log_responses:
|
||||
try:
|
||||
dumped = json.dumps(payload, ensure_ascii=False)
|
||||
except Exception:
|
||||
dumped = str(payload)
|
||||
log(f"TMDB RESPONSE_BODY /tv/{{id}}/season/{{n}} body={dumped[:2000]}")
|
||||
|
||||
plot = (payload.get("overview") or "").strip()
|
||||
poster_path = (payload.get("poster_path") or "").strip()
|
||||
@@ -594,27 +519,9 @@ def lookup_tv_season(
|
||||
return None
|
||||
params = {"api_key": api_key, "language": (language or "de-DE").strip()}
|
||||
url = f"{TMDB_API_BASE}/tv/{tmdb_id}/season/{season_number}?{urlencode(params)}"
|
||||
if callable(log):
|
||||
log(f"TMDB GET {url}")
|
||||
try:
|
||||
response = requests.get(url, timeout=timeout)
|
||||
except Exception as exc: # pragma: no cover
|
||||
if callable(log):
|
||||
log(f"TMDB ERROR /tv/{{id}}/season/{{n}} request_failed error={exc!r}")
|
||||
return None
|
||||
|
||||
status = getattr(response, "status_code", None)
|
||||
payload = None
|
||||
body_text = ""
|
||||
try:
|
||||
payload = response.json() or {}
|
||||
except Exception:
|
||||
try:
|
||||
body_text = (response.text or "").strip()
|
||||
except Exception:
|
||||
body_text = ""
|
||||
|
||||
episodes = (payload or {}).get("episodes") or []
|
||||
status, payload, body_text = _tmdb_get_json(url=url, timeout=timeout, log=log, log_responses=log_responses)
|
||||
episodes = (payload or {}).get("episodes") if isinstance(payload, dict) else []
|
||||
episodes = episodes or []
|
||||
if callable(log):
|
||||
log(f"TMDB RESPONSE /tv/{{id}}/season/{{n}} status={status} episodes={len(episodes)}")
|
||||
if log_responses:
|
||||
|
||||
Reference in New Issue
Block a user