style: echte Umlaute im ganzen Projekt + Installer im Rippy-Look
Ampel / ampel (push) Successful in 27s
Ampel / ampel (push) Successful in 27s
Commander-Rueckmeldung: Umlaute fehlen. Ausloeser war sichtbar der Installer -
"Diese Maschine uebernimmt die Video-Kompression fuer Rippy" stand woertlich im
Screenshot.
## Die .ps1-Falle war loesbar, nicht unumgehbar
Bisher galt: ausgelieferte .ps1 MUESSEN ASCII sein, weil PowerShell 5.1 sie
ohne BOM als ANSI liest. Das ist nur die halbe Wahrheit - gemessen mit echtem
powershell.exe 5.1:
ohne BOM: $s = "Größe: äöü" + Unerwartetes Token -> Skript kaputt
mit BOM: Groesse: aeoeue + laeuft, Length 16 korrekt
Alle drei Skripte sind jetzt UTF-8 MIT BOM und tragen echte Umlaute; unter 5.1
gegengeprueft (BOM vorhanden, Parser fehlerfrei, Text korrekt gelesen). Der
Kopfkommentar sagt das jetzt richtig statt "ASCII-only".
## Umstellung: Text ja, Bezeichner nein
Umlaute gehoeren in Kommentare und Anzeigetexte, nicht in Funktionsnamen oder
Datenschluessel. Deshalb je Sprache das passende Werkzeug:
- Python: ueber den TOKENIZER - angefasst wurden ausschliesslich COMMENT- und
STRING-Tokens. 156 Stellen. Code ist damit garantiert unberuehrt.
- TypeScript: nur // und /* */ Kommentare sowie JSX-Text (kann per Definition
kein Bezeichner sein). 24 Stellen. `const waehlen`, `let laeuft`, `plaetze`,
`GeraetInfo` sind nachweislich unversehrt.
- install.sh: Anzeigetext, aber die Shell-Funktionen (gruen/rot/gelb/titel) und
der Schalter --nur-pruefen bleiben ASCII - das sind Schnittstellen.
- Markdown: 0 Aenderungen, die Doku hatte schon Umlaute.
ZWEI FEHLER MEINES KONVERTERS, beide von Werkzeugen gefangen:
1. In f-Strings steht in {...} CODE, kein Text. Aus f"{groesse}" wurde
f"{größe}", waehrend die Variable groesse hiess - Ruff meldete F821
"Undefined name". Der Konverter lagert Einsetzungen jetzt aus.
2. Ein Dict-Schluessel wurde umbenannt: die Wortliste enthaelt das PRAEFIX
"uebersprung", der Tabu-Schutz prueft aber ganze Woerter. bericht[...] ist
wieder ASCII - Umlaute in Datenschluesseln brechen JSON-Runden und DB-Felder.
## Installer im Rippy-Look
Statt hellgrau jetzt dieselben Toene wie das Web-UI (aus lib/design.ts
uebernommen): slate-900 Flaeche, dunkle Eingabefelder, Amber-Hauptknopf wie
"Los geht's" im Wizard. Oben ein Kopfbereich mit dem Farbverlauf
amber -> indigo -> purple und dem Disc-Symbol - in WinForms per Paint-Ereignis
gezeichnet, weil es dort keine Verlaeufe von der Stange gibt.
Geprueft, nicht gehofft: Das Fenster wurde headless in ein PNG gerendert
(DrawToBitmap) und angesehen - Verlauf, Symbol, Umlaute und Farben sitzen.
RippyWorkerSetup.exe neu gebaut (57344 -> 60416 Bytes); Umlaute und das
requireAdministrator-Manifest sind in der .exe verifiziert.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
"""MakeMKV-Datenverzeichnis: Schluesselspeicher, KEYDB.cfg, AACS-Dumps.
|
||||
"""MakeMKV-Datenverzeichnis: Schlüsselspeicher, KEYDB.cfg, AACS-Dumps.
|
||||
|
||||
WARUM ES DIESE DATEI GIBT (Befund 25.07.2026, auf BEIDEN Maschinen gemessen):
|
||||
4K-UHD-Discs scheiterten mit "The volume key is unknown for this disc". Die
|
||||
Ursache ist weder die Disc noch das Laufwerk — LibreDrive v06.3 laeuft und
|
||||
Ursache ist weder die Disc noch das Laufwerk — LibreDrive v06.3 läuft und
|
||||
MakeMKV liest die Disc — sondern:
|
||||
|
||||
makemkvcon unter LINUX ruft die Disc-Schluessel nie ab.
|
||||
@@ -10,35 +10,35 @@ MakeMKV liest die Disc — sondern:
|
||||
|
||||
Gemessen, nicht vermutet:
|
||||
* Linux: in KEINEM Lauf auch nur eine Verbindung nach draussen. Geprueft mit
|
||||
leerem UND mit gefuelltem Schluesselspeicher, mit und ohne --noscan, mit
|
||||
dev:/dev/sr0 und mit disc:0, und mit erzwungener frischer Pruefung
|
||||
(update.conf geloescht, Meldung 5074 belegt den Web-Kontakt). Immer:
|
||||
leerem UND mit gefülltem Schlüsselspeicher, mit und ohne --noscan, mit
|
||||
dev:/dev/sr0 und mit disc:0, und mit erzwungener frischer Prüfung
|
||||
(update.conf gelöscht, Meldung 5074 belegt den Web-Kontakt). Immer:
|
||||
keine Verbindung, kein Schluessel.
|
||||
* Windows, dieselbe Disc, dasselbe Laufwerk: Meldung 3338 "Downloading
|
||||
latest HK to ...", Verbindung nach 185.84.108.20:443
|
||||
(web33.majordomo.ru), _private_data.tar waechst — und die Disc geht auf
|
||||
(web33.majordomo.ru), _private_data.tar wächst — und die Disc geht auf
|
||||
(TCOUNT:5, "Operation successfully completed").
|
||||
* Der Code dafuer steckt auch im Linux-Binary: die Meldungsvorlage
|
||||
"Downloading latest %1 to %2 ..." steht in makemkvcon. Sie loest nur nie
|
||||
* Der Code dafür steckt auch im Linux-Binary: die Meldungsvorlage
|
||||
"Downloading latest %1 to %2 ..." steht in makemkvcon. Sie löst nur nie
|
||||
aus. Gleiches Symptom im Forum, seit Jahren offen und unbeantwortet.
|
||||
|
||||
FRUEHERE FEHLDIAGNOSE, bewusst festgehalten: An dieser Stelle stand zuerst,
|
||||
MakeMKVs Schluessel-Kanal sei abgeschaltet. Das war FALSCH. Die Herleitung
|
||||
stuetzte sich auf zwei Hostnamen aus alten Forumsbeitraegen
|
||||
stützte sich auf zwei Hostnamen aus alten Forumsbeitraegen
|
||||
(hkdata.fairuse.org, hkdata.crabdance.com), die tatsaechlich nicht mehr
|
||||
aufloesen — MakeMKV benutzt sie aber laengst nicht mehr. Der Dienst lebt, der
|
||||
Worker erreicht ihn sogar; er wird unter Linux nur nie gefragt.
|
||||
|
||||
WAS DARAUS FOLGT: Der Schluesselspeicher (_private_data.tar) muss von einer
|
||||
WAS DARAUS FOLGT: Der Schlüsselspeicher (_private_data.tar) muss von einer
|
||||
MakeMKV-Installation kommen, die ihn wirklich abruft — praktisch von Windows.
|
||||
Rippy nimmt ihn unter Einstellungen -> System entgegen. Die KEYDB.cfg bleibt
|
||||
der Notnagel fuer Pressungen, die auch MakeMKV selbst nicht kennt.
|
||||
der Notnagel für Pressungen, die auch MakeMKV selbst nicht kennt.
|
||||
|
||||
Rippy liefert KEINE Schluessel mit, laedt keine herunter und verteilt keine.
|
||||
Rippy liefert KEINE Schluessel mit, lädt keine herunter und verteilt keine.
|
||||
Es verwaltet nur, was der Nutzer selbst mitbringt.
|
||||
|
||||
QUELLEN (AGENTS Regel D — externe Schnittstellen nie aus dem Kopf):
|
||||
* Linux laedt keine Hashed Keys — dasselbe Symptom, mehrfach berichtet:
|
||||
* Linux lädt keine Hashed Keys — dasselbe Symptom, mehrfach berichtet:
|
||||
https://forum.makemkv.com/forum/viewtopic.php?t=25782
|
||||
https://forum.makemkv.com/forum/viewtopic.php?t=34022
|
||||
* Schluessel liegen als hkd_*.bin in _private_data.tar:
|
||||
@@ -55,7 +55,7 @@ QUELLEN (AGENTS Regel D — externe Schnittstellen nie aus dem Kopf):
|
||||
|
||||
ZWILLINGSDATEI: liegt identisch unter docker/api/ und docker/worker/ — beide
|
||||
Images brauchen sie, ein gemeinsames Paket gibt es in diesem Projekt nicht
|
||||
(gleiche Lage wie bei db.py). Aenderungen IMMER in BEIDEN Dateien nachziehen.
|
||||
(gleiche Lage wie bei db.py). Änderungen IMMER in BEIDEN Dateien nachziehen.
|
||||
"""
|
||||
|
||||
import io
|
||||
@@ -72,14 +72,14 @@ DATEN_DIR = os.getenv("MAKEMKV_DATA_DIR", "/root/.MakeMKV")
|
||||
# GROSS geschrieben — unter Linux case-sensitiv, "keydb.cfg" wird ignoriert.
|
||||
KEYDB_NAME = "KEYDB.cfg"
|
||||
|
||||
# Obergrenze fuer den Upload. Eine vollstaendige oeffentliche KEYDB.cfg liegt
|
||||
# Obergrenze für den Upload. Eine vollständige oeffentliche KEYDB.cfg liegt
|
||||
# im einstelligen MB-Bereich; 64 MB sind reichlich Luft und verhindern, dass
|
||||
# eine versehentlich hochgeladene Riesendatei den Speicher vollschreibt.
|
||||
MAX_KEYDB_BYTES = 64 * 1024 * 1024
|
||||
|
||||
# Eine Disc-Zeile beginnt mit der 40 Zeichen langen Hex-Kennung (optional mit
|
||||
# 0x davor), danach folgt das Gleichheitszeichen. Alles andere (Kommentare ab
|
||||
# ";", Leerzeilen, Fortsetzungsfelder) zaehlt nicht als Eintrag.
|
||||
# ";", Leerzeilen, Fortsetzungsfelder) zählt nicht als Eintrag.
|
||||
_DISC_ZEILE = re.compile(r"^\s*(?:0x)?[0-9a-fA-F]{40}\s*=")
|
||||
|
||||
|
||||
@@ -94,9 +94,9 @@ def keydb_pfad(daten_dir: str = None) -> str:
|
||||
|
||||
|
||||
def zaehle_disc_eintraege(inhalt: str) -> int:
|
||||
"""Zeilen mit Disc-Kennung zaehlen (pure Funktion, testbar).
|
||||
"""Zeilen mit Disc-Kennung zählen (pure Funktion, testbar).
|
||||
|
||||
Bewusst eine Heuristik und keine vollstaendige Auswertung: MakeMKV liest
|
||||
Bewusst eine Heuristik und keine vollständige Auswertung: MakeMKV liest
|
||||
die Datei mit seinem eigenen Parser, und wie viele Eintraege es daraus
|
||||
macht, ist von aussen nicht sichtbar. Die Zahl dient nur dazu, im UI
|
||||
"da liegt wirklich etwas drin" von "leere oder falsche Datei" zu
|
||||
@@ -106,17 +106,17 @@ def zaehle_disc_eintraege(inhalt: str) -> int:
|
||||
|
||||
|
||||
def keydb_pruefen(inhalt: str) -> str:
|
||||
"""Prueft hochgeladenen Inhalt; gibt deutschen Fehlertext oder "" zurueck.
|
||||
"""Prüft hochgeladenen Inhalt; gibt deutschen Fehlertext oder "" zurück.
|
||||
|
||||
Verhindert den haeufigsten Bedienfehler: statt der KEYDB.cfg landet die
|
||||
Verhindert den häufigsten Bedienfehler: statt der KEYDB.cfg landet die
|
||||
HTML-Fehlerseite eines Downloads oder eine leere Datei im Verzeichnis —
|
||||
MakeMKV wuerde dann still weiter "volume key is unknown" melden.
|
||||
MakeMKV würde dann still weiter "volume key is unknown" melden.
|
||||
"""
|
||||
if not inhalt.strip():
|
||||
return "Die Datei ist leer."
|
||||
if len(inhalt.encode("utf-8")) > MAX_KEYDB_BYTES:
|
||||
return (
|
||||
"Die Datei ist groesser als "
|
||||
"Die Datei ist größer als "
|
||||
f"{MAX_KEYDB_BYTES // (1024 * 1024)} MB — das ist keine KEYDB.cfg."
|
||||
)
|
||||
if inhalt.lstrip()[:1] == "<":
|
||||
@@ -150,7 +150,7 @@ def keydb_status(daten_dir: str = None) -> dict:
|
||||
with open(pfad, encoding="utf-8", errors="replace") as datei:
|
||||
eintraege = zaehle_disc_eintraege(datei.read())
|
||||
except OSError:
|
||||
pass # Datei da, aber unlesbar: Groesse/Datum stimmen trotzdem
|
||||
pass # Datei da, aber unlesbar: Größe/Datum stimmen trotzdem
|
||||
return {
|
||||
"vorhanden": True,
|
||||
"pfad": pfad,
|
||||
@@ -163,7 +163,7 @@ def keydb_status(daten_dir: str = None) -> dict:
|
||||
def keydb_schreiben(inhalt: str, daten_dir: str = None) -> dict:
|
||||
"""Schreibt die KEYDB.cfg atomar und meldet den neuen Zustand.
|
||||
|
||||
Erst in eine Nebendatei, dann os.replace: waehrend ein Rip laeuft, darf
|
||||
Erst in eine Nebendatei, dann os.replace: während ein Rip läuft, darf
|
||||
makemkvcon niemals eine halb geschriebene Datei zu sehen bekommen.
|
||||
"""
|
||||
pfad = keydb_pfad(daten_dir)
|
||||
@@ -244,32 +244,32 @@ def dumps_auflisten(daten_dir: str = None) -> list:
|
||||
return liste
|
||||
|
||||
|
||||
# --- Schluesselspeicher (_private_data.tar) -------------------------------
|
||||
# --- Schlüsselspeicher (_private_data.tar) -------------------------------
|
||||
#
|
||||
# Das ist MakeMKVs eigener Speicher fuer die "Hashed Keys": ein tar-Archiv mit
|
||||
# hkd_*.bin-Eintraegen. Unter Windows fuellt MakeMKV es selbst (Meldung 3338),
|
||||
# Das ist MakeMKVs eigener Speicher für die "Hashed Keys": ein tar-Archiv mit
|
||||
# hkd_*.bin-Eintraegen. Unter Windows füllt MakeMKV es selbst (Meldung 3338),
|
||||
# unter Linux nie — siehe Modul-Kopf. Rippy nimmt die Datei deshalb entgegen
|
||||
# und legt sie ins Datenverzeichnis; MakeMKV liest sie beim naechsten Start.
|
||||
# und legt sie ins Datenverzeichnis; MakeMKV liest sie beim nächsten Start.
|
||||
|
||||
PRIVATE_DATA_NAME = "_private_data.tar"
|
||||
|
||||
# Der Speicher lag am 25.07.2026 bei rund 6 MB und waechst mit jeder neuen
|
||||
# Der Speicher lag am 25.07.2026 bei rund 6 MB und wächst mit jeder neuen
|
||||
# Pressung. 64 MB sind reichlich Luft — und derselbe Wert wie
|
||||
# client_max_body_size in docker/ui/nginx.conf: waere die Grenze hier hoeher,
|
||||
# wuerde nginx den Upload abweisen, bevor die API ihn ueberhaupt sieht.
|
||||
# client_max_body_size in docker/ui/nginx.conf: wäre die Grenze hier höher,
|
||||
# würde nginx den Upload abweisen, bevor die API ihn überhaupt sieht.
|
||||
MAX_PRIVATE_DATA_BYTES = 64 * 1024 * 1024
|
||||
|
||||
|
||||
def private_data_pfad(daten_dir: str = None) -> str:
|
||||
"""Voller Pfad zum Schluesselspeicher im Datenverzeichnis."""
|
||||
"""Voller Pfad zum Schlüsselspeicher im Datenverzeichnis."""
|
||||
return os.path.join(daten_dir or DATEN_DIR, PRIVATE_DATA_NAME)
|
||||
|
||||
|
||||
def zaehle_schluessel(rohdaten: bytes) -> int:
|
||||
"""Anzahl der hkd_*.bin-Eintraege im Archiv (pure Funktion, testbar).
|
||||
|
||||
Das ist die ehrliche Kennzahl fuer "wie viele Disc-Schluessel kennt diese
|
||||
Installation". Ein frischer, leerer Speicher enthaelt nur eine
|
||||
Das ist die ehrliche Kennzahl für "wie viele Disc-Schluessel kennt diese
|
||||
Installation". Ein frischer, leerer Speicher enthält nur eine
|
||||
Index-Datei und kommt hier auf 0 — genau der Zustand, in dem jede
|
||||
unbekannte UHD-Disc scheitert.
|
||||
"""
|
||||
@@ -281,20 +281,20 @@ def zaehle_schluessel(rohdaten: bytes) -> int:
|
||||
|
||||
|
||||
def private_data_pruefen(rohdaten: bytes) -> str:
|
||||
"""Prueft hochgeladene Rohdaten; deutscher Fehlertext oder "".
|
||||
"""Prüft hochgeladene Rohdaten; deutscher Fehlertext oder "".
|
||||
|
||||
Faengt die beiden Bedienfehler ab, die sonst still danebengehen: eine
|
||||
voellig andere Datei hochladen, oder den Speicher einer Installation, die
|
||||
selbst noch keine Schluessel geholt hat (dann aendert sich nichts, und
|
||||
selbst noch keine Schluessel geholt hat (dann ändert sich nichts, und
|
||||
niemand versteht warum).
|
||||
"""
|
||||
if not rohdaten:
|
||||
return "Die Datei ist leer."
|
||||
if len(rohdaten) > MAX_PRIVATE_DATA_BYTES:
|
||||
return (
|
||||
"Die Datei ist groesser als "
|
||||
"Die Datei ist größer als "
|
||||
f"{MAX_PRIVATE_DATA_BYTES // (1024 * 1024)} MB — das ist kein "
|
||||
"MakeMKV-Schluesselspeicher."
|
||||
"MakeMKV-Schlüsselspeicher."
|
||||
)
|
||||
try:
|
||||
with tarfile.open(fileobj=io.BytesIO(rohdaten)) as archiv:
|
||||
@@ -308,13 +308,13 @@ def private_data_pruefen(rohdaten: bytes) -> str:
|
||||
return (
|
||||
"In dieser Datei steckt kein einziger Schluessel (kein hkd_*.bin). "
|
||||
"Sie stammt vermutlich von einer MakeMKV-Installation, die selbst "
|
||||
"noch keine geholt hat — oeffne dort erst einmal eine Disc."
|
||||
"noch keine geholt hat — öffne dort erst einmal eine Disc."
|
||||
)
|
||||
return ""
|
||||
|
||||
|
||||
def schluesselspeicher_status(daten_dir: str = None) -> dict:
|
||||
"""Zustand des Schluesselspeichers. Fehlt er, ist das kein Fehler."""
|
||||
"""Zustand des Schlüsselspeichers. Fehlt er, ist das kein Fehler."""
|
||||
pfad = private_data_pfad(daten_dir)
|
||||
try:
|
||||
angaben = os.stat(pfad)
|
||||
@@ -338,9 +338,9 @@ def schluesselspeicher_status(daten_dir: str = None) -> dict:
|
||||
|
||||
|
||||
def private_data_schreiben(rohdaten: bytes, daten_dir: str = None) -> dict:
|
||||
"""Legt den Schluesselspeicher atomar ab und meldet den neuen Zustand.
|
||||
"""Legt den Schlüsselspeicher atomar ab und meldet den neuen Zustand.
|
||||
|
||||
Atomar aus demselben Grund wie bei der KEYDB.cfg: waehrend ein Rip laeuft,
|
||||
Atomar aus demselben Grund wie bei der KEYDB.cfg: während ein Rip läuft,
|
||||
darf makemkvcon nie ein halb geschriebenes Archiv sehen.
|
||||
"""
|
||||
pfad = private_data_pfad(daten_dir)
|
||||
@@ -363,9 +363,9 @@ def settings_conf_zusammenfuehren(inhalt: str, key: str) -> str:
|
||||
"""app_Key setzen, ohne den Rest der settings.conf zu verlieren (pure).
|
||||
|
||||
Bis zum 25.07.2026 haben entrypoint.sh UND tasks.py die Datei komplett
|
||||
ueberschrieben. Mit dem jetzt persistenten Datenverzeichnis waere damit
|
||||
überschrieben. Mit dem jetzt persistenten Datenverzeichnis wäre damit
|
||||
bei jedem Containerstart und vor jedem Rip alles andere weg — z. B.
|
||||
app_UpdateEnable. Leerer Key laesst die vorhandene Zeile ebenfalls fallen,
|
||||
app_UpdateEnable. Leerer Key lässt die vorhandene Zeile ebenfalls fallen,
|
||||
damit ein bewusst geleerter Key nicht heimlich weiterwirkt.
|
||||
"""
|
||||
zeilen = [z for z in inhalt.splitlines() if not z.lstrip().startswith("app_Key")]
|
||||
|
||||
Reference in New Issue
Block a user