Metadaten-Fallback OMDb + Pre-Scan repariert + Eject-Endpoint
- clients/omdb.py: OMDb als zweite Quelle (Fallback-Kette: TMDB exakt ->
OMDb -> bester TMDB-Vorschlag mit niedriger Confidence -> unknown)
- Pre-Scan-Reparatur: Titel kam nie an — makemkvcon existiert nur im
Worker, isosize war nirgends installiert (fiel still auf "DVD" zurueck).
Jetzt: ISO-9660-Volume-Label direkt vom Medium + Label-Normalisierung
(PULP_FICTION -> Pulp Fiction), Disc-Typ ueber zentrale detection.py
- POST /devices/{name}/eject (CDROMEJECT-ioctl) mit Job-Schutz (409 wenn
auf dem Laufwerk gerade gerippt wird)
- OMDB_API_KEY in compose/.env.example; .env.example komplett ehrlich
dokumentiert (JWT-Pflicht, MakeMKV-Beta-Key-Rhythmus)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
"""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):
|
||||
self.api_key = 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()
|
||||
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
|
||||
Reference in New Issue
Block a user