Files
rippy/docker/worker/test_ripping_helpers.py
T
Hitonabi e84afc1718
Ampel / ampel (push) Successful in 29s
feat(transcode): ein HandBrake-Preset je Disc-Typ statt eines fuer alles
Rueckfrage des Commanders beim ersten echten UHD-Rip: "merkt Rippy, dass es
eine UHD ist, und nimmt direkt das 4K-Preset?" Antwort war nein. Die
Kompression fragte den Disc-Typ ueberhaupt nicht:

    preset = einstellungen.get("transcodePreset") or DEFAULT_HB_PRESET

Live eingestellt war "HQ 1080p30 Surround". Der gerade laufende Akira-Rip
waere also verlustfrei in 4K gerippt und danach auf 1080p heruntergerechnet
worden - und mit keepOriginal=False waere der 4K-Rohschnitt danach geloescht
worden. Umgekehrt wurde eine DVD auf 1080p hochskaliert, was nichts bringt.

- preset_fuer(disc_type, einstellungen) in ripping.py, pure und getestet.
  Reihenfolge: Preset des Disc-Typs -> allgemeines transcodePreset ->
  DEFAULT_HB_PRESET. Bestandsinstallationen aendern ihr Verhalten NICHT,
  solange die neuen Felder nicht gespeichert sind.
- Drei Einstellungen: transcodePresetDvd / transcodePresetBluray /
  transcodePresetUhd. transcodePreset bleibt als Rueckfall bestehen.
- transcode_files holt den Disc-Typ aus dem Job-Datensatz und schreibt ihn
  mit ins Log ("Disc-Typ 'uhd', Preset '...'").
- UI: drei Auswahlfelder statt einem, mit Klartext dazu, warum eine 4K-UHD
  auf ein 2160p-Preset gehoert.

Preset-Namen stammen aus "HandBrakeCLI --preset-list" im Worker-Image
(HandBrake 1.6.1) - nicht aus dem Kopf (AGENTS Regel D):
H.265 MKV 2160p60 4K, HQ 2160p60 4K HEVC Surround,
Super HQ 2160p60 4K HEVC Surround, H.265 MKV 1080p30, HQ 1080p30 Surround,
Super HQ 1080p30 Surround, H.265 MKV 576p25, H.265 MKV 480p30,
HQ 576p25 Surround.

Sofortmassnahme am laufenden Job (auf Ansage des Commanders): keepOriginal
auf True gesetzt - nur dieses eine Feld, gegengeprueft dass kein anderer
Schluessel veraendert wurde. Damit ueberlebt der 4K-Rohschnitt die
Kompression.

ACHTUNG - Deploy bewusst NICHT ausgefuehrt: docker compose up -d --build
wuerde den Worker-Container neu erstellen und den laufenden Akira-Rip
abbrechen. Erst nach Abschluss des Jobs deployen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-25 11:38:52 +02:00

181 lines
7.9 KiB
Python

