"""Tests für die puren Pre-Scan-/OMDb-Helfer (Label-Normalisierung, Parsing).""" from clients.omdb import parse_runtime_minutes, parse_year from prescan.prescan import normalize_disc_label, parse_udf_dstring def test_udf_dstring_latin1(): # d-string: [comp_id=8]["MEIN_FILM"][padding][len=10] daten = bytes([8]) + b"MEIN_FILM" + bytes(21) + bytes([10]) assert parse_udf_dstring(daten) == "MEIN_FILM" def test_udf_dstring_utf16(): text = "FILM".encode("utf-16-be") daten = bytes([16]) + text + bytes(32 - 2 - len(text)) + bytes([1 + len(text)]) assert parse_udf_dstring(daten) == "FILM" def test_udf_dstring_kaputt_gibt_leer(): assert parse_udf_dstring(b"") == "" assert parse_udf_dstring(bytes(32)) == "" # Länge 0 daten = bytes([8]) + b"X" + bytes(29) + bytes([99]) # Länge > Puffer assert parse_udf_dstring(daten) == "" def test_label_normalisierung(): # Disc-Labels sind fast immer GROSS_MIT_UNTERSTRICHEN assert normalize_disc_label("PULP_FICTION") == "Pulp Fiction" assert normalize_disc_label("DIE_HARD_4.0_DE") == "Die Hard 4 0 De" assert normalize_disc_label(" doppel raum ") == "doppel raum" assert normalize_disc_label("") == "" def test_label_mit_gemischter_schreibung_bleibt(): # Wer schon sauber benannt hat, wird nicht plattgewalzt assert normalize_disc_label("Inception") == "Inception" def test_omdb_runtime_parsing(): assert parse_runtime_minutes("142 min") == 142 assert parse_runtime_minutes("N/A") == 0 assert parse_runtime_minutes("") == 0 def test_omdb_jahr_parsing(): assert parse_year("1999") == 1999 assert parse_year("2005–2012") == 2005 # Serien-Zeitspanne assert parse_year("N/A") is None assert parse_year("") is None # --- Reihenfolge der Metadaten-Quellen: exakt schlägt unscharf --------------- # # Änderung vom 26.07.2026. Vorher galt der erste Jikan-Treffer über dem Gate # 0,55 sofort als sicherer Fund, und OMDb kam nie dran — ein Kinofilm, den TMDB # verpasst, bekam damit Anime-Metadaten. Die Messwerte dazu stehen in # test_jikan_helpers.py („Alien" gegen „Alien 9" = 0,83). class _StummerClient: """Ein Client, der auf jede Frage dasselbe antwortet — und mitzählt.""" def __init__(self, antwort=None): self.antwort = antwort self.aufrufe = 0 def lookup(self, *a, **k): self.aufrufe += 1 return self.antwort def search_movie(self, *a, **k): return [] def search_tv(self, *a, **k): return [] def get_movie_details(self, *a, **k): return None def find_by_imdb(self, *a, **k): return None def _scanner(monkeypatch, jikan_antwort, omdb_antwort): """PreScan ohne __init__ (das bräuchte DB + API-Keys), Cache stillgelegt.""" from prescan import prescan as modul monkeypatch.setattr(modul, "get", lambda k: None) monkeypatch.setattr(modul, "cache_set", lambda *a, **k: None) scan = modul.PreScan.__new__(modul.PreScan) scan.tmdb = _StummerClient() scan.jikan = _StummerClient(jikan_antwort) scan.omdb = _StummerClient(omdb_antwort) return scan def _jikan(score): return {"type": "movie", "id": "mal-1", "title": "Alien 9", "source": "jikan", "match_score": score} def _omdb(): return {"type": "movie", "id": "tt0078748", "title": "Alien", "source": "omdb", "year": 1979} def test_unscharfer_jikan_treffer_laesst_omdb_zum_zug(monkeypatch): """Der eigentliche Fehler: „Alien" traf mit 0,83 auf den Anime „Alien 9", und der Film wurde nie gesucht.""" scan = _scanner(monkeypatch, _jikan(0.83), _omdb()) ergebnis = scan._scan_video("/dev/sr0", {"title": "Alien", "disc_type": "Blu-ray"}) assert ergebnis.metadata["source"] == "omdb" assert ergebnis.confidence == 0.8 assert scan.omdb.aufrufe > 0 def test_exakter_jikan_treffer_gewinnt_sofort(monkeypatch): """Der Fall, für den Jikan gebaut wurde, darf NICHT verlorengehen (AGENTS Regel B): Bei einem sicheren Treffer wird OMDb nicht mehr gefragt.""" scan = _scanner(monkeypatch, _jikan(1.0), _omdb()) ergebnis = scan._scan_video("/dev/sr0", {"title": "Alien 9", "disc_type": "Blu-ray"}) assert ergebnis.metadata["source"] == "jikan" assert ergebnis.confidence == 0.85 assert scan.omdb.aufrufe == 0 def test_unscharfer_jikan_treffer_bleibt_als_vorschlag(monkeypatch): """Hat OMDb nichts (kein Key, kein Treffer), ist der unscharfe Jikan-Treffer besser als nichts — aber er wird als VORSCHLAG ausgewiesen (0,6), nicht als sicherer Fund.""" scan = _scanner(monkeypatch, _jikan(0.83), None) ergebnis = scan._scan_video("/dev/sr0", {"title": "Alien", "disc_type": "Blu-ray"}) assert ergebnis.metadata["source"] == "jikan" assert ergebnis.confidence == 0.6 def test_ohne_jede_quelle_bleibt_es_unbekannt(monkeypatch): """Antwortet keine Quelle, bleibt es beim Disc-Titel mit Confidence 0,3 und `type: unknown` — der Bestand, unverändert. Der Rip laeuft trotzdem, die Datei heisst dann nur wie das Disc-Label.""" scan = _scanner(monkeypatch, None, None) ergebnis = scan._scan_video("/dev/sr0", {"title": "Nix Da", "disc_type": "DVD"}) assert ergebnis.confidence == 0.3 assert ergebnis.metadata["type"] == "unknown" assert ergebnis.title == "Nix Da" # ── Was auf FREMDEN Rechnern gilt (Befund 28.08.2026) ─────────────────── # # Der Commander: "du hast jetzt nur mein laufwerk analysiert oder? wenn ich # rippy weitergebe wie laeuft es dort?" # # Berechtigt. Gemessen wurde an EINEM Laufwerk (LG BU40N) und EINER Disc # (BD-50 mit BDMV/META). Diese Tests halten fest, was bei ANDEREN Discs # passieren muss -- ohne dass eine davon vorhanden sein muss. def test_bluray_ohne_metadaten_gibt_ehrlich_nichts_zurueck(tmp_path): """Sehr viele Blu-rays haben kein BDMV/META. Dann greift der Rueckfall auf das Volume-Label -- aber diese Funktion darf nichts erfinden.""" from prescan.prescan import titel_aus_bdmt (tmp_path / "BDMV" / "STREAM").mkdir(parents=True) assert titel_aus_bdmt(str(tmp_path)) is None def test_dvd_hat_kein_bdmv(tmp_path): from prescan.prescan import titel_aus_bdmt (tmp_path / "VIDEO_TS").mkdir() assert titel_aus_bdmt(str(tmp_path)) is None def test_bdmt_wird_gelesen_wenn_da(tmp_path): """Der Weg, der bei der Disc des Commanders 'Evangelion 2.22' lieferte — hier ohne Disc nachgestellt.""" from prescan.prescan import titel_aus_bdmt meta = tmp_path / "BDMV" / "META" / "DL" meta.mkdir(parents=True) (meta / "bdmt_deu.xml").write_text( '' 'Evangelion 2.22' '', encoding="utf-8") assert titel_aus_bdmt(str(tmp_path)) == "Evangelion 2.22" def test_englisch_wird_bevorzugt(tmp_path): """Die APIs suchen mit englischen Titeln besser.""" from prescan.prescan import titel_aus_bdmt meta = tmp_path / "BDMV" / "META" / "DL" meta.mkdir(parents=True) for datei, name in (("bdmt_deu.xml", "Deutscher Titel"), ("bdmt_eng.xml", "English Title")): (meta / datei).write_text( "%s" % name, encoding="utf-8") assert titel_aus_bdmt(str(tmp_path)) == "English Title" def test_makemkvcon_wird_ueber_den_katalog_gesucht(): """DER Fehler: `shutil.which("makemkvcon")` gibt unter Windows auch bei installiertem MakeMKV None zurueck -- Windows-Programme liegen in „Programme", nicht im PATH. Dieser Zweig lief dort also NIE.""" import os hier = os.path.dirname(os.path.abspath(__file__)) with open(os.path.join(hier, "prescan", "prescan.py"), encoding="utf-8") as f: # NUR Code, keine Kommentare: Der erste Anlauf dieses Tests fiel ueber # den eigenen Erklaertext, in dem der alte Aufruf zitiert wird. Ein # Waechter, der an einer Erklaerung scheitert, prueft das Falsche. code = [z for z in f if not z.strip().startswith("#")] quelle = "".join(code) assert "shutil.which(" not in quelle, ( "shutil.which findet unter Windows keine Programme aus 'Programme'") assert 'werkzeug_katalog.finden("makemkv")' in quelle