4195854bf8
Vier neue reine Bausteine, jeder mit Tests. Sie beantworten Fragen, die Rippy bisher geraten oder gar nicht gestellt hat. 1. caps.parse_preset_liste - die Preset-NAMEN, die das HandBrake DIESES Workers wirklich kennt (`--preset-list`, Format im Worker-Image gemessen). Damit endet das Raten: die Namen unterscheiden sich je HandBrake-Version, und ein erfundener Name laesst die Kompression scheitern. Richtigstellung zum SAVEPOINT v3.16: Dort galt es als unmoeglich, die Hardware-Preset-Namen auf der Rippy-VM zu ermitteln, weil dort kein Hardware-Encoder laeuft. Gemessen ist das falsch - die Kategorie `Hardware/` steht vollstaendig in der Liste (VCN, NVENC, QSV, MF). HandBrake trennt zwei Fragen: --preset-list nennt alle Presets, --help nur die nutzbaren Encoder. Zwei Fragen, zwei Quellen. 2. mounts.pfad_map_vorschlag/pfad_map_zeile - der fehlende Anschluss fuer RIPPY_PATH_MAP. Geraten werden muss dafuer nichts: Rippy hat die Freigabe selbst eingehaengt und kennt ihre Quelle (//host/share). Mountpunkt plus Quelle IST das Mapping. NFS gibt bewusst "" - Windows-Schreibweise ist nicht ableitbar (AGENTS Regel D). Der Test fand dabei sofort die dokumentierte Windows-Falle: os.path.join baute `/app/media\rippy` in einen Container-Pfad, das Mapping waere still wirkungslos geblieben. _mountpoint nutzt jetzt posixpath. 3. presets.empfehlung - "immer das Beste" (Commander-Anforderung) ohne Punktesystem: fuer jede Lage eine feste Reihenfolge echter Namen, genommen wird der erste, den der Worker kennt. Hardware nur, wenn die Familie wirklich gemeldet ist (vce -> Preset heisst VCN, sonst liefe eine AMD-Karte unter dem Intel-Namen). vaapi und MF werden nie empfohlen: fuer vaapi gibt es kein Preset, bei MF ist die Nutzbarkeit nicht ablesbar. 4. eta - Restzeit aus dem gemessenen Fortschritt. Messreihe je Phase (Rip und Kompression haben nichts miteinander zu tun), Stillstand verlaengert die Schaetzung, und unter zwei Messwerten oder 60 Sekunden Spanne gibt es ehrlich keine Aussage. Test mit den echten 25.07.-Werten: 1,44 % in 29 min landet bei "noch ca. 1 Tag 23 h" - genau die Angabe, deren Fehlen den 50-Stunden-Lauf unsichtbar machte. Dazu: HandBrakes "Invalid preset <Name>" wird uebersetzt. Vorher stand im UI nur "HandBrake endete mit Code 3" - dass der Preset-NAME das Problem ist, war daraus nicht zu erraten. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
383 lines
16 KiB
Python
383 lines
16 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
|
|
# Echte Zeile mit fps-Anhang, wie sie im Binary steht
|
|
assert get_progress_from_line(
|
|
"Encoding: task 1 of 1, 12.34 % (5.67 fps, avg 4.32 fps, ETA 00h12m34s)"
|
|
) == 12
|
|
# Ein echtes 0 % ist eine ANGABE, keine Leermeldung
|
|
assert get_progress_from_line("Encoding: task 1 of 1, 0.00 %") == 0
|
|
|
|
|
|
def test_handbrake_progress_ignoriert_scan_durchlauf():
|
|
"""Befund 25.07.2026 (Akira-UHD, live gemessen): HandBrake läuft VOR dem
|
|
Encodieren einen Scan-Durchlauf, der ebenfalls Prozente ausgibt und dabei
|
|
bis 100 % steigt. Die alte Regex nahm jede Zahl vor einem Prozentzeichen
|
|
und meldete deshalb 99 %, während der Encode bei 1 % stand.
|
|
|
|
-1 heißt „keine Encode-Fortschrittszeile" — dasselbe Muster wie bei
|
|
get_progress_from_prgv. Fremde Zeilen dürfen NIE als 0 % gelten.
|
|
"""
|
|
assert get_progress_from_line("Scanning title 1 of 1, preview 3, 30.00 %") == -1
|
|
assert get_progress_from_line("Scanning title 1 of 1, preview 10, 100.00 %") == -1
|
|
# Vorlauf-Phase: Prozente beziehen sich auf die Suche, nicht auf den Encode
|
|
assert get_progress_from_line(
|
|
"Encoding: task 1 of 1, Searching for start time, 42.00 %"
|
|
) == -1
|
|
assert get_progress_from_line("kein Fortschritt hier") == -1
|
|
assert get_progress_from_line("Muxing: this may take awhile...") == -1
|
|
assert get_progress_from_line("") == -1
|
|
|
|
|
|
def test_handbrake_progress_rechnet_zwei_durchlaeufe_zusammen():
|
|
"""Presets mit zwei Durchläufen zählen die Prozente je Durchlauf neu.
|
|
Ohne Verrechnung sprang die Anzeige mitten in der Datei zurück auf 0."""
|
|
assert get_progress_from_line("Encoding: task 1 of 2, 50.00 %") == 25
|
|
assert get_progress_from_line("Encoding: task 2 of 2, 0.00 %") == 50
|
|
assert get_progress_from_line("Encoding: task 2 of 2, 100.00 %") == 100
|
|
|
|
|
|
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 läuft 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 für die Regex (Stand 25.07.2026): vorher stand hier
|
|
line.split(",", 4)[3]. Das schnitt jede Meldung ab, die selbst ein Komma
|
|
enthält — 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 außer 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_auswurf_folgt_der_einstellung():
|
|
"""Befund 25.07.2026: `-x` (Auswurf) stand fest verdrahtet drin. Eine
|
|
Audio-CD warf damit IMMER aus, eine DVD/Blu-ray NIE — und die Einstellung
|
|
„Automatischer Auswurf" regelte keines von beidem, weil sie nirgends
|
|
gelesen wurde."""
|
|
assert "-x" in build_abcde_cmd("/dev/sr0", "/tmp/c.conf", auswerfen=True)
|
|
assert "-x" not in build_abcde_cmd("/dev/sr0", "/tmp/c.conf", auswerfen=False)
|
|
# Standard bleibt „auswerfen" — so war das Verhalten bisher
|
|
assert "-x" in build_abcde_cmd("/dev/sr0", "/tmp/c.conf")
|
|
# Die Config darf durch das weggefallene -x nicht verrutschen
|
|
ohne = build_abcde_cmd("/dev/sr0", "/tmp/c.conf", auswerfen=False)
|
|
assert ohne[ohne.index("-c") + 1] == "/tmp/c.conf"
|
|
|
|
|
|
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 für alles — eine 4K-UHD wurde
|
|
# damit auf 1080p heruntergerechnet, und beim ersten echten UHD-Rip wäre
|
|
# 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 ändern.
|
|
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 zählt 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
|
|
|
|
|
|
def test_handbrake_prueft_abbruch_bei_jeder_zeile_nicht_nur_bei_fortschritt():
|
|
"""Befund 25.07.2026 (am laufenden Akira-Job beobachtet): Der Abbruch hing
|
|
am Fortschritts-Callback, und der stieg bei unveraenderter Prozentzahl
|
|
sofort aus. Bei einem 4K-Encode mit einem Prozent je halber Stunde sah
|
|
„Abbrechen" minutenlang wirkungslos aus (gemessen: 3,4 min).
|
|
|
|
Der Abbruch-Kanal muss deshalb JEDE Ausgabezeile sehen — auch die des
|
|
Scan-Durchlaufs, der gar keine Encode-Prozente liefert.
|
|
"""
|
|
import ripping
|
|
|
|
zeilen = [
|
|
"Scanning title 1 of 1, preview 1, 10.00 %\n",
|
|
"Scanning title 1 of 1, preview 2, 20.00 %\n",
|
|
"Encoding: task 1 of 1, 0.00 %\n",
|
|
"Encoding: task 1 of 1, 0.00 %\n",
|
|
]
|
|
gesehen = []
|
|
|
|
class FakeProcess:
|
|
def __init__(self):
|
|
self.stdout = iter(zeilen)
|
|
self.returncode = 0
|
|
self.getoetet = False
|
|
|
|
def kill(self):
|
|
self.getoetet = True
|
|
|
|
def wait(self):
|
|
return 0
|
|
|
|
prozess = FakeProcess()
|
|
|
|
def abbruch_cb():
|
|
gesehen.append(1)
|
|
if len(gesehen) == 2: # beim zweiten Mal abbrechen
|
|
raise ripping.RipAbbruch()
|
|
|
|
ergebnis = ripping._handbrake_schleife(prozess, "/x.mkv", abbruch_cb, None)
|
|
|
|
assert ergebnis["status"] == "cancelled"
|
|
assert prozess.getoetet is True
|
|
# Zwei Scan-Zeilen genügten — es musste NICHT auf eine Encode-Zeile gewartet
|
|
# werden. Genau das war der Fehler.
|
|
assert len(gesehen) == 2
|
|
|
|
|
|
def test_komprimieren_fuer_globaler_schalter_schlaegt_alles():
|
|
from ripping import komprimieren_fuer
|
|
|
|
assert komprimieren_fuer("uhd", {"transcodeEnabled": False}) is False
|
|
assert komprimieren_fuer("dvd", {"transcodeEnabled": False}) is False
|
|
# Standard ist AN - Bestandsinstallationen ändern ihr Verhalten nicht
|
|
assert komprimieren_fuer("bluray", {}) is True
|
|
assert komprimieren_fuer("bluray", None) is True
|
|
|
|
|
|
def test_komprimieren_fuer_einzelner_disc_typ_abwaehlbar():
|
|
"""Der Grund dafür (Befund 25.07.2026): 4K-HEVC brauchte auf der Rippy-VM
|
|
gemessene 28-55 Stunden, weil die CPU kein AVX2 hat. Vorher gab es nur
|
|
einen globalen Schalter - wer 4K verlustfrei behalten und DVDs trotzdem
|
|
schrumpfen wollte, hatte keine Möglichkeit."""
|
|
from ripping import PRESET_KEINE, komprimieren_fuer
|
|
|
|
nur_uhd_aus = {"transcodePresetUhd": PRESET_KEINE,
|
|
"transcodePresetDvd": "H.265 MKV 576p25"}
|
|
assert komprimieren_fuer("uhd", nur_uhd_aus) is False
|
|
assert komprimieren_fuer("dvd", nur_uhd_aus) is True
|
|
assert komprimieren_fuer("bluray", nur_uhd_aus) is True
|
|
|
|
|
|
def test_preset_fuer_gibt_den_reservewert_nie_als_preset_zurueck():
|
|
"""Sonst bekaeme HandBrake `--preset keine` und würde scheitern. Wer über
|
|
"Neu komprimieren" ausdruecklich doch komprimieren will, soll ein
|
|
brauchbares Preset bekommen statt eines Fehlers."""
|
|
from ripping import DEFAULT_HB_PRESET, PRESET_KEINE, preset_fuer
|
|
|
|
assert preset_fuer("uhd", {"transcodePresetUhd": PRESET_KEINE,
|
|
"transcodePreset": "HQ 1080p30 Surround"}) == "HQ 1080p30 Surround"
|
|
# Auch ohne allgemeines Preset kommt ein gültiger Name heraus
|
|
assert preset_fuer("uhd", {"transcodePresetUhd": PRESET_KEINE}) == DEFAULT_HB_PRESET
|
|
|
|
|
|
def test_reservewert_kollidiert_mit_keinem_echten_preset():
|
|
"""Gegengeprueft gegen alle 90 Presets aus `HandBrakeCLI --preset-list`
|
|
im Worker-Image (25.07.2026) - keines heißt so."""
|
|
from ripping import PRESET_KEINE
|
|
|
|
assert PRESET_KEINE == "keine"
|
|
assert " " not in PRESET_KEINE # echte Preset-Namen haben durchweg Leerzeichen
|
|
|
|
|
|
# --- Unbekanntes Preset: aus „Code 3" wird eine handelbare Meldung -----------
|
|
|
|
|
|
def test_unbekanntes_preset_wird_erkannt():
|
|
"""Wortlaut echt gemessen (26.07.2026, Worker-Image): HandBrake antwortet
|
|
„Invalid preset <Name>" und listet danach die gültigen auf."""
|
|
from ripping import unbekanntes_preset
|
|
|
|
assert unbekanntes_preset("Invalid preset Gibt Es Nicht 999p") == "Gibt Es Nicht 999p"
|
|
assert unbekanntes_preset(" Invalid preset H.265 XYZ 4K ") == "H.265 XYZ 4K"
|
|
# Alles andere ist kein Preset-Fehler
|
|
assert unbekanntes_preset("Valid presets are:") == ""
|
|
assert unbekanntes_preset("Encoding: task 1 of 1, 5.00 %") == ""
|
|
assert unbekanntes_preset("") == ""
|
|
assert unbekanntes_preset(None) == ""
|
|
|
|
|
|
def test_falsches_preset_erklaert_den_fehlschlag_statt_nur_den_code():
|
|
"""Vorher stand im UI nur „HandBrake endete mit Code 3" — daraus war nicht
|
|
zu erraten, dass der Preset-NAME das Problem ist. Der Fall wird häufiger,
|
|
weil ein Remote-Worker eine andere HandBrake-Version haben kann."""
|
|
import ripping
|
|
|
|
class FakeProcess:
|
|
def __init__(self):
|
|
self.stdout = iter([
|
|
"Invalid preset H.265 VCN 2160p 4K\n",
|
|
"Valid presets are:\n",
|
|
])
|
|
self.returncode = 3
|
|
|
|
def kill(self):
|
|
pass
|
|
|
|
def wait(self):
|
|
return 3
|
|
|
|
ergebnis = ripping._handbrake_schleife(FakeProcess(), "/gibt-es-nicht.mkv")
|
|
|
|
assert ergebnis["status"] == "error"
|
|
assert "H.265 VCN 2160p 4K" in ergebnis["error"]
|
|
assert "Bestes wählen" in ergebnis["error"]
|
|
assert ergebnis["return_code"] == 3
|
|
|
|
|
|
def test_fehler_ohne_preset_problem_bleibt_der_alte():
|
|
import ripping
|
|
|
|
class FakeProcess:
|
|
def __init__(self):
|
|
self.stdout = iter(["irgendwas ganz anderes\n"])
|
|
self.returncode = 1
|
|
|
|
def kill(self):
|
|
pass
|
|
|
|
def wait(self):
|
|
return 1
|
|
|
|
ergebnis = ripping._handbrake_schleife(FakeProcess(), "/gibt-es-nicht.mkv")
|
|
assert ergebnis["error"] == "HandBrake endete mit Code 1"
|