fix(windows): Der Schalter, den es nicht gibt, und vierzehn weitere Funde
Commander: „Kompression fehlgeschlagen bei Spartacus … _t01.mkv: HandBrake
endete mit Code 0" — 16,5 GB fertiger Rohschnitt, und die Kompression war in
derselben Sekunde vorbei, in der sie begann.
Nachgestellt mit genau der Befehlszeile, die Rippy baute:
unknown option (--audio-codec)
HandBrake has exited. $? = 0
Den Schalter `--audio-codec` gibt es bei HandBrakeCLI nicht; er heisst
`-E` / `--aencoder`. Ein unbekannter Schalter ist fuer HandBrake kein Fehler,
der Rueckgabewert ist 0. Der Test dazu forderte den falschen Namen sogar ein.
Daraus wurde ein Rundgang durch den Windows-Pfad. Alles unten ist gemessen,
nichts vermutet (Regel D).
## Die Kompression
1. `--aencoder` statt `--audio-codec`. Am mitgelieferten HandBrakeCLI 1.11.2
gemessen, mit einem 5-Sekunden-Encode auf der echten Roh-Datei bestaetigt.
2. HandBrakes letzte Zeilen werden aufgehoben (12 gepuffert, 4 in der
Meldung) und `unknown option (...)` wird als eigener Fall erkannt, VOR
allen anderen. Vorher wurde jede Zeile weggeworfen, die kein Fortschritt
war — bei Rueckgabewert 0 blieb damit keine Auskunft uebrig. Die geratene
Zeile „Meist ist der Zielordner nicht beschreibbar" ist raus; sie war
falsch und hat die Suche in die falsche Richtung geschickt.
3. Ein `ue`-Umlaut im Pfad toetete die Kompression. HandBrake schreibt zwei
Kodierungen in denselben Strom (derselbe Pfad einmal UTF-8, einmal CP850).
In CP850 ist das Byte 0x81, und das ist in cp1252 — was `text=True` auf
deutschem Windows waehlt — undefiniert:
UnicodeDecodeError: charmap codec can't decode byte 0x81
Neu: `rip/handbrake_aufruf.py` mit `HB_LESEN`, benutzt von ripping.py und
caps.py. Bewusst nicht binaer wie bei makemkvcon: HandBrake trennt
Fortschrittszeilen mit CR, im Binaermodus waere der Balken weg.
## Die Rohdaten
4. `rohdaten.kandidaten` machte aus dem Arbeitsordner `F:\` ein `F:` und
verband damit weiter. Das ist unter Windows der aktuelle Ordner auf
Laufwerk F, nicht dessen Wurzel — 16,5 GB waren unsichtbar, und der
Wiederholen-Dialog bot nur „Neu rippen" an. Die Falle steht woertlich im
Kopf von `pfade.verbinden`.
5. Gesucht wurde unter der heutigen Einstellung statt unter der Wahl DIESES
Rips (`meta["work_dir"]`). Genau dafuer wurde rohdaten.py am 26.07.
gebaut; repariert wurde damals die Kandidatenliste, nicht der Aufrufer.
Neu: `_arbeitsverzeichnis_des_jobs`, benutzt an vier Stellen.
6. Zwei Speicher fuer dieselben Ordner: Die Oberflaeche schreibt
`outputDir`/`workDir` in die Datenbank, `betrieb` liest `storage.*` aus
der Konfigurationsdatei, und die schreibt niemand. Gemessen: eingestellt
`E:\Rippy`, angezeigt `C:\Users\...\Videos\Rippy`. Neu:
`betrieb.mit_einstellungen`.
## Das Laufwerk
7. `device_info` fing den OSError ab und lieferte „unknown" ohne den Grund.
Nach einem Rip mit Lesefehlern beantwortete das Laufwerk keine
Medien-Abfragen mehr (Win32-Fehler 1), die Geraete-Auskunft aber schon —
im UI stand eine volle Laufwerkskarte, kein Rip startbar, und im
Protokoll das laengst veraltete „Disc erkannt". Neu: `ZUGRIFFS_GRUENDE`,
ein Feld `grund` im Laufwerks-Eintrag und eine Protokollzeile je Wechsel.
Eine fehlgeschlagene Disc-Erkennung wird ebenfalls protokolliert.
8. Der Linux-Treiber nannte ein unzugaengliches Laufwerk „empty", waehrend
Windows richtig „unknown" sagt. Angeglichen, samt Feld-Paritaet.
9. `CreateFileW`, `DeviceIoControl` und `CloseHandle` hatten weder `restype`
noch `argtypes` — 32-Bit-`c_int` fuer einen 64-Bit-HANDLE, in beide
Richtungen. Mit `restype` aendert sich der Fehlerwert von -1 auf
0xFFFFFFFFFFFFFFFF; die Pruefung deckt jetzt beides ab. Am echten
Laufwerk gegengeprueft, Fehlerpfad eingeschlossen.
10. Der Vor-Scan lief bei JEDER eingelegten Disc ein `makemkvcon info` mit
120 s Zeitgrenze — 20 bis 120 Sekunden „Disc wird gelesen". Frueher war
das schnell, weil der Zweig unter Windows nie lief (`shutil.which`,
repariert am 28.08.). Das Ergebnis landete allein in `toc["tracks"]`,
das niemand liest: Der Rip-Dialog holt seine Liste ueber
`/devices/{id}/scan-tracks`, wenn sie gebraucht wird. Entfernt.
## Notbremsen
11. `_frei_bytes` suchte den naechsten vorhandenen Ordner selbst.
`os.path.dirname("Q:\\")` gibt sich selbst zurueck — ein
Arbeitsverzeichnis auf einer abgezogenen Platte haette den Job vor dem
Rip stumm haengen lassen. Benutzt jetzt
`pfade.naechster_vorhandener`, das den Abbruch seit V2-1 hat.
12. `naechster_vorhandener` haelt Laufwerks- und UNC-Wurzeln jetzt absolut.
13. `aufraeum_skript` baut sein `rmdir /s /q` aus `InstallLocation` in der
Registry. Waere das eine Laufwerks-Wurzel, loeschte die Deinstallation
das Laufwerk. Nicht beobachtet, aber nicht wiedergutzumachen — der
Loeschbefehl bleibt in dem Fall weg.
## Lesefehler
MakeMKV sicherte 1 von 2 Titeln, endete mit 0, und Rippy schrieb „Rip
fertig". Jetzt gibt es eine Warnung, auch wenn der Rip als Erfolg endet, und
die MSG-Nummer steht im Protokoll: MakeMKVs Texte sind uebersetzt, die
Nummern nicht.
## Aus der Gegenprobe am laufenden Rippy
Die erste Fassung dieses Standes war installiert, als der Commander meldete:
„nun oeffnen sich diverse fenster im hintergrund, gehen ganz kurz auf und dann
wieder zu. Das laufwerk hoert auch einfach auf zu lesen." Beides Altlasten,
die erst durch die neue Protokollzeile sichtbar wurden.
14. Prozesserzeugung mitgeschnitten:
14:54:40 timeout.exe timeout 4 ls -d C:\Users\...\d7ee6c06-...
14:54:40 WindowsTerminal.exe
`rohdaten.pruefen` fragt mit `timeout N ls -d`, ob es ein Verzeichnis
gibt. Unter Linux ist das richtig (os.path.isdir kann an einem toten
CIFS-Mount im Kernel haengen, ein Kindprozess laesst sich abbrechen).
Unter Windows ist es dreifach falsch: timeout.exe gibt es dort, kennt
aber weder `ls` noch `-d`; sie braucht eine Konsole, und die reisst
Windows auf; und ihr Rueckgabewert ist nie 0, die Antwort lautete also
„weg" fuer JEDES Verzeichnis. Rohdaten waren unter Windows
grundsaetzlich unsichtbar. Neu: `nativ_nachsehen()`. Die zwei
gleichartigen Aufrufe in mounts.py bekommen dieselbe Absicherung.
15. Der Waechter fragte das Laufwerk alle drei Sekunden ab — auch mitten im
Rip, also drei CreateFileW plus IOCTLs auf ein Geraet, das makemkvcon
gerade liest:
12:49:52 bluray-Rip gestartet
12:50:09 [watcher] Laufwerk G: beantwortet keine Medien-Abfragen
12:50:12 MSG 2003 SCSI-Fehler ILLEGAL REQUEST:INVALID FIELD IN CDB
12:50:12 makemkvcon endete mit Code 11
`_auto_prescan` haelt sich seit dem 29.08.2026 an die Regel „waehrend
eines Rips wird nicht gescannt"; die Laufwerksabfrage tat es nicht.
Jetzt gilt in der Zeit der letzte bekannte Stand.
## Zwei Tests, die gelogen haben
* `assert "--audio-codec" in cmd` schrieb den Fehler fest.
* `lambda: {}` als Doppelgaenger fuer `get_settings(key, bei_fehler_leer)`
brach, sobald ein Aufrufer einen Parameter benutzte — und zeigte dann auf
den Code statt auf sich selbst.
977 Tests gruen, ruff sauber.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
b6a93aa726
commit
d3e86d2641
+148
-8
@@ -37,6 +37,7 @@ from rippy.drives.linux import ( # noqa: F401
|
||||
)
|
||||
from rippy.drives.linux import auswerfen_versuchen as wirf_disc_aus # noqa: F401
|
||||
from rippy.platform.winlauf import OHNE_FENSTER
|
||||
from rippy.rip.handbrake_aufruf import HB_LESEN
|
||||
from rippy.rip.makemkv_aufruf import KRITISCHE_CODES, text_von
|
||||
from rippy.rip.makemkv_aufruf import quelle as makemkv_quelle
|
||||
from rippy.tools import katalog as werkzeuge
|
||||
@@ -88,6 +89,24 @@ def check_cdparanoia_installed() -> bool:
|
||||
return shutil.which("cdparanoia") is not None
|
||||
|
||||
|
||||
#: Sprachunabhaengige Marke fuer „die Disc liess sich stellenweise nicht lesen".
|
||||
#:
|
||||
#: Am 30.08.2026 im Protokoll des Commanders abgelesen (Spartacus Disc 2,
|
||||
#: deutschsprachiges MakeMKV):
|
||||
#:
|
||||
#: Encountered 29 errors of type 'Read Error' - see
|
||||
#: http://www.makemkv.com/errors/read/
|
||||
#: Das Kopieren wurde abgeschlossen. 1 Titel wurden gesichert, 1 schlugen fehl.
|
||||
#:
|
||||
#: Die URL steht auch in der deutschen Fassung englisch da — sie ist damit die
|
||||
#: einzige Stelle dieser Meldung, auf die Verlass ist. Die MSG-NUMMER waere
|
||||
#: besser (so macht es KRITISCHE_CODES), aber sie war im Protokoll nicht zu
|
||||
#: sehen: `melde_makemkv` schrieb sie bis heute nicht mit. Das ist behoben —
|
||||
#: beim naechsten Lesefehler steht die Nummer im Log, und dann gehoert sie
|
||||
#: hierher statt dieser Textsuche.
|
||||
LESEFEHLER_MARKE = "makemkv.com/errors/read"
|
||||
|
||||
|
||||
def build_makemkv_cmd(device_path: str, output_dir: str, titel: str = "all") -> list:
|
||||
"""Baut das MakeMKV-Kommando (pure Funktion, testbar).
|
||||
|
||||
@@ -273,6 +292,9 @@ def rip_titel_auswahl(device_path: str, output_dir: str, titel_liste: list,
|
||||
Titel oder 'all' — also ein Aufruf je Titel, Fortschritt anteilig)."""
|
||||
gesamt = len(titel_liste)
|
||||
alle_dateien = []
|
||||
# Ein Lesefehler in Titel 1 darf nicht verschwinden, nur weil Titel 2
|
||||
# sauber durchlief — je Titel laeuft ein eigener makemkvcon.
|
||||
lesefehler = False
|
||||
for index, nr in enumerate(titel_liste):
|
||||
def anteilig(p, _i=index):
|
||||
if progress_cb:
|
||||
@@ -280,13 +302,16 @@ def rip_titel_auswahl(device_path: str, output_dir: str, titel_liste: list,
|
||||
|
||||
ergebnis = run_makemkv(device_path, output_dir, progress_cb=anteilig, titel=str(nr),
|
||||
log_cb=log_cb)
|
||||
lesefehler = lesefehler or bool(ergebnis.get("lesefehler"))
|
||||
if ergebnis.get("status") == "cancelled":
|
||||
return ergebnis
|
||||
if ergebnis.get("status") != "success":
|
||||
ergebnis["error"] = f"Titel {nr}: {ergebnis.get('error')}"
|
||||
ergebnis["lesefehler"] = lesefehler
|
||||
return ergebnis
|
||||
alle_dateien = ergebnis.get("files", []) # kumulativ: run_makemkv listet den Ordner
|
||||
return {"status": "success", "output_dir": output_dir, "files": alle_dateien}
|
||||
return {"status": "success", "output_dir": output_dir, "files": alle_dateien,
|
||||
"lesefehler": lesefehler}
|
||||
|
||||
|
||||
def laengster_titel(dauern: dict, meta: dict = None):
|
||||
@@ -362,7 +387,7 @@ def lies_datei_dauer(pfad: str, timeout: int = 120) -> int:
|
||||
try:
|
||||
ergebnis = subprocess.run(
|
||||
[werkzeug("handbrake"), "--scan", "-i", pfad],
|
||||
capture_output=True, text=True, timeout=timeout,
|
||||
capture_output=True, timeout=timeout, **HB_LESEN,
|
||||
creationflags=OHNE_FENSTER,
|
||||
)
|
||||
except (OSError, subprocess.TimeoutExpired):
|
||||
@@ -507,7 +532,7 @@ def build_handbrake_cmd(input_path: str, output_path: str,
|
||||
Sprachen drin — und das ist bei einer verlustfreien Ablage richtig.
|
||||
|
||||
`--audio-lang-list` zusammen mit `--first-audio` heißt: HandBrake pickt pro
|
||||
Sprache genau die erste (beste) Tonspur heraus. `--audio-codec copy` reicht
|
||||
Sprache genau die erste (beste) Tonspur heraus. `--aencoder copy` reicht
|
||||
diese dann verlustfrei durch, statt sie auf Stereo herunterzurechnen.
|
||||
"""
|
||||
befehl = [
|
||||
@@ -539,7 +564,23 @@ def build_handbrake_cmd(input_path: str, output_path: str,
|
||||
if audio:
|
||||
befehl += ["--audio-lang-list", ",".join(audio)]
|
||||
befehl.append("--first-audio")
|
||||
befehl += ["--audio-codec", "copy", "--audio-fallback", "av_aac"]
|
||||
# `--aencoder`, NICHT `--audio-codec` (Befund 30.08.2026).
|
||||
#
|
||||
# Den Schalter `--audio-codec` gibt es bei HandBrakeCLI nicht und hat es
|
||||
# nie gegeben — er heisst `-E` / `--aencoder`. Am mitgelieferten
|
||||
# HandBrakeCLI 1.11.2 nachgestellt, mit Rippys eigener Befehlszeile:
|
||||
#
|
||||
# unknown option (--audio-codec)
|
||||
# HandBrake has exited. $? = 0
|
||||
#
|
||||
# Ein ganzer Blu-ray-Rip (16,5 GB) lief damit ins Leere: HandBrake war
|
||||
# in derselben Sekunde wieder weg, in der es startete, und meldete das
|
||||
# als ERFOLG. Der Test darunter forderte den falschen Namen sogar ein
|
||||
# (`assert "--audio-codec" in cmd`) — ein Test, der einen Fehler
|
||||
# festschreibt, statt ihn zu finden.
|
||||
#
|
||||
# `copy` ist als Wert gueltig (in `--help` gelistet, ebenda geprueft).
|
||||
befehl += ["--aencoder", "copy", "--audio-fallback", "av_aac"]
|
||||
untertitel = [s for s in (untertitel_sprachen or []) if s]
|
||||
if untertitel:
|
||||
befehl += ["--subtitle-lang-list", ",".join(untertitel)]
|
||||
@@ -644,9 +685,9 @@ def run_handbrake(input_path: str, output_path: str, preset: str = DEFAULT_HB_PR
|
||||
audio_sprachen, untertitel_sprachen),
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
text=True,
|
||||
bufsize=1,
|
||||
creationflags=OHNE_FENSTER,
|
||||
**HB_LESEN,
|
||||
)
|
||||
return _handbrake_schleife(process, output_path, abbruch_cb, progress_cb)
|
||||
except Exception as e:
|
||||
@@ -673,10 +714,67 @@ def unbekanntes_preset(zeile: str) -> str:
|
||||
return text[len(kopf):].strip() if text.startswith(kopf) else ""
|
||||
|
||||
|
||||
#: Wie viele Ausgabezeilen von HandBrake fuer den Fehlerfall aufgehoben werden.
|
||||
HB_ZEILEN_PUFFER = 12
|
||||
#: ... und wie viele davon in die Fehlermeldung wandern. Sie steht in der
|
||||
#: Jobzeile und im UI; zwoelf Zeilen Muxer-Statistik waeren dort unlesbar.
|
||||
HB_ZEILEN_MELDUNG = 4
|
||||
|
||||
_UNBEKANNTER_SCHALTER = re.compile(r"unknown option \(([^)]*)\)")
|
||||
|
||||
|
||||
def unbekannter_schalter(zeile: str) -> str:
|
||||
"""Meldet diese Zeile einen Schalter, den DIESES HandBrake nicht kennt?
|
||||
|
||||
## Der Befund des Commanders (30.08.2026)
|
||||
|
||||
> „Kompression fehlgeschlagen bei Spartacus … _t01.mkv: HandBrake endete
|
||||
> mit Code 0 — Roh-Datei bleibt erhalten"
|
||||
|
||||
16,5 GB Rohschnitt, und die Kompression war in derselben Sekunde vorbei,
|
||||
in der sie begann — fuer einen Scan-Durchlauf haette das nicht gereicht.
|
||||
Am mitgelieferten HandBrakeCLI 1.11.2 nachgestellt, mit genau der
|
||||
Befehlszeile, die Rippy baute:
|
||||
|
||||
unknown option (--audio-codec)
|
||||
HandBrake has exited.
|
||||
$? = 0
|
||||
|
||||
**Der Rueckgabewert ist 0.** HandBrake meldet einen Tippfehler in seiner
|
||||
eigenen Befehlszeile als ERFOLG. Rippy sah nur „Code 0" und keine Datei —
|
||||
und riet daraufhin auf „Zielordner nicht beschreibbar". Das war falsch,
|
||||
und es schickte die Suche in die vollkommen falsche Richtung.
|
||||
|
||||
Der Schalter ist repariert (siehe `build_handbrake_cmd`). Diese Pruefung
|
||||
bleibt trotzdem: Der naechste falsche Schalter soll sich SELBST melden,
|
||||
statt wieder einen ganzen Rip zu kosten.
|
||||
"""
|
||||
treffer = _UNBEKANNTER_SCHALTER.search(zeile or "")
|
||||
return treffer.group(1).strip() if treffer else ""
|
||||
|
||||
|
||||
def hb_schluss(zeilen) -> str:
|
||||
"""HandBrakes letzte Worte als Anhang fuer eine Fehlermeldung (pure).
|
||||
|
||||
Bis zum 30.08.2026 warf `_handbrake_schleife` jede Zeile weg, die kein
|
||||
Fortschritt war. Im Fehlerfall blieb damit nur der Rueckgabewert uebrig —
|
||||
und wenn der 0 ist, sagt er nichts. Der Grund stand die ganze Zeit in der
|
||||
Ausgabe, nur hoerte niemand zu.
|
||||
"""
|
||||
sauber = [z.strip() for z in (zeilen or []) if z and z.strip()]
|
||||
if not sauber:
|
||||
return ""
|
||||
return " — HandBrake sagte zuletzt: " + " | ".join(sauber[-HB_ZEILEN_MELDUNG:])
|
||||
|
||||
|
||||
def _handbrake_schleife(process, output_path: str, abbruch_cb=None, progress_cb=None) -> dict:
|
||||
"""Liest HandBrakes Ausgabe und wertet sie aus. Eigene Funktion, damit die
|
||||
Reihenfolge (Abbruch VOR Fortschritt) ohne echtes HandBrake testbar ist."""
|
||||
falsches_preset = ""
|
||||
falscher_schalter = ""
|
||||
# Die letzten Zeilen aufheben — im Fehlerfall sind sie die einzige
|
||||
# Auskunft, die es ueberhaupt gibt (siehe `hb_schluss`).
|
||||
letzte_zeilen = []
|
||||
try:
|
||||
for line in process.stdout:
|
||||
# Zuerst der Abbruch — unabhängig davon, ob die Zeile überhaupt
|
||||
@@ -684,8 +782,14 @@ def _handbrake_schleife(process, output_path: str, abbruch_cb=None, progress_cb=
|
||||
# sich die Prozentzahl bewegt (Befund 25.07.2026).
|
||||
if abbruch_cb:
|
||||
abbruch_cb()
|
||||
if line.strip():
|
||||
letzte_zeilen.append(line)
|
||||
if len(letzte_zeilen) > HB_ZEILEN_PUFFER:
|
||||
del letzte_zeilen[0]
|
||||
if not falsches_preset:
|
||||
falsches_preset = unbekanntes_preset(line)
|
||||
if not falscher_schalter:
|
||||
falscher_schalter = unbekannter_schalter(line)
|
||||
progress = get_progress_from_line(line)
|
||||
# >= 0: ein echtes 0 % ist eine Angabe und muss durch. Der alte
|
||||
# Filter `> 0` verwarf den gesamten ersten Prozentpunkt — bei
|
||||
@@ -702,6 +806,24 @@ def _handbrake_schleife(process, output_path: str, abbruch_cb=None, progress_cb=
|
||||
if process.returncode == 0 and os.path.exists(output_path):
|
||||
return {"status": "success", "output_path": output_path}
|
||||
|
||||
# Ein Schalter, den DIESES HandBrake nicht kennt, ist ein Fehler in
|
||||
# RIPPY — und er kommt mit Rueckgabewert 0 daher (siehe
|
||||
# `unbekannter_schalter`). Deshalb steht die Pruefung VOR allen
|
||||
# anderen: sonst landet der Fall unten bei „HandBrake endete mit Code
|
||||
# 0", und dort ist er nicht zu erraten. Genau das kostete am
|
||||
# 30.08.2026 einen fertigen 16,5-GB-Rip.
|
||||
if falscher_schalter:
|
||||
return {
|
||||
"status": "error",
|
||||
"error": (
|
||||
'Rippy hat HandBrake den Schalter „%s" übergeben, den '
|
||||
'diese HandBrake-Fassung nicht kennt. Das ist ein Fehler '
|
||||
'in Rippy, keine Einstellung — bitte melden.' % falscher_schalter
|
||||
+ hb_schluss(letzte_zeilen)
|
||||
),
|
||||
"return_code": process.returncode,
|
||||
}
|
||||
|
||||
# Code 0, aber die Datei fehlt: HandBrake hat sie woanders hingeschrieben.
|
||||
#
|
||||
# Commander 29.08.2026: „Kompression fehlgeschlagen bei title_t00.mkv:
|
||||
@@ -721,11 +843,17 @@ def _handbrake_schleife(process, output_path: str, abbruch_cb=None, progress_cb=
|
||||
"(der Container kommt aus dem Preset)."
|
||||
% (os.path.basename(daneben),
|
||||
os.path.basename(output_path))}
|
||||
# Frueher stand hier geraten „Meist ist der Zielordner nicht
|
||||
# beschreibbar". Am 30.08.2026 war das falsch (der Ordner war da
|
||||
# und leer, der Grund ein falscher Schalter) — und die Vermutung
|
||||
# schickte die Suche in die falsche Richtung. Jetzt wird der
|
||||
# Ordner GENANNT und HandBrake selbst zitiert.
|
||||
return {
|
||||
"status": "error",
|
||||
"error": ("HandBrake meldet Erfolg, aber es ist keine Datei "
|
||||
"entstanden. Meist ist der Zielordner nicht "
|
||||
"beschreibbar: %s" % os.path.dirname(output_path)),
|
||||
"entstanden (Zielordner: %s)."
|
||||
% os.path.dirname(output_path)
|
||||
+ hb_schluss(letzte_zeilen)),
|
||||
"return_code": 0,
|
||||
}
|
||||
if falsches_preset:
|
||||
@@ -742,7 +870,8 @@ def _handbrake_schleife(process, output_path: str, abbruch_cb=None, progress_cb=
|
||||
}
|
||||
return {
|
||||
"status": "error",
|
||||
"error": f"HandBrake endete mit Code {process.returncode}",
|
||||
"error": (f"HandBrake endete mit Code {process.returncode}"
|
||||
+ hb_schluss(letzte_zeilen)),
|
||||
"return_code": process.returncode,
|
||||
}
|
||||
|
||||
@@ -862,6 +991,13 @@ def run_makemkv(device_path: str, output_dir: str, progress_cb=None, titel: str
|
||||
)
|
||||
|
||||
letzte_meldung = ""
|
||||
# Lesefehler sind KEIN Abbruchgrund — MakeMKV ueberspringt den
|
||||
# kaputten Titel und macht mit dem naechsten weiter. Genau deshalb
|
||||
# muessen sie gesagt werden: Am 30.08.2026 endete ein Rip als
|
||||
# „erfolgreich", obwohl von zwei Titeln nur einer ankam. Im
|
||||
# Jobprotokoll stand „Rip fertig" — dass ein Titel fehlt, war nur
|
||||
# den MakeMKV-Zeilen zu entnehmen, die niemand liest.
|
||||
lesefehler = False
|
||||
# Kritische Meldungen einsammeln: die LETZTE Zeile ist fast immer nur
|
||||
# "Failed to open disc" — die URSACHE ("volume key is unknown", Key
|
||||
# abgelaufen) steht Zeilen davor und ging im Fehlertext verloren
|
||||
@@ -891,6 +1027,8 @@ def run_makemkv(device_path: str, output_dir: str, progress_cb=None, titel: str
|
||||
if meldung is None:
|
||||
continue
|
||||
code, letzte_meldung = meldung
|
||||
if LESEFEHLER_MARKE in letzte_meldung:
|
||||
lesefehler = True
|
||||
if code in KRITISCHE_CODES:
|
||||
kritische_meldungen.append(
|
||||
"%s (%s)" % (KRITISCHE_CODES[code], letzte_meldung.strip()))
|
||||
@@ -922,9 +1060,11 @@ def run_makemkv(device_path: str, output_dir: str, progress_cb=None, titel: str
|
||||
"output_dir": output_dir,
|
||||
"files": mkv_dateien,
|
||||
"return_code": process.returncode,
|
||||
"lesefehler": lesefehler,
|
||||
}
|
||||
return {
|
||||
"status": "error",
|
||||
"lesefehler": lesefehler,
|
||||
"error": (
|
||||
f"makemkvcon endete mit Code {process.returncode}"
|
||||
+ (f" — Ursache: {'; '.join(kritische_meldungen)}" if kritische_meldungen else "")
|
||||
|
||||
Reference in New Issue
Block a user