"""Tests für ripping.py: Kommando-Bau und Fortschritts-Parsing.
Deckt genau die Stellen ab, an denen Reviews erfundene Schnittstellen fanden
(abcde-Flags, Celery-API, HandBrake-Regex) — damit so etwas nie wieder still liegt.
Das PRGV-Format stammt aus der MakeMKV-Doku (makemkv.com/developers/usage.txt).
"""
import os
from ripping import (
build_abcde_cmd,
build_handbrake_cmd,
build_makemkv_cmd,
get_progress_from_line,
get_progress_from_prgv,
parse_msg,
write_abcde_config,
)
def test_makemkv_cmd_vollstaendig():
cmd = build_makemkv_cmd("/dev/sr0", "/app/media/dvd/x")
assert cmd[0] == "makemkvcon"
assert "-r" in cmd # Robot-Mode: maschinenlesbar
assert "--noscan" in cmd # Scan hängt/crasht im Container (23.07.)
assert "--progress=-same" in cmd # Fortschritt im selben Stream
assert "mkv" in cmd
assert "dev:/dev/sr0" in cmd # Geräte-Notation laut Doku
assert cmd[-2:] == ["all", "/app/media/dvd/x"]
def test_handbrake_cmd_arbeitet_auf_datei_nicht_geraet():
"""HandBrake kann AACS nicht — es bekommt IMMER die MKV-Datei aus dem
MakeMKV-Rip, nie das Laufwerk (die alte Direkt-am-Gerät-Pipeline war
für Blu-rays prinzipiell funktionsunfähig)."""
cmd = build_handbrake_cmd("/app/temp/raw/x/t00.mkv", "/app/media/bluray/x/t00.mkv")
assert cmd[0] == "HandBrakeCLI"
assert cmd[cmd.index("--input") + 1] == "/app/temp/raw/x/t00.mkv"
assert cmd[cmd.index("--output") + 1] == "/app/media/bluray/x/t00.mkv"
assert "--preset" in cmd
assert "--all-audio" in cmd # alle Sprachen behalten
assert "--all-subtitles" in cmd
def test_handbrake_progress_parsing():
# Testfund 22.07.: echtes HandBrake schreibt „45.50 %" MIT Leerzeichen
assert get_progress_from_line("Encoding: task 1 of 1, 45.50 %") == 45
assert get_progress_from_line("Encoding: task 1 of 1, 100.00 %") == 100
assert get_progress_from_line("kein Fortschritt hier") == 0
def test_prgv_parsing():
# PRGV:current,total,max — total/max ist der Gesamtfortschritt
assert get_progress_from_prgv("PRGV:100,32768,65536") == 50
assert get_progress_from_prgv("PRGV:0,65536,65536") == 100
assert get_progress_from_prgv("PRGV:0,0,65536") == 0
def test_prgv_parsing_ignoriert_fremde_zeilen():
# -1 heißt „keine Fortschrittszeile" — MSG-Zeilen dürfen NIE als 0% gelten,
# sonst springt die Anzeige ständig auf null zurück.
assert get_progress_from_prgv('MSG:1005,0,1,"MakeMKV gestartet","%1","x"') == -1
assert get_progress_from_prgv("irgendwas") == -1
assert get_progress_from_prgv("PRGV:kaputt") == -1
def test_parse_msg_trennt_code_und_klartext():
"""Echte Zeilen aus einem makemkvcon-Lauf vom 25.07.2026 (Akira UHD).
Feld 4 ist laut https://www.makemkv.com/developers/usage.txt der fertig
zusammengesetzte Klartext — genau der landet im Rippy-Log.
"""
assert parse_msg(
'MSG:1005,0,1,"MakeMKV v1.18.4 linux(x64-release) started","%1 started","MakeMKV v1.18.4 linux(x64-release)"'
) == (1005, "MakeMKV v1.18.4 linux(x64-release) started")
assert parse_msg(
'MSG:1011,0,1,"Using LibreDrive mode (v06.3 id=866A98CB9C4E)","%1","Using LibreDrive mode (v06.3 id=866A98CB9C4E)"'
) == (1011, "Using LibreDrive mode (v06.3 id=866A98CB9C4E)")
def test_parse_msg_liest_die_uhd_fehlermeldung():
"""3303 ist der Befund, um den es beim ganzen KEYDB-Thema geht: das
Laufwerk laeuft im LibreDrive-Modus, MakeMKV kennt nur den Schluessel
DIESER Pressung nicht. Ohne diese Zeile im Log raet der Commander."""
assert parse_msg(
'MSG:3303,16777216,0,"The volume key is unknown for this disc - video can\'t be decrypted","The volume key is unknown for this disc - video can\'t be decrypted"'
) == (3303, "The volume key is unknown for this disc - video can't be decrypted")
assert parse_msg('MSG:5010,0,0,"Failed to open disc","Failed to open disc"') == (
5010,
"Failed to open disc",
)
def test_parse_msg_schneidet_meldungen_mit_komma_nicht_ab():
"""DER Grund fuer die Regex (Stand 25.07.2026): vorher stand hier
line.split(",", 4)[3]. Das schnitt jede Meldung ab, die selbst ein Komma
enthaelt — und MakeMKV schreibt solche laufend. Im Log stand dann nur noch
ein Satzfragment, das mehr verwirrt als hilft."""
zeile = (
'MSG:3025,0,3,"Title #1 has length of 12 seconds, which is less than '
'minimum title length of 120 seconds and was therefore skipped",'
'"Title #%1 has length of %2 seconds which is less than minimum title '
'length of %3 seconds and was therefore skipped","1","12","120"'
)
code, text = parse_msg(zeile)
assert code == 3025
assert text.endswith("and was therefore skipped")
assert "which is less than" in text
def test_parse_msg_ignoriert_fremde_zeilen():
# Alles ausser MSG muss None liefern, sonst landet Fortschritts-Rauschen
# (PRGV kommt mehrmals pro Sekunde) als Log-Eintrag in der Datenbank.
assert parse_msg("PRGV:100,32768,65536") is None
assert parse_msg('DRV:0,2,999,12,"BD-RE ASUS BW-16D1HT","AKIRA","/dev/sr0"') is None
assert parse_msg("TCOUNT:5") is None
assert parse_msg("") is None
assert parse_msg("irgendwelcher Muell ohne Struktur") is None
def test_abcde_cmd_hat_genau_ein_ausgabeformat():
"""Review-Fund 22.07.: '-o' stand doppelt (Format UND Verzeichnis) — abcde
parste das Verzeichnis als Format, CD-Ripping war nie funktionsfähig."""
cmd = build_abcde_cmd("/dev/sr0", "/tmp/test.abcde.conf")
assert cmd.count("-o") == 1
assert cmd[cmd.index("-o") + 1] == "flac"
assert "-c" in cmd
assert cmd[cmd.index("-c") + 1] == "/tmp/test.abcde.conf"
assert "-N" in cmd # nicht-interaktiv, sonst hängt der Worker
def test_abcde_config_enthaelt_zielverzeichnis():
pfad = write_abcde_config("/app/media/cd/test123")
try:
with open(pfad, encoding="utf-8") as f:
inhalt = f.read()
assert "OUTPUTDIR='/app/media/cd/test123'" in inhalt
assert "INTERACTIVE=n" in inhalt
finally:
os.unlink(pfad)
def test_preset_fuer_nimmt_das_preset_des_disc_typs():
# Befund 25.07.2026: vorher galt EIN Preset fuer alles — eine 4K-UHD wurde
# damit auf 1080p heruntergerechnet, und beim ersten echten UHD-Rip waere
# die 4K-Aufloesung still verlorengegangen.
from ripping import preset_fuer
einstellungen = {
"transcodePreset": "HQ 1080p30 Surround",
"transcodePresetDvd": "H.265 MKV 576p25",
"transcodePresetBluray": "H.265 MKV 1080p30",
"transcodePresetUhd": "H.265 MKV 2160p60 4K",
}
assert preset_fuer("uhd", einstellungen) == "H.265 MKV 2160p60 4K"
assert preset_fuer("bluray", einstellungen) == "H.265 MKV 1080p30"
assert preset_fuer("dvd", einstellungen) == "H.265 MKV 576p25"
def test_preset_fuer_faellt_auf_das_allgemeine_preset_zurueck():
# Bestandsinstallationen kennen die drei neuen Felder nicht. Solange der
# Nutzer sie nicht speichert, MUSS sich sein Verhalten nicht aendern.
from ripping import preset_fuer
alt = {"transcodePreset": "HQ 1080p30 Surround"}
assert preset_fuer("uhd", alt) == "HQ 1080p30 Surround"
assert preset_fuer("dvd", alt) == "HQ 1080p30 Surround"
# Leere Zeichenkette zaehlt als "nicht gesetzt" (leeres Select-Feld im UI)
assert preset_fuer("uhd", {"transcodePresetUhd": " ", "transcodePreset": "X"}) == "X"
def test_preset_fuer_ohne_einstellungen_nimmt_den_eingebauten_standard():
# Unbekannter Disc-Typ, leere oder fehlende Einstellungen: nie None, nie
# Absturz — sonst stirbt die Kompression an einem leeren --preset-Argument.
from ripping import DEFAULT_HB_PRESET, preset_fuer
assert preset_fuer("uhd", {}) == DEFAULT_HB_PRESET
assert preset_fuer("", None) == DEFAULT_HB_PRESET
assert preset_fuer(None, {}) == DEFAULT_HB_PRESET
assert preset_fuer("cd", {"transcodePresetUhd": "egal"}) == DEFAULT_HB_PRESET