fix(windows): Rippen ging nicht — drei Container-Annahmen im Rip-Pfad
Commander: „Das Rippen der disk funktioniert nicht. makemkvcon endete mit
Code 11 — letzte Meldung: Das Öffnen der Disk schlug fehl — keine MKV-Datei
entstanden"
In diesem einen Satz steckten drei Fehler. Alle drei an seinem Laufwerk
gemessen, nicht hergeleitet.
## 1. Die Quellenangabe — das war der Abbruch
dev:\.\G: MSG:2024 „Unknown device" -> MSG:5010 Öffnen schlug fehl
dev:G: 68 TINFO-Zeilen, „Die Aufgabe wurde erfolgreich abgeschlossen"
`f"dev:{device_path}"` stand an VIER Stellen. Unter Linux richtig (/dev/sr0),
unter Windows nicht: `rippy.drives.windows` meldet den Geraete-Namensraum
`\.\G:`, den `CreateFileW` fuer die IOCTLs braucht — MakeMKV kennt ihn
nicht.
## 2. „Ö" statt „Ö"
`Ö` als UTF-8 (C3 96), gelesen als cp1252. `subprocess` stand auf `text=True`
ohne `encoding=`, nahm also die Gebietsschema-Kodierung; Rippy stellt seine
Konsole beim Start aber auf UTF-8 (sonst stirbt der Start an einer
Umlaut-Logzeile). Direkt gemessen kommen die Bytes je nach Umgebung als UTF-8
ODER cp1252 — deshalb wird jetzt bestimmt statt behauptet.
## 3. Die Ursache fehlte im Fehlertext
Gesucht wurde nach ENGLISCHEN Textbausteinen („evaluation period has
expired"). Auf einem deutschen Windows trifft keiner. Jetzt zaehlen die
Meldungs-NUMMERN, die sprachunabhaengig sind.
⚠️ Beim ersten Anlauf hatte ich 5021 aus dem Kopf mit „Volume-Key unbekannt"
beschriftet — falsch. Meine eigene Ausgabe zeigte nur meine Beschriftung
statt MakeMKVs Text, sodass es fast durchgegangen waere. Der echte Wortlaut
steht jetzt im Code.
## Nebenbefund: der Beta-Key lag zweimal am falschen Ort
`makemkv_daten.DATEN_DIR` war fest `/root/.MakeMKV`. Nach der Reparatur des
Ordners blieb MSG:5051 trotzdem stehen — weil MakeMKV unter Windows seine
Einstellungen in der REGISTRY haelt (HKCU\Software\MakeMKV), nicht in einer
settings.conf. Nachgesehen statt vermutet.
Und der wichtigste Teil davon: **Ein abgelehnter Schluessel ist schlimmer als
gar keiner.** Ohne Key las MakeMKV die Disc noch (68 TINFO), mit dem
abgelehnten verweigerte es alles (0 Titel). Deshalb wird jetzt geprueft und
bei Ablehnung zurueckgenommen.
## Beweis
Echter Rip ueber `ripping.run_makemkv`, kuerzester Titel:
Status success, Code 0, „Evangelion 2.22_t03.mkv" 217,9 MB
5036 „Das Kopieren wurde abgeschlossen. 1 Titel wurden gesichert."
828 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
73ed104bbc
commit
954f293871
+20
-4
@@ -2445,14 +2445,30 @@ async def makemkv_key_holen():
|
||||
detail="Im Forum-Beitrag stand kein Key im erwarteten Format. "
|
||||
"Vermutlich hat sich die Seite geändert.")
|
||||
geaendert = await asyncio.to_thread(key_modul._apply_key, key)
|
||||
|
||||
# Nimmt MakeMKV ihn an? Nur MakeMKV selbst weiss das.
|
||||
#
|
||||
# Am 29.08.2026 auf dem Rechner des Commanders: der frische Schluessel
|
||||
# wurde mit MSG 5020 („ungueltig") und 5021 („Programmversion zu alt")
|
||||
# abgelehnt — sein MakeMKV 1.18.4 ist aelter als der Schluessel verlangt.
|
||||
# Ohne diese Rueckfrage haette hier „erfolgreich geholt" gestanden, und
|
||||
# der naechste Rip waere trotzdem gescheitert.
|
||||
from rippy.rip import makemkv_aufruf
|
||||
from rippy.tools import katalog as werkzeug_katalog
|
||||
|
||||
urteil = await asyncio.to_thread(
|
||||
makemkv_aufruf.key_setzen_und_pruefen, key,
|
||||
werkzeug_katalog.finden("makemkv"))
|
||||
await asyncio.to_thread(
|
||||
db.add_log, "success", "makemkv-key",
|
||||
"Beta-Key aus dem Forum geholt (…%s)%s"
|
||||
% (key[-6:], "" if geaendert else " — war schon der aktuelle"))
|
||||
db.add_log, "success" if urteil["ok"] else "warning", "makemkv-key",
|
||||
"Beta-Key aus dem Forum geholt (…%s)%s%s"
|
||||
% (key[-6:], "" if geaendert else " — war schon der aktuelle",
|
||||
"" if urteil["ok"] else " — ABGELEHNT: " + urteil["grund"]))
|
||||
# Der Key selbst wird NICHT zurueckgegeben: Er steht in den Einstellungen,
|
||||
# und ein zweiter Weg zu demselben Wert ist ein zweiter Weg, ihn zu
|
||||
# verlieren.
|
||||
return {"geholt": True, "geaendert": geaendert, "endet_auf": key[-6:]}
|
||||
return {"geholt": True, "geaendert": geaendert, "endet_auf": key[-6:],
|
||||
"angenommen": urteil["ok"], "grund": urteil["grund"]}
|
||||
|
||||
|
||||
@app.post("/system/keydb")
|
||||
|
||||
@@ -10,6 +10,8 @@ import subprocess
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
from rippy import drives as _laufwerks_schicht
|
||||
from rippy.rip.makemkv_aufruf import quelle as makemkv_quelle
|
||||
from rippy.rip.makemkv_aufruf import text_von
|
||||
from rippy.tools import katalog as werkzeug_katalog
|
||||
from clients.tmdb import TMDBClient
|
||||
from clients.jikan import JikanClient
|
||||
@@ -365,12 +367,11 @@ class PreScan:
|
||||
makemkv = werkzeug_katalog.finden("makemkv")
|
||||
if makemkv:
|
||||
result = subprocess.run(
|
||||
[makemkv, "-r", "--noscan", "--minlength=300", "info", f"dev:{device_path}"],
|
||||
[makemkv, "-r", "--noscan", "--minlength=300", "info", makemkv_quelle(device_path)],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=120
|
||||
timeout=120 # binaer lesen — Begruendung in text_von
|
||||
)
|
||||
for line in result.stdout.split('\n'):
|
||||
for line in text_von(result.stdout).split('\n'):
|
||||
if line.startswith('TINFO:'):
|
||||
parts = line.split(',')
|
||||
if len(parts) >= 5:
|
||||
|
||||
@@ -270,10 +270,19 @@ export default function SettingsPage() {
|
||||
const r = await api.post('/system/makemkv-key/holen')
|
||||
const antwort = await api.get('/settings')
|
||||
setSettings({ ...defaultSettings, ...antwort.data })
|
||||
setKeyMeldung(r.data?.geaendert
|
||||
? `Neuer Key geholt (endet auf …${r.data.endet_auf}).`
|
||||
: `Schon aktuell (endet auf …${r.data?.endet_auf || '?'}).`)
|
||||
toast('success', 'MakeMKV-Beta-Key aus dem Forum geholt.')
|
||||
if (r.data?.angenommen === false) {
|
||||
// Geholt ist nicht angenommen. Am 29.08.2026 lehnte MakeMKV 1.18.4
|
||||
// den aktuellen Key ab („Programmversion zu alt") — ohne diesen
|
||||
// Hinweis stünde hier ein Erfolg, und der nächste Rip scheiterte
|
||||
// trotzdem.
|
||||
setKeyMeldung('Fehlgeschlagen: ' + (r.data.grund || 'MakeMKV nimmt den Key nicht an.'))
|
||||
toast('error', r.data.grund || 'MakeMKV nimmt den Key nicht an.')
|
||||
} else {
|
||||
setKeyMeldung(r.data?.geaendert
|
||||
? `Neuer Key geholt und angenommen (endet auf …${r.data.endet_auf}).`
|
||||
: `Schon aktuell (endet auf …${r.data?.endet_auf || '?'}).`)
|
||||
toast('success', 'MakeMKV-Beta-Key aus dem Forum geholt.')
|
||||
}
|
||||
} catch (e: any) {
|
||||
const grund = e?.response?.data?.detail
|
||||
|| 'Das Forum ist nicht erreichbar.'
|
||||
|
||||
+18
-11
@@ -47,7 +47,7 @@ import time
|
||||
import requests
|
||||
|
||||
from rippy import store as db
|
||||
from rippy.rip import makemkv_daten
|
||||
from rippy.rip import makemkv_aufruf, makemkv_daten
|
||||
import medien
|
||||
from rippy.core import notify
|
||||
|
||||
@@ -243,17 +243,24 @@ def _makemkv_key_anwenden(einstellungen: dict) -> None:
|
||||
key = (einstellungen.get("makemkvAppKey") or "").strip()
|
||||
if not key:
|
||||
return
|
||||
pfad = os.path.join(makemkv_daten.DATEN_DIR, "settings.conf")
|
||||
try:
|
||||
os.makedirs(makemkv_daten.DATEN_DIR, exist_ok=True)
|
||||
try:
|
||||
with open(pfad, encoding="utf-8", errors="replace") as f:
|
||||
alt = f.read()
|
||||
except OSError:
|
||||
alt = ""
|
||||
with open(pfad, "w", encoding="utf-8", newline="\n") as f:
|
||||
f.write(makemkv_daten.settings_conf_zusammenfuehren(alt, key))
|
||||
except OSError as e:
|
||||
# WOHIN, entscheidet der Rechner — unter Windows die Registry, sonst
|
||||
# die settings.conf. Begruendung und Messung in
|
||||
# `rippy.rip.makemkv_aufruf.key_ablegen`.
|
||||
#
|
||||
# UND nachsehen, ob MakeMKV ihn annimmt: Ein abgelehnter Schluessel
|
||||
# ist schlimmer als gar keiner — ohne las MakeMKV die Disc noch, mit
|
||||
# einem abgelehnten verweigerte es alles (gemessen 29.08.2026). Die
|
||||
# paar Sekunden Rueckfrage sind nichts gegen einen Rip, der Stunden
|
||||
# laeuft und sonst am ersten Titel scheitert.
|
||||
from ripping import werkzeug
|
||||
|
||||
urteil = makemkv_aufruf.key_setzen_und_pruefen(key, werkzeug("makemkv"))
|
||||
if not urteil["ok"]:
|
||||
db.add_log("warning", "worker",
|
||||
"MakeMKV nimmt den hinterlegten Beta-Key nicht an: %s"
|
||||
% urteil["grund"])
|
||||
except (OSError, ImportError) as e:
|
||||
db.add_log("warning", "worker", f"MakeMKV-Key konnte nicht gesetzt werden: {e}")
|
||||
|
||||
|
||||
|
||||
+32
-11
@@ -37,6 +37,8 @@ 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.makemkv_aufruf import KRITISCHE_CODES, text_von
|
||||
from rippy.rip.makemkv_aufruf import quelle as makemkv_quelle
|
||||
from rippy.tools import katalog as werkzeuge
|
||||
|
||||
_NACKTER_NAME = {"makemkv": "makemkvcon", "handbrake": "HandBrakeCLI"}
|
||||
@@ -102,7 +104,7 @@ def build_makemkv_cmd(device_path: str, output_dir: str, titel: str = "all") ->
|
||||
"--noscan",
|
||||
"--progress=-same",
|
||||
"mkv",
|
||||
f"dev:{device_path}",
|
||||
makemkv_quelle(device_path),
|
||||
str(titel),
|
||||
output_dir,
|
||||
]
|
||||
@@ -257,11 +259,11 @@ def lies_titel_info(device_path: str, timeout: int = 300) -> tuple:
|
||||
wäre eine Minute Wartezeit für nichts.
|
||||
"""
|
||||
ergebnis = subprocess.run(
|
||||
[werkzeug("makemkv"), "-r", "--noscan", "info", f"dev:{device_path}"],
|
||||
capture_output=True, text=True, timeout=timeout,
|
||||
[werkzeug("makemkv"), "-r", "--noscan", "info", makemkv_quelle(device_path)],
|
||||
capture_output=True, timeout=timeout, # binaer: siehe text_von
|
||||
creationflags=OHNE_FENSTER,
|
||||
)
|
||||
ausgabe = ergebnis.stdout or ""
|
||||
ausgabe = text_von(ergebnis.stdout)
|
||||
return parse_titel_info(ausgabe), parse_stream_info(ausgabe)
|
||||
|
||||
|
||||
@@ -330,13 +332,13 @@ def lies_titel_dauern(device_path: str, timeout: int = 300) -> dict:
|
||||
"""Fragt die Titel-Laufzeiten der Disc ab (makemkvcon info, Robot-Mode)."""
|
||||
try:
|
||||
ergebnis = subprocess.run(
|
||||
[werkzeug("makemkv"), "-r", "--noscan", "info", f"dev:{device_path}"],
|
||||
capture_output=True, text=True, timeout=timeout,
|
||||
[werkzeug("makemkv"), "-r", "--noscan", "info", makemkv_quelle(device_path)],
|
||||
capture_output=True, timeout=timeout, # binaer: siehe text_von
|
||||
creationflags=OHNE_FENSTER,
|
||||
)
|
||||
except (OSError, subprocess.TimeoutExpired):
|
||||
return {}
|
||||
return parse_tinfo_dauern(ergebnis.stdout or "")
|
||||
return parse_tinfo_dauern(text_von(ergebnis.stdout))
|
||||
|
||||
|
||||
def parse_scan_dauer(ausgabe: str) -> int:
|
||||
@@ -730,8 +732,17 @@ def run_makemkv(device_path: str, output_dir: str, progress_cb=None, titel: str
|
||||
build_makemkv_cmd(device_path, output_dir, titel),
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
text=True,
|
||||
bufsize=1,
|
||||
# BINAER lesen und selbst dekodieren (siehe `text_von`):
|
||||
# `text=True` nahm die Gebietsschema-Kodierung, makemkvcon
|
||||
# schrieb aber UTF-8 — daraus wurde beim Commander
|
||||
# „Das Öffnen der Disk schlug fehl".
|
||||
#
|
||||
# bufsize=0 statt 1: Zeilenpufferung gibt es im Binaermodus
|
||||
# nicht (Python warnt und puffert doch). Ungepuffert kommt
|
||||
# jede Fortschrittszeile sofort an — bei einem Rip, der
|
||||
# Stunden laeuft, ist das der Unterschied zwischen einem
|
||||
# Balken und einem eingefrorenen Fenster.
|
||||
bufsize=0,
|
||||
creationflags=OHNE_FENSTER,
|
||||
)
|
||||
|
||||
@@ -741,6 +752,12 @@ def run_makemkv(device_path: str, output_dir: str, progress_cb=None, titel: str
|
||||
# abgelaufen) steht Zeilen davor und ging im Fehlertext verloren
|
||||
# (Befund 24.07., Summer-Wars-UHD).
|
||||
kritische_meldungen = []
|
||||
# Die Textmuster bleiben als Rueckfall fuer englische Installationen —
|
||||
# die MELDUNGSNUMMERN sind aber die verlaessliche Quelle. Beim
|
||||
# Commander meldet MakeMKV deutsch, also traf hier am 29.08.2026 kein
|
||||
# einziges Muster: Im Fehler stand nur „Das Öffnen der Disk schlug
|
||||
# fehl", waehrend die Ursache eine Zeile darueber („Der Testzeitraum
|
||||
# ist abgelaufen", MSG 5051) unter den Tisch fiel.
|
||||
KRITISCH = (
|
||||
"volume key is unknown",
|
||||
"evaluation period has expired",
|
||||
@@ -748,7 +765,8 @@ def run_makemkv(device_path: str, output_dir: str, progress_cb=None, titel: str
|
||||
"Too old version",
|
||||
)
|
||||
try:
|
||||
for line in process.stdout:
|
||||
for rohzeile in process.stdout:
|
||||
line = text_von(rohzeile)
|
||||
progress = get_progress_from_prgv(line)
|
||||
if progress >= 0:
|
||||
if progress_cb:
|
||||
@@ -758,7 +776,10 @@ def run_makemkv(device_path: str, output_dir: str, progress_cb=None, titel: str
|
||||
if meldung is None:
|
||||
continue
|
||||
code, letzte_meldung = meldung
|
||||
if any(muster in letzte_meldung for muster in KRITISCH):
|
||||
if code in KRITISCHE_CODES:
|
||||
kritische_meldungen.append(
|
||||
"%s (%s)" % (KRITISCHE_CODES[code], letzte_meldung.strip()))
|
||||
elif any(muster in letzte_meldung for muster in KRITISCH):
|
||||
kritische_meldungen.append(letzte_meldung)
|
||||
if log_cb:
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user