feat(uhd): KEYDB.cfg-Unterstuetzung - MakeMKVs Schluessel-Kanal liefert nichts mehr
Ampel / ampel (push) Successful in 28s
Ampel / ampel (push) Successful in 28s
4K-UHD scheiterte an "The volume key is unknown for this disc". Am 25.07.2026
im Worker-Container nachgemessen: Laufwerk und MakeMKV sind in Ordnung
(LibreDrive v06.3 / Meldung 1011, Disc wird gelesen, AACS-Dump geschrieben) -
MakeMKV versucht gar nicht erst, online einen Schluessel zu holen. Belege:
_private_data.tar enthaelt nur die Index-Datei und KEINE hkd_*.bin; auch mit
geloeschter update.conf (Meldung 5074 belegt den Web-Kontakt) und mit
app_UpdateEnable="1" kam keiner; hkdata.fairuse.org und hkdata.crabdance.com
loesen weltweit nicht mehr auf (NXDOMAIN gegen Fritz!Box, 8.8.8.8, 1.1.1.1).
Betroffen war Akira UHD (MKB v76, Pressung Dez. 2020) - also gerade KEINE
Neuerscheinung. Der bisherige Fehlertext ("Disc neuer als die
Schluessel-Datenbank, mit einem der naechsten Updates rippbar") war falsch.
Einziger heute funktionierender Weg ist eine vom Nutzer selbst mitgebrachte
KEYDB.cfg. Rippy liefert KEINE Schluessel mit, laedt keine herunter und
verteilt keine - es stellt nur den Platz bereit und zeigt an, was dort liegt.
- Datenverzeichnis persistent gemountet (MAKEMKV_DATA_HOST, Default
/srv/rippy/makemkv): KEYDB.cfg und AACS-Dumps ueberleben jeden Rebuild.
Vorher loeschte jeder "up -d --build" beides - inklusive des Dumps, auf den
die Fehlermeldung selbst verwies.
- entrypoint.sh und tasks.py schreiben settings.conf ergaenzend statt
zerstoerend. Der entrypoint bricht bei nicht beschreibbarem Verzeichnis
nicht mehr ab - mit "restart: unless-stopped" waere das ein Crashloop
gewesen, in dem auch reines DVD-Rippen tot ist.
- Neues Zwillings-Modul makemkv_daten.py (docker/api + docker/worker,
byteweise identisch; test_zwillinge_sind_byteweise_identisch wacht darueber
und wurde durch absichtliches Verstellen als wirksam nachgewiesen).
- API: GET/POST/DELETE /system/keydb, GET /system/aacs-dumps(/{dateiname}).
JSON-Body statt Multipart - python-multipart ist bewusst nicht installiert
und wuerde die API beim Import toeten. nginx client_max_body_size 64m,
sonst scheitert der Upload mit 413, bevor die API ihn sieht.
- UI (Einstellungen -> System): Status, Hochladen per Datei-Dialog, Entfernen,
Dump-Download, KEYDB-Plakette je Worker (nur wo das Verzeichnis wirklich
gemountet ist - ein Remote-Transcode-Worker truege sonst eine Warnung,
die ihn nichts angeht).
- parse_msg() + log_cb: MakeMKV-Meldungen landen im Rippy-Log (gedrosselt:
Code 1003 raus, keine Wiederholungen, max. 40 je Rip). Nebenbei behoben:
der alte Parser (split(",", 4)[3]) schnitt jede Meldung am ersten Komma ab.
- Fuenf Stellen richtiggestellt, die behaupteten, MakeMKV-Updates braechten
die neueste Disc-Schluessel-Datenbank mit (UI, Anleitung, README,
Worker-Dockerfile, makemkv_key.py).
NICHT bewiesen: ein erfolgreicher UHD-Rip - es lag keine KEYDB.cfg mit dem
Akira-Schluessel vor. Belegt sind der Befund und die neue Mechanik. So steht
es auch im SAVEPOINT und in der ROADMAP.
Quellen (AGENTS Regel D):
- Datenverzeichnis + Dateiname GROSS/case-sensitiv:
https://forum.makemkv.com/forum/viewtopic.php?t=30636
- hkd_*.bin in _private_data.tar:
https://forum.makemkv.com/forum/viewtopic.php?t=32675
- headless settings.conf / app_UpdateEnable:
https://forum.makemkv.com/forum/viewtopic.php?t=20364
- KEYDB.cfg-Zeilenformat (libaacs):
https://github.com/ShiftMediaProject/libaacs/blob/master/KEYDB.cfg
- MSG-/PRGV-Format: https://www.makemkv.com/developers/usage.txt
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,7 @@ import uuid
|
||||
|
||||
import db
|
||||
import devices as device_discovery
|
||||
import makemkv_daten
|
||||
import makemkv_key
|
||||
import mounts as mount_verwaltung
|
||||
import notify
|
||||
@@ -1190,6 +1191,146 @@ async def system_info():
|
||||
return await asyncio.to_thread(sammle)
|
||||
|
||||
|
||||
# Eigene Wurzel für die Datei-Härtung der AACS-Dumps. Bewusst NICHT die
|
||||
# MEDIA_ROOT-Helfer (_sicherer_dateiname/_validiere_ziel/_job_ausgabeordner):
|
||||
# die prüfen hart gegen /app/media und würden hier IMMER 404 liefern.
|
||||
# Das MakeMKV-Datenverzeichnis liegt woanders (in der API auf
|
||||
# /app/makemkv-data, im Worker auf /root/.MakeMKV — laut docker-compose.yml
|
||||
# beides dasselbe Host-Verzeichnis).
|
||||
MAKEMKV_DATA_ROOT = os.path.realpath(makemkv_daten.DATEN_DIR)
|
||||
|
||||
|
||||
class KeydbRequest(BaseModel):
|
||||
inhalt: str # voller Text der KEYDB.cfg (kein Upload — es gibt kein python-multipart)
|
||||
|
||||
|
||||
@app.get("/system/keydb")
|
||||
async def get_keydb_status():
|
||||
"""Was liegt gerade als KEYDB.cfg im MakeMKV-Datenverzeichnis?
|
||||
|
||||
Hintergrund (Befund 25.07.2026, live auf der VM nachgemessen): Bei
|
||||
4K-UHD-Discs meldet MakeMKV "The volume key is unknown for this disc" und
|
||||
holt den Schlüssel NICHT mehr online nach — die dokumentierten
|
||||
Schlüssel-Server lösen weltweit nicht mehr auf. Der einzige heute
|
||||
funktionierende Weg ist eine KEYDB.cfg, die der Nutzer selbst mitbringt.
|
||||
Rippy liefert KEINE Schlüssel mit, lädt keine herunter und verteilt keine —
|
||||
es stellt nur den Platz bereit und zeigt ehrlich an, was dort liegt.
|
||||
|
||||
Fehlendes Verzeichnis oder fehlende Datei ist der NORMALFALL: dann kommt
|
||||
200 mit vorhanden=false zurück, niemals 404 oder 500.
|
||||
"""
|
||||
def sammle():
|
||||
return makemkv_daten.keydb_status()
|
||||
|
||||
return await asyncio.to_thread(sammle)
|
||||
|
||||
|
||||
@app.post("/system/keydb")
|
||||
async def set_keydb(request: KeydbRequest):
|
||||
"""Legt die vom Nutzer mitgebrachte KEYDB.cfg ab (atomar, ersetzt die alte).
|
||||
|
||||
WICHTIG für die Ehrlichkeit: Die Datei wirkt erst beim NÄCHSTEN Rip —
|
||||
makemkvcon liest sie beim Prozessstart, ein bereits laufender Rip merkt
|
||||
nichts davon. Genau so steht es auch im Log-Eintrag.
|
||||
"""
|
||||
# Reine Prüfung (kein Dateisystem) — fängt den häufigsten Bedienfehler ab:
|
||||
# statt der KEYDB.cfg landet die HTML-Fehlerseite eines Downloads im Feld.
|
||||
fehler = makemkv_daten.keydb_pruefen(request.inhalt)
|
||||
if fehler:
|
||||
raise HTTPException(status_code=422, detail=fehler)
|
||||
|
||||
def schreibe():
|
||||
return makemkv_daten.keydb_schreiben(request.inhalt)
|
||||
|
||||
try:
|
||||
status = await asyncio.to_thread(schreibe)
|
||||
except OSError as e:
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail=(
|
||||
f"KEYDB.cfg konnte nicht geschrieben werden: {e}. "
|
||||
"Prüfe, ob das MakeMKV-Datenverzeichnis auf der VM existiert und "
|
||||
"beschreibbar ist (Standard: /srv/rippy/makemkv)."
|
||||
),
|
||||
)
|
||||
await asyncio.to_thread(
|
||||
db.add_log, "success", "makemkv-keydb",
|
||||
f"KEYDB.cfg abgelegt: {status['eintraege']} Zeilen mit Disc-Kennung, "
|
||||
f"{status['groesse_bytes']} Bytes ({status['pfad']}). "
|
||||
"Wirkt erst beim NÄCHSTEN Rip — MakeMKV liest die Datei beim Start.",
|
||||
)
|
||||
return status
|
||||
|
||||
|
||||
@app.delete("/system/keydb")
|
||||
async def delete_keydb():
|
||||
"""Entfernt die KEYDB.cfg (z. B. nach einem Fehlgriff beim Einfügen).
|
||||
|
||||
Auch hier gilt: Die Änderung wirkt erst beim NÄCHSTEN Rip. Fehlt die Datei
|
||||
schon, ist das kein Fehler — es kommt derselbe Zustand mit vorhanden=false.
|
||||
"""
|
||||
def loesche():
|
||||
return makemkv_daten.keydb_loeschen()
|
||||
|
||||
try:
|
||||
status = await asyncio.to_thread(loesche)
|
||||
except OSError as e:
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail=f"KEYDB.cfg konnte nicht entfernt werden: {e}",
|
||||
)
|
||||
await asyncio.to_thread(
|
||||
db.add_log, "warning", "makemkv-keydb",
|
||||
"KEYDB.cfg entfernt. Ab dem NÄCHSTEN Rip fehlen die selbst mitgebrachten "
|
||||
"Schlüssel wieder — UHD-Discs können dann erneut an "
|
||||
"'The volume key is unknown for this disc' scheitern.",
|
||||
)
|
||||
return status
|
||||
|
||||
|
||||
@app.get("/system/aacs-dumps")
|
||||
async def get_aacs_dumps():
|
||||
"""AACS-Dumps, die MakeMKV selbst abgelegt hat (neueste zuerst).
|
||||
|
||||
MakeMKV schreibt sie beim gescheiterten UHD-Versuch ins Datenverzeichnis
|
||||
(Meldung 3332 "Saved AACS dump file as file:///root/.MakeMKV/<name>.tgz",
|
||||
am 25.07.2026 so beobachtet). Rippy wertet sie nicht aus und schickt sie
|
||||
nirgendwohin — es zeigt nur, dass sie da sind, damit der Nutzer selbst
|
||||
entscheiden kann, was er damit tut.
|
||||
"""
|
||||
def liste():
|
||||
return {"dumps": makemkv_daten.dumps_auflisten()}
|
||||
|
||||
return await asyncio.to_thread(liste)
|
||||
|
||||
|
||||
@app.get("/system/aacs-dumps/{dateiname}")
|
||||
async def download_aacs_dump(dateiname: str):
|
||||
"""Lädt EINEN AACS-Dump herunter.
|
||||
|
||||
Pfad-Validierung genauso streng wie beim Job-Datei-Download: nackter Name
|
||||
ohne Pfadtrenner und ohne führenden Punkt (ist_aacs_dump) PLUS realpath,
|
||||
der das MakeMKV-Datenverzeichnis nicht verlassen darf (kein ..-Ausbruch,
|
||||
kein Symlink nach draußen).
|
||||
"""
|
||||
if not makemkv_daten.ist_aacs_dump(dateiname):
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail="Kein gültiger Dump-Name — erwartet wird eine .tgz-Datei ohne Pfadangabe.",
|
||||
)
|
||||
pfad = os.path.join(MAKEMKV_DATA_ROOT, dateiname)
|
||||
|
||||
def pruefe():
|
||||
return os.path.isfile(pfad) and os.path.realpath(pfad).startswith(MAKEMKV_DATA_ROOT)
|
||||
|
||||
if not await asyncio.to_thread(pruefe):
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail="Dump nicht gefunden — MakeMKV legt ihn erst beim gescheiterten UHD-Versuch an.",
|
||||
)
|
||||
return FileResponse(pfad, filename=dateiname, media_type="application/gzip")
|
||||
|
||||
|
||||
class NotificationTestRequest(BaseModel):
|
||||
url: str
|
||||
|
||||
|
||||
Reference in New Issue
Block a user