90eb17d407
Ampel / ampel (push) Failing after 38s
Commander-Befunde 23.07. abends:
1. Metadaten "klappen nicht": Wurzel = kryptisches Volume-Label + kein
TMDB-Key. Jetzt: API mountet die Disc read-only (CAP_SYS_ADMIN ist da)
und liest den KLARTEXT-Titel aus BDMV/META/DL/bdmt_*.xml; dazu
progressive Suchdegradation (Titel-Varianten) und OMDb-Unscharf-Suche
(s= + imdbID-Nachladen) — mit dem vorhandenen OMDb-Key gibt es damit
Titel/Jahr/Poster auch ohne TMDB
2. Weisses Fenster beim Pre-Scan: Lucide-Icons sind forwardRef —
Direktaufruf getIcon(...)({size}) crashte die Seite; als JSX gerendert
3. Dashboard sortiert: System-Leiste (Encoder + aktiver Job) oben,
Disc/Geraete -> Stats -> Jobs, Live-Log ans Ende
4. Ziel-Dialog mit ORDNER-BROWSER (GET /browse, nur unter /app/media);
Schnellwahl nutzt die Settings-Unterordner statt Hartkodierung;
Tab "Verzeichnisse" in "Speicherziele" aufgegangen (redundant)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
95 lines
3.3 KiB
Python
95 lines
3.3 KiB
Python
"""OMDb API Client — Fallback-Quelle für die Metadaten-Erkennung.
|
||
|
||
Kommt zum Zug, wenn TMDB nichts (Sicheres) findet. Kostenloser Key von
|
||
https://www.omdbapi.com/apikey.aspx (1000 Anfragen/Tag reichen fürs Heimlab).
|
||
"""
|
||
|
||
from typing import Dict, Optional
|
||
|
||
import requests
|
||
|
||
from cache import get, set as cache_set
|
||
from config import settings
|
||
|
||
OMDB_BASE_URL = "https://www.omdbapi.com/"
|
||
|
||
|
||
def parse_runtime_minutes(runtime: str) -> int:
|
||
"""OMDb liefert '142 min' als Text → Minuten als Zahl (pure Funktion, testbar)."""
|
||
if not runtime:
|
||
return 0
|
||
teil = runtime.strip().split(" ")[0]
|
||
return int(teil) if teil.isdigit() else 0
|
||
|
||
|
||
def parse_year(year: str) -> Optional[int]:
|
||
"""OMDb-Jahr kann '1999' oder '2005–2012' (Serie) sein → erstes Jahr."""
|
||
if not year:
|
||
return None
|
||
erste = year.strip()[:4]
|
||
return int(erste) if erste.isdigit() else None
|
||
|
||
|
||
class OMDbClient:
|
||
def __init__(self):
|
||
# DB-Einstellung (Settings-UI/Wizard) gewinnt gegen die Env-Variable
|
||
from db import get_settings
|
||
self.api_key = get_settings().get("omdbApiKey") or settings.omdb_api_key
|
||
self.session = requests.Session()
|
||
|
||
def lookup(self, title: str, year: Optional[int] = None) -> Optional[Dict]:
|
||
"""Sucht per Titel (t=), liefert normalisierte Metadaten oder None."""
|
||
if not self.api_key:
|
||
return None
|
||
|
||
cache_key = f"omdb:{title.lower()}:{year or ''}"
|
||
cached = get(cache_key)
|
||
if cached:
|
||
return cached
|
||
|
||
params = {"apikey": self.api_key, "t": title, "r": "json"}
|
||
if year:
|
||
params["y"] = str(year)
|
||
|
||
try:
|
||
response = self.session.get(OMDB_BASE_URL, params=params, timeout=10)
|
||
response.raise_for_status()
|
||
data = response.json()
|
||
|
||
# t= ist exakt — wenn leer, unscharfe Suche (s=) und den besten
|
||
# Treffer per imdbID nachladen
|
||
if data.get("Response") != "True":
|
||
suche = self.session.get(
|
||
OMDB_BASE_URL,
|
||
params={"apikey": self.api_key, "s": title, "r": "json"},
|
||
timeout=10,
|
||
).json()
|
||
treffer = (suche.get("Search") or []) if suche.get("Response") == "True" else []
|
||
if treffer:
|
||
data = self.session.get(
|
||
OMDB_BASE_URL,
|
||
params={"apikey": self.api_key, "i": treffer[0]["imdbID"], "r": "json"},
|
||
timeout=10,
|
||
).json()
|
||
except Exception as e:
|
||
print(f"OMDb API Error: {e}")
|
||
return None
|
||
|
||
if data.get("Response") != "True":
|
||
return None
|
||
|
||
ergebnis = {
|
||
"type": "tv" if data.get("Type") == "series" else "movie",
|
||
"id": data.get("imdbID", ""),
|
||
"title": data.get("Title", title),
|
||
"year": parse_year(data.get("Year", "")),
|
||
"overview": data.get("Plot", ""),
|
||
"poster_path": data.get("Poster", "") if data.get("Poster") != "N/A" else "",
|
||
"backdrop_path": "",
|
||
"runtime": parse_runtime_minutes(data.get("Runtime", "")),
|
||
"genres": [g.strip() for g in data.get("Genre", "").split(",") if g.strip()],
|
||
"source": "omdb",
|
||
}
|
||
cache_set(cache_key, ergebnis)
|
||
return ergebnis
|