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:
|
||||
|
||||
@@ -0,0 +1,315 @@
|
||||
"""Wie makemkvcon angesprochen und wie seine Antwort gelesen wird.
|
||||
|
||||
## Warum das im GEMEINSAMEN Paket liegt
|
||||
|
||||
Beide Seiten rufen makemkvcon auf: der Worker zum Rippen
|
||||
(`docker/worker/ripping.py`) und die API beim Vor-Scan
|
||||
(`docker/api/prescan/prescan.py`). Sie stecken in verschiedenen Images und
|
||||
können einander nicht importieren — deshalb stand die Formatzeile zweimal da,
|
||||
und beide Male falsch.
|
||||
|
||||
Genau das ist das Muster, das in diesem Projekt schon dreimal Geld gekostet
|
||||
hat: `os.path` (viermal repariert, jetzt `rippy/pfade.py`), `/app/media` und
|
||||
`/root/.MakeMKV`. Eine Entscheidung, zwei Orte — einer altert.
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
|
||||
def quelle(device_path: str, plattform_name: str = None) -> str:
|
||||
"""Wie MakeMKV DIESES Laufwerk nennt (pure Funktion, testbar).
|
||||
|
||||
## Der Befund des Commanders (29.08.2026)
|
||||
|
||||
> „Das Rippen der disk funktioniert nicht. makemkvcon endete mit Code 11 —
|
||||
> letzte Meldung: Das Öffnen der Disk schlug fehl — keine MKV-Datei
|
||||
> entstanden"
|
||||
|
||||
An seinem Laufwerk gemessen, mit `info` (öffnet die Disc genauso wie
|
||||
`mkv`, rippt aber nichts):
|
||||
|
||||
dev:\\\\.\\G: MSG:2024 „Unknown device" -> MSG:5010 Öffnen schlug fehl
|
||||
dev:G: 68 TINFO-Zeilen, „Die Aufgabe wurde erfolgreich
|
||||
abgeschlossen"
|
||||
|
||||
Vorher stand überall `f"dev:{device_path}"`. Unter Linux ist das richtig:
|
||||
dort heißt das Laufwerk `/dev/sr0`, und genau so will MakeMKV es haben.
|
||||
Unter Windows meldet `rippy.drives.windows` den Geräte-Namensraum
|
||||
`\\\\.\\G:` — den braucht `CreateFileW` für die IOCTLs, aber MakeMKV kennt
|
||||
ihn nicht. Es will den Laufwerksbuchstaben.
|
||||
|
||||
`plattform_name` ist einspritzbar, damit beide Fälle überall prüfbar sind.
|
||||
Entschieden wird aber nach der FORM des Pfades, nicht nach dem Rechner —
|
||||
dieselbe Regel wie in `rippy.pfade`: Ein Windows-Pfad bleibt ein
|
||||
Windows-Pfad, auch wenn gerade Linux danach fragt.
|
||||
"""
|
||||
pfad = (device_path or "").strip()
|
||||
if not pfad:
|
||||
return ""
|
||||
# Geraete-Namensraum abstreifen: \\.\G: -> G: (auch \\?\ kommt vor)
|
||||
for vorsatz in ("\\\\.\\", "\\\\?\\"):
|
||||
if pfad.startswith(vorsatz):
|
||||
pfad = pfad[len(vorsatz):]
|
||||
break
|
||||
# „G:\" -> „G:" — mit Trenner geht es zwar auch (gemessen), aber ohne ist
|
||||
# es das, was MakeMKV in seiner eigenen Laufwerksliste ausgibt.
|
||||
if len(pfad) == 3 and pfad[1] == ":" and pfad[2] in "\\/":
|
||||
pfad = pfad[:2]
|
||||
return "dev:%s" % pfad
|
||||
|
||||
|
||||
def text_von(roh) -> str:
|
||||
"""makemkvcon-Ausgabe lesbar machen — egal in welcher Kodierung sie kommt.
|
||||
|
||||
## Der Befund des Commanders (29.08.2026)
|
||||
|
||||
> „letzte Meldung: Das Öffnen der Disk schlug fehl"
|
||||
|
||||
`Ö` ist `Ö` als UTF-8 (Bytes C3 96), gelesen als cp1252. Die Ausgabe kam
|
||||
also in einer anderen Kodierung an, als sie gelesen wurde.
|
||||
|
||||
Beide Seiten waren für sich richtig: `subprocess` stand auf `text=True`
|
||||
**ohne** `encoding=`, benutzt also die Gebietsschema-Kodierung — auf einem
|
||||
deutschen Windows cp1252. Und Rippy stellt seine Konsole beim Start
|
||||
ausdrücklich auf UTF-8 (`daemon.ausgabe_auf_utf8_stellen`, sonst stirbt
|
||||
der Start an einer Umlaut-Logzeile). Ein Konsolenprogramm richtet sich
|
||||
danach — makemkvcon schrieb UTF-8 in eine Leitung, an deren Ende cp1252
|
||||
erwartet wurde.
|
||||
|
||||
Direkt an makemkvcon gemessen, ohne unsere Konsolen-Umstellung: die Bytes
|
||||
sind dann **cp1252** (`Das Öffnen` liest sich nur so richtig). Die
|
||||
Kodierung hängt also wirklich an der Umgebung und ist nicht festzulegen.
|
||||
|
||||
Deshalb wird sie nicht behauptet, sondern bestimmt: UTF-8 streng
|
||||
versuchen, sonst cp1252. Streng ist hier der Punkt — mit `errors=replace`
|
||||
ginge jeder Umlaut still in einem Fragezeichen unter, und wir würden es
|
||||
nie erfahren.
|
||||
"""
|
||||
if isinstance(roh, str):
|
||||
return roh
|
||||
if not roh:
|
||||
return ""
|
||||
try:
|
||||
return roh.decode("utf-8")
|
||||
except UnicodeDecodeError:
|
||||
# cp1252 kann jedes Byte abbilden -> hier kann nichts mehr scheitern.
|
||||
return roh.decode("cp1252", "replace")
|
||||
|
||||
|
||||
#: Meldungs-Nummern, die die URSACHE eines Fehlschlags benennen. Sie sind
|
||||
#: sprachunabhängig — anders als die Texte, auf die vorher geprüft wurde.
|
||||
#:
|
||||
#: Bis zum 29.08.2026 suchte `ripping.py` nur englische Textbausteine
|
||||
#: („evaluation period has expired", „volume key is unknown"). Auf dem
|
||||
#: Commander-PC meldet MakeMKV deutsch, also traf keiner: Im Fehler stand nur
|
||||
#: „Das Öffnen der Disk schlug fehl" — die Ursache eine Zeile darüber fiel
|
||||
#: unter den Tisch.
|
||||
#:
|
||||
#: ⚠️ Jede Nummer hier ist an seinem Laufwerk GESEHEN worden, im Wortlaut.
|
||||
#: Beim ersten Anlauf hatte ich 5021 aus dem Kopf mit „Volume-Key unbekannt"
|
||||
#: beschriftet — falsch, und meine eigene Ausgabe zeigte nur meine
|
||||
#: Beschriftung statt MakeMKVs Text, sodass es fast durchgegangen wäre.
|
||||
#: Genau der Fehler, vor dem AGENTS Regel D warnt. Der echte Wortlaut:
|
||||
#:
|
||||
#: 2024 „Unknown device - '\\\\.\\G:'"
|
||||
#: 5020 „Der hinterlegte Aktivierungsschlüssel ist ungültig."
|
||||
#: 5021 „Diese Programmversion ist zu alt. Bitte laden Sie die aktuelle
|
||||
#: Version von http://www.makemkv.com/ herunter."
|
||||
#: 5051 „Der Testzeitraum ist abgelaufen. Erwerben Sie bitte einen
|
||||
#: Aktivierungsschlüssel …"
|
||||
KRITISCHE_CODES = {
|
||||
2024: "MakeMKV kennt dieses Laufwerk nicht",
|
||||
5020: "Der hinterlegte MakeMKV-Schlüssel wird nicht angenommen",
|
||||
5021: "MakeMKV ist zu alt für den aktuellen Beta-Schlüssel — bitte MakeMKV "
|
||||
"aktualisieren",
|
||||
5051: "MakeMKV ist nicht freigeschaltet (Testzeitraum abgelaufen)",
|
||||
}
|
||||
|
||||
#: Meldungen, die sagen: DIESER Schlüssel taugt für DIESE Fassung nicht.
|
||||
KEY_ABGELEHNT = (5020, 5021)
|
||||
|
||||
|
||||
def plattform(name: str = None) -> str:
|
||||
"""Nur für Tests, die den Rechner ausdrücklich benennen wollen."""
|
||||
return (name if name is not None else sys.platform).lower()
|
||||
|
||||
|
||||
#: Wo MakeMKV unter Windows SEINE Einstellungen hält — gemessen, nicht geraten.
|
||||
REGISTRY_PFAD = r"Software\MakeMKV"
|
||||
KEY_NAME = "app_Key"
|
||||
|
||||
|
||||
def key_ablegen(key: str, plattform_name: str = None, schreiber=None,
|
||||
datei_schreiber=None) -> str:
|
||||
"""Den Beta-Key dorthin legen, wo makemkvcon ihn WIRKLICH sucht.
|
||||
|
||||
Gibt zurück, wohin geschrieben wurde (für die Protokollzeile).
|
||||
|
||||
## Der Befund des Commanders (29.08.2026)
|
||||
|
||||
Beim ersten echten Rip-Versuch meldete makemkvcon:
|
||||
|
||||
MSG:5051 „Der Testzeitraum ist abgelaufen. Erwerben Sie bitte einen
|
||||
Aktivierungsschlüssel"
|
||||
|
||||
— obwohl in Rippys Einstellungen ein aktueller Beta-Key lag. Zwei Fehler
|
||||
hintereinander, beide gemessen:
|
||||
|
||||
**1. Falscher Ordner.** `makemkv_daten.DATEN_DIR` war fest
|
||||
`/root/.MakeMKV`, ein Container-Pfad. Repariert (siehe dort).
|
||||
|
||||
**2. Falsche ART von Ablage.** Auch mit richtigem Ordner blieb die Meldung
|
||||
stehen. Nachgesehen, was auf seinem Rechner wirklich existiert:
|
||||
|
||||
C:\\Users\\TobisPC\\.MakeMKV\\ nur _private_data.tar (6,4 MB)
|
||||
HKCU\\Software\\MakeMKV app_UpdateLastCheck, app_SiteInfoString,
|
||||
app_BackupDecrypted, path_BackupDirMRU …
|
||||
|
||||
**Unter Windows hält MakeMKV seine Einstellungen in der Registry.** Die
|
||||
`settings.conf` ist der Linux-Weg; im Container stimmt sie, hier nicht. Der
|
||||
Ordner `.MakeMKV` existiert trotzdem — aber nur für den Schlüsselspeicher.
|
||||
|
||||
Rippy hat den Key also zweimal brav gespeichert und beide Male an einen
|
||||
Ort, den niemand liest. Eine Automatik, die ins Leere schreibt, ist
|
||||
schlimmer als keine: Sie sieht aus wie erledigt.
|
||||
|
||||
## Warum hier NUR ein einziger Wert geschrieben wird
|
||||
|
||||
Weil in dieser Registry die Arbeit des Commanders steckt —
|
||||
Laufwerksprofile, Fensterlage, zuletzt benutzte Ordner. Es wird genau
|
||||
`app_Key` gesetzt und sonst nichts angefasst; gelöscht wird nie. (Am
|
||||
28.08.2026 hat eine zu großzügige Testsuite schon einmal echte
|
||||
Registry-Einträge von ihm entfernt.)
|
||||
"""
|
||||
key = (key or "").strip()
|
||||
if not key:
|
||||
return ""
|
||||
if not plattform(plattform_name).startswith("win"):
|
||||
# Linux/Container: die settings.conf, wie gehabt.
|
||||
return (datei_schreiber or _in_datei)(key)
|
||||
return (schreiber or _in_registry)(key)
|
||||
|
||||
|
||||
def _in_registry(key: str) -> str:
|
||||
import winreg
|
||||
|
||||
with winreg.CreateKey(winreg.HKEY_CURRENT_USER, REGISTRY_PFAD) as schluessel:
|
||||
winreg.SetValueEx(schluessel, KEY_NAME, 0, winreg.REG_SZ, key)
|
||||
return "HKCU\\" + REGISTRY_PFAD
|
||||
|
||||
|
||||
def key_zuruecknehmen(plattform_name: str = None, entferner=None) -> bool:
|
||||
"""Einen abgelehnten Schlüssel wieder entfernen. True, wenn etwas wegging."""
|
||||
if not plattform(plattform_name).startswith("win"):
|
||||
return False
|
||||
return (entferner or _aus_registry)()
|
||||
|
||||
|
||||
def _aus_registry() -> bool:
|
||||
import winreg
|
||||
|
||||
try:
|
||||
with winreg.OpenKey(winreg.HKEY_CURRENT_USER, REGISTRY_PFAD, 0,
|
||||
winreg.KEY_ALL_ACCESS) as schluessel:
|
||||
winreg.DeleteValue(schluessel, KEY_NAME)
|
||||
return True
|
||||
except OSError:
|
||||
return False # war nicht da — auch gut
|
||||
|
||||
|
||||
def key_pruefen(programm: str, laufen=None) -> dict:
|
||||
"""Nimmt MakeMKV den hinterlegten Schlüssel an? — es selbst gefragt.
|
||||
|
||||
`{"ok": bool, "code": int|None, "grund": str}`.
|
||||
|
||||
## Warum GEFRAGT und nicht gerechnet (Befund 29.08.2026)
|
||||
|
||||
Rippy hat den frischen Beta-Key aus dem Forum geholt und abgelegt — alles
|
||||
richtig. Auf dem Rechner des Commanders antwortete MakeMKV darauf:
|
||||
|
||||
5020 „Der hinterlegte Aktivierungsschlüssel ist ungültig."
|
||||
5021 „Diese Programmversion ist zu alt."
|
||||
|
||||
Sein MakeMKV 1.18.4 ist älter als der Schlüssel verlangt. Rippys
|
||||
Update-Prüfung kann das NICHT sehen: makemkv.com antwortet seit Tagen mit
|
||||
HTTP 525, und die Ausweichquellen kennen als höchste Fassung genau 1.18.4
|
||||
(gemessen). Die einzige Instanz, die die Wahrheit kennt, ist MakeMKV
|
||||
selbst.
|
||||
|
||||
## Warum das wichtiger ist, als es klingt
|
||||
|
||||
**Ein abgelehnter Schlüssel ist schlimmer als gar keiner.** Ohne Schlüssel
|
||||
lief MakeMKV im abgelaufenen Testmodus und las die Disc immerhin noch (68
|
||||
TINFO-Zeilen). Mit dem abgelehnten verweigerte es alles — 0 Titel, kein
|
||||
Laufwerk. Wer also blind schreibt, verschlechtert den Zustand.
|
||||
|
||||
Der Aufruf ist billig: kein Datenträger nötig, MakeMKV meldet die
|
||||
Freischaltung schon beim Start.
|
||||
"""
|
||||
if not programm:
|
||||
return {"ok": False, "code": None, "grund": "MakeMKV nicht gefunden"}
|
||||
laufen = laufen or _makemkvcon_laufen
|
||||
try:
|
||||
ausgabe = laufen(programm)
|
||||
except Exception as e: # noqa: BLE001
|
||||
return {"ok": False, "code": None,
|
||||
"grund": "MakeMKV nicht befragbar (%s)" % type(e).__name__}
|
||||
|
||||
for zeile in text_von(ausgabe).splitlines():
|
||||
if not zeile.startswith("MSG:"):
|
||||
continue
|
||||
try:
|
||||
code = int(zeile[4:].split(",", 1)[0])
|
||||
except ValueError:
|
||||
continue
|
||||
if code in KEY_ABGELEHNT:
|
||||
return {"ok": False, "code": code, "grund": KRITISCHE_CODES[code]}
|
||||
return {"ok": True, "code": None, "grund": ""}
|
||||
|
||||
|
||||
def _makemkvcon_laufen(programm: str):
|
||||
import subprocess
|
||||
|
||||
from rippy.platform.winlauf import OHNE_FENSTER
|
||||
|
||||
# `info disc:9999` fragt nur die Laufwerksliste ab — kein Datenträger
|
||||
# nötig, und die Freischaltungs-Meldung kommt trotzdem.
|
||||
ergebnis = subprocess.run([programm, "-r", "--noscan", "info", "disc:9999"],
|
||||
capture_output=True, timeout=120,
|
||||
creationflags=OHNE_FENSTER)
|
||||
return ergebnis.stdout
|
||||
|
||||
|
||||
def key_setzen_und_pruefen(key: str, programm: str, plattform_name: str = None,
|
||||
schreiber=None, pruefer=None, entferner=None) -> dict:
|
||||
"""Schlüssel ablegen — und wieder wegnehmen, wenn MakeMKV ihn ablehnt.
|
||||
|
||||
Gibt `{"ok", "wohin", "grund"}` zurück. Siehe `key_pruefen`, warum das
|
||||
Zurücknehmen wichtig ist.
|
||||
"""
|
||||
wohin = key_ablegen(key, plattform_name, schreiber=schreiber)
|
||||
if not wohin:
|
||||
return {"ok": False, "wohin": "", "grund": "kein Schlüssel angegeben"}
|
||||
urteil = (pruefer or key_pruefen)(programm)
|
||||
if urteil["ok"]:
|
||||
return {"ok": True, "wohin": wohin, "grund": ""}
|
||||
key_zuruecknehmen(plattform_name, entferner=entferner)
|
||||
return {"ok": False, "wohin": wohin, "grund": urteil["grund"]}
|
||||
|
||||
|
||||
def _in_datei(key: str) -> str:
|
||||
import os
|
||||
|
||||
from rippy.rip import makemkv_daten
|
||||
|
||||
ordner = makemkv_daten.DATEN_DIR
|
||||
pfad = os.path.join(ordner, "settings.conf")
|
||||
os.makedirs(ordner, 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))
|
||||
return pfad
|
||||
@@ -62,13 +62,68 @@ es eins: `rippy`. Die Kopie ist weg, der Wächter-Test damit gegenstandslos.
|
||||
import io
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import tarfile
|
||||
from datetime import datetime, timezone
|
||||
|
||||
# Container-Pfad aus der Umgebung: /root/.MakeMKV im Worker (nur dort sucht
|
||||
# makemkvcon), /app/makemkv-data in der API. Beide zeigen laut
|
||||
# docker-compose.yml auf DASSELBE Host-Verzeichnis.
|
||||
DATEN_DIR = os.getenv("MAKEMKV_DATA_DIR", "/root/.MakeMKV")
|
||||
def daten_verzeichnis(umgebung=None, plattform_name=None, existiert=None) -> str:
|
||||
"""Wo makemkvcon SEINE Einstellungen sucht — je Rechner ein anderer Ort.
|
||||
|
||||
## Der Befund des Commanders (29.08.2026)
|
||||
|
||||
Beim ersten echten Rip-Versuch meldete makemkvcon neben dem Öffnen-Fehler
|
||||
auch das hier (an seiner Disc gemessen):
|
||||
|
||||
MSG:5051 „Der Testzeitraum ist abgelaufen. Erwerben Sie bitte einen
|
||||
Aktivierungsschlüssel"
|
||||
|
||||
Und das, obwohl in seinen Einstellungen ein gültiger Beta-Key lag.
|
||||
|
||||
Hier stand `os.getenv("MAKEMKV_DATA_DIR", "/root/.MakeMKV")`. Im Container
|
||||
stimmt das. Auf einem Windows-PC schrieb Rippy den Key damit nach
|
||||
`/root/.MakeMKV` — was dort ein Ordner namens `root\\.MakeMKV` auf dem
|
||||
aktuellen Laufwerk ist. MakeMKV liest `%USERPROFILE%\\.MakeMKV`
|
||||
(gemessen am 26.07.2026 auf genau diesem Rechner, MakeMKV 1.18.4), und
|
||||
dort lag **keine settings.conf**.
|
||||
|
||||
Der Key wurde also brav geholt, brav gespeichert — nur an einer Stelle,
|
||||
die niemand liest. Eine Automatik, die ins Leere schreibt, ist schlimmer
|
||||
als keine: Sie sieht aus wie erledigt.
|
||||
|
||||
`docker/worker/schluessel.py` kannte den richtigen Ort seit einem Monat.
|
||||
Zwei Module, zwei Antworten, eine davon falsch — dieselbe Sorte Fund wie
|
||||
`os.path` und `/app/media`.
|
||||
|
||||
Alles einspritzbar, damit jeder Betriebsfall ohne ihn prüfbar ist.
|
||||
"""
|
||||
umgebung = os.environ if umgebung is None else umgebung
|
||||
existiert = existiert or os.path.isdir
|
||||
gesetzt = (umgebung.get("MAKEMKV_DATA_DIR") or "").strip()
|
||||
if gesetzt:
|
||||
return gesetzt # docker-compose entscheidet, wenn es das tut
|
||||
|
||||
system = (plattform_name if plattform_name is not None else sys.platform).lower()
|
||||
if not system.startswith("win"):
|
||||
return "/root/.MakeMKV"
|
||||
|
||||
# Windows: MakeMKV ist über die Versionen umgezogen. Geprüft wird, welcher
|
||||
# Ort wirklich da ist, statt einen zu behaupten. Der erste ist zugleich
|
||||
# der Ort zum ANLEGEN, wenn es noch gar keinen gibt — MakeMKV legt sein
|
||||
# Verzeichnis erst beim ersten Speichern an.
|
||||
profil = umgebung.get("USERPROFILE") or os.path.expanduser("~")
|
||||
kandidaten = [
|
||||
os.path.join(profil, ".MakeMKV"),
|
||||
os.path.join(umgebung.get("APPDATA") or "", "MakeMKV"),
|
||||
os.path.join(umgebung.get("LOCALAPPDATA") or "", "MakeMKV"),
|
||||
]
|
||||
for pfad in kandidaten:
|
||||
if pfad and existiert(pfad):
|
||||
return pfad
|
||||
return kandidaten[0]
|
||||
|
||||
|
||||
#: Beim Laden ausgerechnet — die Aufrufer kennen ihn als Konstante.
|
||||
DATEN_DIR = daten_verzeichnis()
|
||||
|
||||
# GROSS geschrieben — unter Linux case-sensitiv, "keydb.cfg" wird ignoriert.
|
||||
KEYDB_NAME = "KEYDB.cfg"
|
||||
|
||||
@@ -0,0 +1,215 @@
|
||||
"""Wie makemkvcon angesprochen und wie seine Antwort gelesen wird.
|
||||
|
||||
## Der Befund des Commanders (29.08.2026)
|
||||
|
||||
> „Das Rippen der disk funktioniert nicht. makemkvcon endete mit Code 11 —
|
||||
> letzte Meldung: Das Öffnen der Disk schlug fehl — keine MKV-Datei
|
||||
> entstanden"
|
||||
|
||||
In dem einen Satz stecken DREI Fehler, und alle drei sind an seinem Laufwerk
|
||||
gemessen worden:
|
||||
|
||||
**1. Die Quellenangabe.** Rippy uebergab `dev:\\\\.\\G:`.
|
||||
|
||||
dev:\\\\.\\G: MSG:2024 „Unknown device" -> MSG:5010 Öffnen schlug fehl
|
||||
dev:G: 68 TINFO-Zeilen, „Die Aufgabe wurde erfolgreich abgeschlossen"
|
||||
|
||||
**2. `Ö` statt `Ö`.** Das ist `Ö` als UTF-8 (C3 96), gelesen als cp1252 —
|
||||
also eine Ausgabe, die anders geschrieben als gelesen wurde.
|
||||
|
||||
**3. Die Ursache fehlte.** Im selben Lauf meldete makemkvcon MSG:5051 „Der
|
||||
Testzeitraum ist abgelaufen". Gesucht wurde aber nur nach dem ENGLISCHEN
|
||||
„evaluation period has expired" — auf einem deutschen Windows trifft das nie.
|
||||
Uebrig blieb die nichtssagende letzte Zeile.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from rippy.rip import makemkv_aufruf as auf
|
||||
|
||||
|
||||
# ── 1. Die Quellenangabe ────────────────────────────────────────────────
|
||||
@pytest.mark.parametrize("geraet,erwartet", [
|
||||
# DER Fall des Commanders: so meldet rippy.drives.windows das Laufwerk.
|
||||
(r"\\.\G:", "dev:G:"),
|
||||
(r"\\?\G:", "dev:G:"),
|
||||
("G:", "dev:G:"),
|
||||
("G:\\", "dev:G:"),
|
||||
("G:/", "dev:G:"),
|
||||
# Linux bleibt unveraendert — die Reparatur darf den Container nicht anfassen.
|
||||
("/dev/sr0", "dev:/dev/sr0"),
|
||||
("/dev/sr1", "dev:/dev/sr1"),
|
||||
])
|
||||
def test_jede_schreibweise_wird_zu_dem_was_makemkv_versteht(geraet, erwartet):
|
||||
assert auf.quelle(geraet) == erwartet
|
||||
|
||||
|
||||
def test_ohne_geraet_kommt_nichts():
|
||||
"""Ein `dev:` ohne Pfad waere eine Quellenangabe, die nichts benennt."""
|
||||
assert auf.quelle("") == ""
|
||||
assert auf.quelle(None) == ""
|
||||
assert auf.quelle(" ") == ""
|
||||
|
||||
|
||||
def test_ein_langer_windows_pfad_wird_nicht_verstuemmelt():
|
||||
"""Die Kuerzung gilt NUR fuer die nackte Laufwerkswurzel."""
|
||||
assert auf.quelle(r"\\.\D:\Ordner") == "dev:D:\\Ordner"
|
||||
|
||||
|
||||
# ── 2. Die Kodierung ────────────────────────────────────────────────────
|
||||
def test_utf8_ausgabe_wird_richtig_gelesen():
|
||||
"""So kommt es an, wenn Rippy die Konsole auf UTF-8 gestellt hat."""
|
||||
assert auf.text_von("Das Öffnen".encode("utf-8")) == "Das Öffnen"
|
||||
|
||||
|
||||
def test_cp1252_ausgabe_wird_AUCH_richtig_gelesen():
|
||||
"""So kommt es an, wenn nicht — direkt an makemkvcon gemessen."""
|
||||
assert auf.text_von("Das Öffnen".encode("cp1252")) == "Das Öffnen"
|
||||
|
||||
|
||||
def test_der_fehler_des_commanders_kommt_NICHT_zurueck():
|
||||
"""`Ö` darf nirgends mehr entstehen."""
|
||||
for kodierung in ("utf-8", "cp1252"):
|
||||
gelesen = auf.text_von("Das Öffnen der Disk schlug fehl".encode(kodierung))
|
||||
assert "Ã" not in gelesen, "Doppelt kodiert: %r" % gelesen
|
||||
assert gelesen == "Das Öffnen der Disk schlug fehl"
|
||||
|
||||
|
||||
def test_text_bleibt_text_und_leer_bleibt_leer():
|
||||
assert auf.text_von("schon Text") == "schon Text"
|
||||
assert auf.text_von(b"") == ""
|
||||
assert auf.text_von(None) == ""
|
||||
|
||||
|
||||
def test_unlesbare_bytes_werfen_nicht():
|
||||
"""Eine kaputte Zeile darf keinen laufenden Rip umwerfen."""
|
||||
assert auf.text_von(b"\xff\xfe kaputt") is not None
|
||||
|
||||
|
||||
# ── 3. Die Ursache ──────────────────────────────────────────────────────
|
||||
def test_die_gemessenen_ursachen_haben_eine_nummer():
|
||||
"""Nummern gelten in jeder Sprache, Texte nicht."""
|
||||
assert 2024 in auf.KRITISCHE_CODES, "Unknown device"
|
||||
assert 5051 in auf.KRITISCHE_CODES, "Testzeitraum abgelaufen"
|
||||
assert 5020 in auf.KRITISCHE_CODES, "Aktivierungsschluessel ungueltig"
|
||||
assert 5021 in auf.KRITISCHE_CODES, "Programmversion zu alt"
|
||||
|
||||
|
||||
def test_jede_ursache_ist_auf_deutsch_erklaert():
|
||||
"""Der Commander liest diese Zeile — eine Nummer allein hilft ihm nicht."""
|
||||
for code, text in auf.KRITISCHE_CODES.items():
|
||||
assert len(text) > 20, "%d ist nicht erklaert: %r" % (code, text)
|
||||
assert not text.startswith("MSG"), code
|
||||
|
||||
|
||||
# ── 4. Wohin gehoert der Schluessel — und was, wenn er abgelehnt wird ────
|
||||
#
|
||||
# Weiterer Befund vom 29.08.2026, in zwei Schritten gemessen:
|
||||
#
|
||||
# Erst schrieb Rippy den Beta-Key nach `/root/.MakeMKV/settings.conf` — ein
|
||||
# Container-Pfad. Nach der Reparatur des Ordners blieb MSG:5051
|
||||
# („Testzeitraum abgelaufen") trotzdem stehen. Nachgesehen, was auf dem
|
||||
# Rechner wirklich existiert:
|
||||
#
|
||||
# C:\Users\TobisPC\.MakeMKV\ nur _private_data.tar (6,4 MB)
|
||||
# HKCU\Software\MakeMKV app_UpdateLastCheck, app_SiteInfoString, …
|
||||
#
|
||||
# Unter Windows haelt MakeMKV seine Einstellungen in der REGISTRY. Rippy hat
|
||||
# den Key also zweimal brav gespeichert — beide Male an einen Ort, den
|
||||
# niemand liest.
|
||||
#
|
||||
# Und dann der Nachschlag: Mit dem Key in der Registry verschwand 5051, dafuer
|
||||
# kam 5020 („Aktivierungsschluessel ungueltig") und 5021 („Diese
|
||||
# Programmversion ist zu alt"). Sein MakeMKV 1.18.4 ist aelter als der
|
||||
# aktuelle Beta-Key verlangt. WICHTIG: Ohne Key las MakeMKV die Disc noch (68
|
||||
# TINFO-Zeilen), mit dem abgelehnten verweigerte es alles — 0 Titel.
|
||||
# **Ein abgelehnter Schluessel ist schlimmer als gar keiner.**
|
||||
|
||||
|
||||
def test_unter_windows_geht_der_key_in_die_registry():
|
||||
gelandet = []
|
||||
auf.key_ablegen("T-abc", plattform_name="win32",
|
||||
schreiber=lambda k: gelandet.append(k) or r"HKCU\Software\MakeMKV")
|
||||
assert gelandet == ["T-abc"]
|
||||
|
||||
|
||||
def test_unter_linux_bleibt_es_die_datei():
|
||||
"""Der Container-Weg darf sich durch die Reparatur NICHT aendern."""
|
||||
gelandet = []
|
||||
wohin = auf.key_ablegen("T-abc", plattform_name="linux",
|
||||
datei_schreiber=lambda k: gelandet.append(k) or "/root/.MakeMKV/settings.conf")
|
||||
assert gelandet == ["T-abc"]
|
||||
assert wohin.endswith("settings.conf")
|
||||
|
||||
|
||||
def test_ohne_key_wird_nichts_geschrieben():
|
||||
"""Sonst stuende ein leerer app_Key da — und der ist ein ungueltiger."""
|
||||
assert auf.key_ablegen("", plattform_name="win32",
|
||||
schreiber=lambda k: 1 / 0) == ""
|
||||
assert auf.key_ablegen(None, plattform_name="win32",
|
||||
schreiber=lambda k: 1 / 0) == ""
|
||||
|
||||
|
||||
AUSGABE_ABGELEHNT = (
|
||||
'MSG:1005,0,1,"MakeMKV v1.18.4 win(x64-release) gestartet"\n'
|
||||
'MSG:5020,0,0,"Der hinterlegte Aktivierungsschlüssel ist ungültig."\n'
|
||||
'MSG:5021,0,0,"Diese Programmversion ist zu alt."\n'
|
||||
).encode("utf-8")
|
||||
|
||||
AUSGABE_GUT = (
|
||||
'MSG:1005,0,1,"MakeMKV v1.18.4 win(x64-release) gestartet"\n'
|
||||
'DRV:0,2,999,12,"BD-RE BU40N","","G:"\n'
|
||||
).encode("utf-8")
|
||||
|
||||
|
||||
def test_ein_abgelehnter_schluessel_wird_erkannt():
|
||||
urteil = auf.key_pruefen("makemkvcon", laufen=lambda p: AUSGABE_ABGELEHNT)
|
||||
assert urteil["ok"] is False
|
||||
assert urteil["code"] == 5020
|
||||
assert "nicht angenommen" in urteil["grund"]
|
||||
|
||||
|
||||
def test_ein_angenommener_schluessel_auch():
|
||||
assert auf.key_pruefen("makemkvcon", laufen=lambda p: AUSGABE_GUT)["ok"] is True
|
||||
|
||||
|
||||
def test_ohne_makemkv_wird_nichts_behauptet():
|
||||
"""„Nicht befragbar" ist keine Aussage ueber den Schluessel."""
|
||||
urteil = auf.key_pruefen("")
|
||||
assert urteil["ok"] is False
|
||||
assert "nicht gefunden" in urteil["grund"]
|
||||
|
||||
|
||||
def test_ein_abgelehnter_schluessel_wird_WIEDER_ENTFERNT():
|
||||
"""Der Kern des Befundes: Ohne Key las MakeMKV die Disc noch, mit dem
|
||||
abgelehnten verweigerte es alles. Blind schreiben verschlechtert."""
|
||||
geschrieben, entfernt = [], []
|
||||
ergebnis = auf.key_setzen_und_pruefen(
|
||||
"T-zualt", "makemkvcon", plattform_name="win32",
|
||||
schreiber=lambda k: geschrieben.append(k) or r"HKCU\Software\MakeMKV",
|
||||
pruefer=lambda p: {"ok": False, "code": 5021,
|
||||
"grund": auf.KRITISCHE_CODES[5021]},
|
||||
entferner=lambda: entfernt.append(True) or True)
|
||||
assert geschrieben == ["T-zualt"]
|
||||
assert entfernt == [True], "der abgelehnte Schluessel muss wieder weg"
|
||||
assert ergebnis["ok"] is False
|
||||
assert "zu alt" in ergebnis["grund"]
|
||||
|
||||
|
||||
def test_ein_angenommener_schluessel_bleibt_liegen():
|
||||
entfernt = []
|
||||
ergebnis = auf.key_setzen_und_pruefen(
|
||||
"T-gut", "makemkvcon", plattform_name="win32",
|
||||
schreiber=lambda k: r"HKCU\Software\MakeMKV",
|
||||
pruefer=lambda p: {"ok": True, "code": None, "grund": ""},
|
||||
entferner=lambda: entfernt.append(True) or True)
|
||||
assert ergebnis["ok"] is True
|
||||
assert entfernt == [], "ein gueltiger Schluessel darf nicht angefasst werden"
|
||||
|
||||
|
||||
def test_die_codes_stehen_so_da_wie_gemessen():
|
||||
"""Waechter gegen den Fehler, den ich am 29.08.2026 selbst gemacht habe:
|
||||
5021 aus dem Kopf mit „Volume-Key unbekannt" zu beschriften."""
|
||||
assert "zu alt" in auf.KRITISCHE_CODES[5021]
|
||||
assert "Volume" not in auf.KRITISCHE_CODES[5021]
|
||||
assert auf.KEY_ABGELEHNT == (5020, 5021)
|
||||
@@ -371,7 +371,22 @@ def _beta_key_holen(melden=None) -> str:
|
||||
"Einstellungen.")
|
||||
return ""
|
||||
_einstellungen_schreiben({"makemkvAppKey": key})
|
||||
melden(" MakeMKV-Beta-Key geholt (…%s)." % key[-6:])
|
||||
|
||||
# Und jetzt nachsehen, ob MakeMKV ihn ueberhaupt annimmt. Am 29.08.2026
|
||||
# tat es das nicht: Version 1.18.4 ist aelter als der aktuelle Schluessel
|
||||
# verlangt (MSG 5020 + 5021). Ein abgelehnter Schluessel ist SCHLIMMER als
|
||||
# gar keiner — ohne las MakeMKV die Disc noch, mit dem abgelehnten
|
||||
# verweigerte es alles. `key_setzen_und_pruefen` nimmt ihn deshalb wieder
|
||||
# weg. Begruendung in `rippy.rip.makemkv_aufruf.key_pruefen`.
|
||||
from rippy.rip import makemkv_aufruf
|
||||
from rippy.tools import katalog
|
||||
|
||||
urteil = makemkv_aufruf.key_setzen_und_pruefen(key, katalog.finden("makemkv"))
|
||||
if urteil["ok"]:
|
||||
melden(" MakeMKV-Beta-Key geholt und angenommen (…%s)." % key[-6:])
|
||||
else:
|
||||
melden(" MakeMKV-Beta-Key geholt, aber NICHT angenommen: %s"
|
||||
% urteil["grund"])
|
||||
return key
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user