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
-1
@@ -203,7 +203,7 @@ def zaehle_online_worker(sekunden: int = 120) -> int:
|
||||
|
||||
Die Zombie-Erkennung vergleicht das mit der Zahl der Celery-Antworten:
|
||||
melden sich weniger Worker als bekannt sind, ist die Auskunft
|
||||
unvollstaendig — dann wird NICHTS als Leiche gewertet.
|
||||
unvollständig — dann wird NICHTS als Leiche gewertet.
|
||||
"""
|
||||
from datetime import timedelta
|
||||
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -223,9 +223,9 @@ def parse_msg(zeile: str):
|
||||
fertig zusammengesetzte Klartext.
|
||||
|
||||
Bis zum 25.07.2026 stand hier line.split(",", 4)[3]: das schnitt JEDE
|
||||
Meldung ab, die selbst ein Komma enthaelt — und MakeMKV schreibt solche
|
||||
Meldung ab, die selbst ein Komma enthält — und MakeMKV schreibt solche
|
||||
laufend ("Title #1 has length of 12 seconds, which is less than ...").
|
||||
Deshalb eine Regex, die die Anfuehrungszeichen respektiert.
|
||||
Deshalb eine Regex, die die Anführungszeichen respektiert.
|
||||
"""
|
||||
treffer = _MSG_RE.match(zeile.strip())
|
||||
if not treffer:
|
||||
@@ -409,7 +409,7 @@ def _handbrake_schleife(process, output_path: str, abbruch_cb=None, progress_cb=
|
||||
try:
|
||||
for line in process.stdout:
|
||||
# Zuerst der Abbruch — unabhängig davon, ob die Zeile überhaupt
|
||||
# einen Fortschritt enthält. Sonst haengt „Abbrechen" daran, dass
|
||||
# einen Fortschritt enthält. Sonst hängt „Abbrechen" daran, dass
|
||||
# sich die Prozentzahl bewegt (Befund 25.07.2026).
|
||||
if abbruch_cb:
|
||||
abbruch_cb()
|
||||
@@ -475,7 +475,7 @@ def run_makemkv(device_path: str, output_dir: str, progress_cb=None, titel: str
|
||||
log_cb(code, text) bekommt JEDE MakeMKV-Meldung. Bewusst ein eigener
|
||||
Kanal statt progress_cb: der Fortschritts-Callback in tasks.py verwirft
|
||||
Aufrufe, bei denen sich die Prozentzahl nicht geaendert hat — Meldungen
|
||||
waeren dort also grossteils verschwunden. Ohne diesen Kanal war am
|
||||
wären dort also großteils verschwunden. Ohne diesen Kanal war am
|
||||
25.07.2026 nicht von aussen erkennbar, dass MakeMKV bei der UHD-Disc
|
||||
nicht einmal versucht, einen Schluessel zu holen (siehe makemkv_daten).
|
||||
"""
|
||||
|
||||
@@ -104,7 +104,7 @@ def unter_wurzel(pfad: str, wurzel: str) -> bool:
|
||||
"""Liegt `pfad` wirklich unterhalb von `wurzel` (oder IST es die Wurzel)?
|
||||
|
||||
Pure Funktion, testbar. Ein nacktes `startswith()` genügt nicht:
|
||||
„/app/media-boese/x" beginnt mit „/app/media", liegt aber ausserhalb
|
||||
„/app/media-boese/x" beginnt mit „/app/media", liegt aber außerhalb
|
||||
(Befund 25.07.2026 bei der Durchsicht). Immer „/" als Trenner — das sind
|
||||
Container-Pfade, auch wenn ein nativer Windows-Worker das Modul lädt.
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
"""Tests fuer makemkv_daten.py: die reinen Helfer rund um das MakeMKV-Datenverzeichnis.
|
||||
"""Tests für makemkv_daten.py: die reinen Helfer rund um das MakeMKV-Datenverzeichnis.
|
||||
|
||||
WARUM ES DIESE TESTS GIBT (Befund 25.07.2026, live im Worker nachgemessen):
|
||||
Eine 4K-UHD-Disc (Akira UHD, MKB v76) scheiterte mit "The volume key is unknown
|
||||
for this disc", obwohl Laufwerk und MakeMKV in Ordnung waren. Der einzige heute
|
||||
noch funktionierende Weg ist eine selbst mitgebrachte KEYDB.cfg im
|
||||
Datenverzeichnis. Damit haengt einiges an diesen kleinen Funktionen: erkennen wir
|
||||
die Datei falsch, meldet das UI "alles gut", waehrend MakeMKV weiter scheitert.
|
||||
Datenverzeichnis. Damit hängt einiges an diesen kleinen Funktionen: erkennen wir
|
||||
die Datei falsch, meldet das UI "alles gut", während MakeMKV weiter scheitert.
|
||||
|
||||
Getestet wird nur, was ohne Postgres, Redis und ohne Laufwerk laeuft — also die
|
||||
Getestet wird nur, was ohne Postgres, Redis und ohne Laufwerk läuft — also die
|
||||
puren Funktionen mit echten Beispieldaten. Zeilenformat der KEYDB.cfg laut
|
||||
libaacs (AGENTS Regel D, externe Schnittstellen nie aus dem Kopf):
|
||||
https://github.com/ShiftMediaProject/libaacs/blob/master/KEYDB.cfg
|
||||
@@ -17,7 +17,7 @@ ist eine Zwillingsdatei, es gibt sie unter docker/api/ UND docker/worker/, und
|
||||
beide Seiten haben Tests. Da im Projekt keine __init__.py liegen, importiert
|
||||
pytest Testdateien unter ihrem blossen Dateinamen — zwei Dateien namens
|
||||
test_makemkv_daten.py brechen deshalb die Sammelphase ab ("import file
|
||||
mismatch") und faerben die ganze Ampel rot. Nicht zurueckbenennen.
|
||||
mismatch") und faerben die ganze Ampel rot. Nicht zurückbenennen.
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
@@ -47,10 +47,10 @@ zaehle_schluessel = _modul.zaehle_schluessel
|
||||
|
||||
|
||||
def _tar_mit(namen):
|
||||
"""Baut ein tar-Archiv im Speicher — so sieht MakeMKVs Schluesselspeicher aus.
|
||||
"""Baut ein tar-Archiv im Speicher — so sieht MakeMKVs Schlüsselspeicher aus.
|
||||
|
||||
Echte Eintragsnamen aus dem Speicher der Windows-Installation vom
|
||||
25.07.2026: hkd_<8 Hex>.bin fuer die Schluessel, dazu Index-Dateien.
|
||||
25.07.2026: hkd_<8 Hex>.bin für die Schluessel, dazu Index-Dateien.
|
||||
"""
|
||||
import io
|
||||
import tarfile
|
||||
@@ -67,7 +67,7 @@ def _tar_mit(namen):
|
||||
def test_zaehle_schluessel_zaehlt_nur_hkd_eintraege():
|
||||
# Nur hkd_*.bin sind Disc-Schluessel. Index- und sdf-Dateien gehoeren zum
|
||||
# Speicher dazu, sind aber keine Schluessel — sonst meldete das UI
|
||||
# "1 Schluessel vorhanden" fuer einen komplett leeren Vorrat.
|
||||
# "1 Schluessel vorhanden" für einen komplett leeren Vorrat.
|
||||
voll = _tar_mit([
|
||||
"hkd_00000059.bin",
|
||||
"hkd_0000005a.bin",
|
||||
@@ -101,7 +101,7 @@ def test_private_data_pruefen_lehnt_leere_und_falsche_dateien_ab():
|
||||
|
||||
def test_private_data_pruefen_lehnt_speicher_ohne_schluessel_ab():
|
||||
# Der teuerste Bedienfehler: den Speicher einer Installation hochladen,
|
||||
# die selbst noch nie Schluessel geholt hat. Ohne diese Pruefung aendert
|
||||
# die selbst noch nie Schluessel geholt hat. Ohne diese Prüfung ändert
|
||||
# sich nichts und niemand versteht, warum.
|
||||
leer = _tar_mit(["--index-4EF73C3560D489497ACE763962A7F07A4A1545C4.bin"])
|
||||
assert "kein einziger Schluessel" in private_data_pruefen(leer)
|
||||
@@ -123,7 +123,7 @@ def test_zwillinge_sind_byteweise_identisch():
|
||||
api = hashlib.sha256(datei.read()).hexdigest()
|
||||
assert worker == api, (
|
||||
"docker/worker/makemkv_daten.py und docker/api/makemkv_daten.py sind "
|
||||
"auseinandergelaufen - Aenderungen immer in BEIDE Dateien uebernehmen."
|
||||
"auseinandergelaufen - Änderungen immer in BEIDE Dateien übernehmen."
|
||||
)
|
||||
|
||||
# Eine kleine, aber echte KEYDB.cfg im libaacs-Format: Kommentarkopf, eine
|
||||
@@ -142,7 +142,7 @@ A1B2C3D4E5F60718293A4B5C6D7E8F90A1B2C3D4 = BLADE RUNNER 2049
|
||||
def test_zaehle_disc_eintraege_zaehlt_nur_echte_disc_zeilen():
|
||||
"""Nur Zeilen mit 40 Hex-Zeichen und Gleichheitszeichen sind Eintraege.
|
||||
|
||||
Kommentare, Leerzeilen und Fortsetzungsfelder duerfen nicht mitzaehlen —
|
||||
Kommentare, Leerzeilen und Fortsetzungsfelder dürfen nicht mitzaehlen —
|
||||
sonst meldet das UI bei einer reinen Kommentardatei stolz "42 Eintraege".
|
||||
"""
|
||||
assert zaehle_disc_eintraege(BEISPIEL_KEYDB) == 2
|
||||
@@ -158,14 +158,14 @@ def test_zaehle_disc_eintraege_ignoriert_zu_kurze_kennung():
|
||||
"""39 Hex-Zeichen sind keine Disc-Kennung.
|
||||
|
||||
Genau so sieht eine beim Kopieren verstuemmelte Datei aus — die darf nicht
|
||||
als gueltig durchgehen, sonst sucht der Commander den Fehler beim Laufwerk.
|
||||
als gültig durchgehen, sonst sucht der Commander den Fehler beim Laufwerk.
|
||||
"""
|
||||
zu_kurz = "A1B2C3D4E5F60718293A4B5C6D7E8F90A1B2C3D = KAPUTT\n"
|
||||
assert zaehle_disc_eintraege(zu_kurz) == 0
|
||||
|
||||
|
||||
def test_keydb_pruefen_meldet_leere_datei():
|
||||
# Haeufigster Fehlgriff: das Textfeld war leer, es wird trotzdem gespeichert.
|
||||
# Häufigster Fehlgriff: das Textfeld war leer, es wird trotzdem gespeichert.
|
||||
assert keydb_pruefen("") != ""
|
||||
assert keydb_pruefen(" \n\n ") != ""
|
||||
|
||||
@@ -173,7 +173,7 @@ def test_keydb_pruefen_meldet_leere_datei():
|
||||
def test_keydb_pruefen_erkennt_html_fehlerseite():
|
||||
"""Der zweithaeufigste Fehlgriff: der Download lieferte eine HTML-Seite.
|
||||
|
||||
MakeMKV wuerde die Datei still ignorieren und weiter "volume key is unknown"
|
||||
MakeMKV würde die Datei still ignorieren und weiter "volume key is unknown"
|
||||
melden — deshalb muss der Fehler schon beim Hochladen sichtbar werden.
|
||||
"""
|
||||
html = "<!DOCTYPE html>\n<html><body><h1>404 Not Found</h1></body></html>\n"
|
||||
@@ -184,12 +184,12 @@ def test_keydb_pruefen_erkennt_html_fehlerseite():
|
||||
|
||||
def test_keydb_pruefen_meldet_text_ohne_disc_zeile():
|
||||
# Irgendein Text (hier: eine README) ist keine KEYDB.cfg.
|
||||
meldung = keydb_pruefen("Diese Datei enthaelt keine Schluessel, nur Prosa.\n")
|
||||
meldung = keydb_pruefen("Diese Datei enthält keine Schluessel, nur Prosa.\n")
|
||||
assert meldung != ""
|
||||
|
||||
|
||||
def test_keydb_pruefen_akzeptiert_gueltigen_inhalt():
|
||||
# Leerer Rueckgabewert heisst laut Vertrag: alles in Ordnung.
|
||||
# Leerer Rueckgabewert heißt laut Vertrag: alles in Ordnung.
|
||||
assert keydb_pruefen(BEISPIEL_KEYDB) == ""
|
||||
|
||||
|
||||
@@ -200,9 +200,9 @@ def test_ist_aacs_dump_akzeptiert_echten_namen():
|
||||
|
||||
|
||||
def test_ist_aacs_dump_lehnt_pfad_tricks_und_fremde_dateien_ab():
|
||||
"""Der Download-Endpunkt haengt den Namen an das Datenverzeichnis an —
|
||||
ohne diese Pruefung koennte man sich damit aus dem Verzeichnis heraus
|
||||
lesen. Versteckte Dateien und Nicht-Dumps sind ebenfalls nichts fuer die
|
||||
"""Der Download-Endpunkt hängt den Namen an das Datenverzeichnis an —
|
||||
ohne diese Prüfung könnte man sich damit aus dem Verzeichnis heraus
|
||||
lesen. Versteckte Dateien und Nicht-Dumps sind ebenfalls nichts für die
|
||||
Liste."""
|
||||
assert ist_aacs_dump("../ausbruch.tgz") is False
|
||||
assert ist_aacs_dump(".versteckt.tgz") is False
|
||||
@@ -212,8 +212,8 @@ def test_ist_aacs_dump_lehnt_pfad_tricks_und_fremde_dateien_ab():
|
||||
|
||||
def test_settings_conf_ersetzt_key_und_behaelt_den_rest():
|
||||
"""DIE Regression, um die es geht: bis zum 25.07.2026 haben entrypoint.sh
|
||||
und tasks.py die settings.conf komplett ueberschrieben. Mit dem jetzt
|
||||
persistenten Datenverzeichnis waere damit bei jedem Containerstart und vor
|
||||
und tasks.py die settings.conf komplett überschrieben. Mit dem jetzt
|
||||
persistenten Datenverzeichnis wäre damit bei jedem Containerstart und vor
|
||||
jedem Rip alles andere weg — allen voran app_UpdateEnable."""
|
||||
alt = 'app_Key = "T-alterSchluessel"\napp_UpdateEnable = "1"\napp_DefaultSelectionString = "+sel:all"\n'
|
||||
neu = settings_conf_zusammenfuehren(alt, "T-neuerSchluessel")
|
||||
|
||||
@@ -114,15 +114,15 @@ def test_pfad_lokal_uebersetzt_fuer_windows_worker():
|
||||
assert pfad_lokal("/app/temp/raw/abc", "") == "/app/temp/raw/abc"
|
||||
|
||||
|
||||
# Die beiden frueheren Tests zu _original_aufheben sind nach
|
||||
# test_original_aufheben.py gewandert und dort erweitert. Sie pruefen jetzt das
|
||||
# Die beiden früheren Tests zu _original_aufheben sind nach
|
||||
# test_original_aufheben.py gewandert und dort erweitert. Sie prüfen jetzt das
|
||||
# richtige Kriterium: Vorher taeuschten sie per gefaelschtem os.stat
|
||||
# „verschiedene Dateisysteme" vor — genau die Annahme, an der der Schutz in der
|
||||
# Praxis scheiterte (st_dev war identisch, os.rename trotzdem EXDEV).
|
||||
|
||||
|
||||
def test_arbeitsverzeichnis_wahl_des_rips_schlaegt_die_einstellung():
|
||||
"""Pro Rip waehlbar (Commander 25.07.2026), Einstellung bleibt Standard.
|
||||
"""Pro Rip wählbar (Commander 25.07.2026), Einstellung bleibt Standard.
|
||||
|
||||
Reihenfolge: Wahl dieses Rips -> Setting -> Container-Default. Der
|
||||
Setting-Wert ist genau der, der bei Vollautomatik-Rips greift, weil dort
|
||||
@@ -134,16 +134,16 @@ def test_arbeitsverzeichnis_wahl_des_rips_schlaegt_die_einstellung():
|
||||
assert tasks._arbeitsverzeichnis(einst, "/app/media/rippy") == "/app/media/rippy"
|
||||
assert tasks._arbeitsverzeichnis(einst) == "/app/media/movies"
|
||||
assert tasks._arbeitsverzeichnis({}) == tasks.RAW_DIR
|
||||
# Ausbruchsversuche und Pfade ausserhalb /app/media fallen durch
|
||||
# Ausbruchsversuche und Pfade außerhalb /app/media fallen durch
|
||||
assert tasks._arbeitsverzeichnis({}, "/etc") == tasks.RAW_DIR
|
||||
assert tasks._arbeitsverzeichnis({}, "/app/media/../etc") == tasks.RAW_DIR
|
||||
# Leere Wahl faellt sauber auf die Einstellung zurueck
|
||||
# Leere Wahl fällt sauber auf die Einstellung zurück
|
||||
assert tasks._arbeitsverzeichnis(einst, " ") == "/app/media/movies"
|
||||
|
||||
|
||||
def test_unter_wurzel_faellt_nicht_auf_praefix_namen_herein():
|
||||
"""Befund 25.07.2026: Elf Stellen prueften mit nacktem startswith().
|
||||
„/app/media-boese/x" beginnt mit „/app/media", liegt aber ausserhalb."""
|
||||
"""Befund 25.07.2026: Elf Stellen prüften mit nacktem startswith().
|
||||
„/app/media-boese/x" beginnt mit „/app/media", liegt aber außerhalb."""
|
||||
import tasks
|
||||
|
||||
assert tasks.unter_wurzel("/app/media", "/app/media") is True
|
||||
@@ -155,7 +155,7 @@ def test_unter_wurzel_faellt_nicht_auf_praefix_namen_herein():
|
||||
assert tasks.unter_wurzel("/etc/passwd", "/app/media") is False
|
||||
assert tasks.unter_wurzel("", "/app/media") is False
|
||||
assert tasks.unter_wurzel("/app/media", "") is False
|
||||
# Wurzel mit Schrägstrich am Ende darf nichts aendern
|
||||
# Wurzel mit Schrägstrich am Ende darf nichts ändern
|
||||
assert tasks.unter_wurzel("/app/media/movies", "/app/media/") is True
|
||||
|
||||
|
||||
@@ -163,7 +163,7 @@ def test_zielbasis_lehnt_praefix_ausbruch_ab():
|
||||
import tasks
|
||||
|
||||
assert tasks._zielbasis("/app/media/movies", "bluray") == "/app/media/movies"
|
||||
# Ausbruch per Praefix-Namen faellt auf den Standard zurueck
|
||||
# Ausbruch per Praefix-Namen fällt auf den Standard zurück
|
||||
assert tasks._zielbasis("/app/media-boese", "bluray") != "/app/media-boese"
|
||||
assert tasks._zielbasis("/etc", "bluray") != "/etc"
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
"""Tests fuer _original_aufheben — der Pfad, der am 25.07.2026 die Platte fuellte.
|
||||
"""Tests für _original_aufheben — der Pfad, der am 25.07.2026 die Platte füllte.
|
||||
|
||||
Der Kern: Es wird NICHT vorhergesagt, ob umgehaengt werden kann, sondern
|
||||
os.rename versucht. Die alte Fassung verglich st_dev und lag damit falsch —
|
||||
auf der Rippy-VM sind st_dev von /app/temp und /app/media identisch (2050),
|
||||
os.rename scheitert zwischen ihnen trotzdem mit EXDEV, weil der Kernel den
|
||||
Mount vergleicht und nicht das Geraet. Die Platzpruefung wurde deshalb
|
||||
uebersprungen und shutil.move kopierte 75 GB bei 37 GB frei.
|
||||
Mount vergleicht und nicht das Gerät. Die Platzpruefung wurde deshalb
|
||||
übersprungen und shutil.move kopierte 75 GB bei 37 GB frei.
|
||||
"""
|
||||
|
||||
import errno
|
||||
@@ -46,7 +46,7 @@ def _lege_rohdaten_an(tmp_path, groesse=2048):
|
||||
|
||||
|
||||
def test_umhaengen_wenn_derselbe_mount(tmp_path, fake_db):
|
||||
"""Der gute Fall: rename klappt, nichts wird kopiert, kein Platz noetig."""
|
||||
"""Der gute Fall: rename klappt, nichts wird kopiert, kein Platz nötig."""
|
||||
raw, final = _lege_rohdaten_an(tmp_path)
|
||||
|
||||
tasks._original_aufheben("job-1", raw, final)
|
||||
@@ -58,9 +58,9 @@ def test_umhaengen_wenn_derselbe_mount(tmp_path, fake_db):
|
||||
|
||||
|
||||
def test_bei_exdev_und_zu_wenig_platz_wird_nur_gewarnt(tmp_path, fake_db, monkeypatch):
|
||||
"""Der Fall, der die Platte fuellte: rename geht nicht, Platz reicht nicht.
|
||||
"""Der Fall, der die Platte füllte: rename geht nicht, Platz reicht nicht.
|
||||
|
||||
Vorher lief hier eine Vollkopie an, weil die st_dev-Pruefung „gleiches
|
||||
Vorher lief hier eine Vollkopie an, weil die st_dev-Prüfung „gleiches
|
||||
Dateisystem" meldete und die Platzpruefung deshalb ausblieb.
|
||||
"""
|
||||
raw, final = _lege_rohdaten_an(tmp_path)
|
||||
|
||||
@@ -113,7 +113,7 @@ def test_parse_msg_trennt_code_und_klartext():
|
||||
|
||||
def test_parse_msg_liest_die_uhd_fehlermeldung():
|
||||
"""3303 ist der Befund, um den es beim ganzen KEYDB-Thema geht: das
|
||||
Laufwerk laeuft im LibreDrive-Modus, MakeMKV kennt nur den Schluessel
|
||||
Laufwerk läuft im LibreDrive-Modus, MakeMKV kennt nur den Schluessel
|
||||
DIESER Pressung nicht. Ohne diese Zeile im Log raet der Commander."""
|
||||
assert parse_msg(
|
||||
'MSG:3303,16777216,0,"The volume key is unknown for this disc - video can\'t be decrypted","The volume key is unknown for this disc - video can\'t be decrypted"'
|
||||
@@ -125,9 +125,9 @@ def test_parse_msg_liest_die_uhd_fehlermeldung():
|
||||
|
||||
|
||||
def test_parse_msg_schneidet_meldungen_mit_komma_nicht_ab():
|
||||
"""DER Grund fuer die Regex (Stand 25.07.2026): vorher stand hier
|
||||
"""DER Grund für die Regex (Stand 25.07.2026): vorher stand hier
|
||||
line.split(",", 4)[3]. Das schnitt jede Meldung ab, die selbst ein Komma
|
||||
enthaelt — und MakeMKV schreibt solche laufend. Im Log stand dann nur noch
|
||||
enthält — und MakeMKV schreibt solche laufend. Im Log stand dann nur noch
|
||||
ein Satzfragment, das mehr verwirrt als hilft."""
|
||||
zeile = (
|
||||
'MSG:3025,0,3,"Title #1 has length of 12 seconds, which is less than '
|
||||
@@ -142,7 +142,7 @@ def test_parse_msg_schneidet_meldungen_mit_komma_nicht_ab():
|
||||
|
||||
|
||||
def test_parse_msg_ignoriert_fremde_zeilen():
|
||||
# Alles ausser MSG muss None liefern, sonst landet Fortschritts-Rauschen
|
||||
# Alles außer MSG muss None liefern, sonst landet Fortschritts-Rauschen
|
||||
# (PRGV kommt mehrmals pro Sekunde) als Log-Eintrag in der Datenbank.
|
||||
assert parse_msg("PRGV:100,32768,65536") is None
|
||||
assert parse_msg('DRV:0,2,999,12,"BD-RE ASUS BW-16D1HT","AKIRA","/dev/sr0"') is None
|
||||
@@ -188,8 +188,8 @@ def test_abcde_config_enthaelt_zielverzeichnis():
|
||||
|
||||
|
||||
def test_preset_fuer_nimmt_das_preset_des_disc_typs():
|
||||
# Befund 25.07.2026: vorher galt EIN Preset fuer alles — eine 4K-UHD wurde
|
||||
# damit auf 1080p heruntergerechnet, und beim ersten echten UHD-Rip waere
|
||||
# Befund 25.07.2026: vorher galt EIN Preset für alles — eine 4K-UHD wurde
|
||||
# damit auf 1080p heruntergerechnet, und beim ersten echten UHD-Rip wäre
|
||||
# die 4K-Aufloesung still verlorengegangen.
|
||||
from ripping import preset_fuer
|
||||
|
||||
@@ -206,13 +206,13 @@ def test_preset_fuer_nimmt_das_preset_des_disc_typs():
|
||||
|
||||
def test_preset_fuer_faellt_auf_das_allgemeine_preset_zurueck():
|
||||
# Bestandsinstallationen kennen die drei neuen Felder nicht. Solange der
|
||||
# Nutzer sie nicht speichert, MUSS sich sein Verhalten nicht aendern.
|
||||
# Nutzer sie nicht speichert, MUSS sich sein Verhalten nicht ändern.
|
||||
from ripping import preset_fuer
|
||||
|
||||
alt = {"transcodePreset": "HQ 1080p30 Surround"}
|
||||
assert preset_fuer("uhd", alt) == "HQ 1080p30 Surround"
|
||||
assert preset_fuer("dvd", alt) == "HQ 1080p30 Surround"
|
||||
# Leere Zeichenkette zaehlt als "nicht gesetzt" (leeres Select-Feld im UI)
|
||||
# Leere Zeichenkette zählt als "nicht gesetzt" (leeres Select-Feld im UI)
|
||||
assert preset_fuer("uhd", {"transcodePresetUhd": " ", "transcodePreset": "X"}) == "X"
|
||||
|
||||
|
||||
@@ -269,7 +269,7 @@ def test_handbrake_prueft_abbruch_bei_jeder_zeile_nicht_nur_bei_fortschritt():
|
||||
|
||||
assert ergebnis["status"] == "cancelled"
|
||||
assert prozess.getoetet is True
|
||||
# Zwei Scan-Zeilen genuegten — es musste NICHT auf eine Encode-Zeile gewartet
|
||||
# Zwei Scan-Zeilen genügten — es musste NICHT auf eine Encode-Zeile gewartet
|
||||
# werden. Genau das war der Fehler.
|
||||
assert len(gesehen) == 2
|
||||
|
||||
@@ -279,16 +279,16 @@ def test_komprimieren_fuer_globaler_schalter_schlaegt_alles():
|
||||
|
||||
assert komprimieren_fuer("uhd", {"transcodeEnabled": False}) is False
|
||||
assert komprimieren_fuer("dvd", {"transcodeEnabled": False}) is False
|
||||
# Standard ist AN - Bestandsinstallationen aendern ihr Verhalten nicht
|
||||
# Standard ist AN - Bestandsinstallationen ändern ihr Verhalten nicht
|
||||
assert komprimieren_fuer("bluray", {}) is True
|
||||
assert komprimieren_fuer("bluray", None) is True
|
||||
|
||||
|
||||
def test_komprimieren_fuer_einzelner_disc_typ_abwaehlbar():
|
||||
"""Der Grund dafuer (Befund 25.07.2026): 4K-HEVC brauchte auf der Rippy-VM
|
||||
"""Der Grund dafür (Befund 25.07.2026): 4K-HEVC brauchte auf der Rippy-VM
|
||||
gemessene 28-55 Stunden, weil die CPU kein AVX2 hat. Vorher gab es nur
|
||||
einen globalen Schalter - wer 4K verlustfrei behalten und DVDs trotzdem
|
||||
schrumpfen wollte, hatte keine Moeglichkeit."""
|
||||
schrumpfen wollte, hatte keine Möglichkeit."""
|
||||
from ripping import PRESET_KEINE, komprimieren_fuer
|
||||
|
||||
nur_uhd_aus = {"transcodePresetUhd": PRESET_KEINE,
|
||||
@@ -299,20 +299,20 @@ def test_komprimieren_fuer_einzelner_disc_typ_abwaehlbar():
|
||||
|
||||
|
||||
def test_preset_fuer_gibt_den_reservewert_nie_als_preset_zurueck():
|
||||
"""Sonst bekaeme HandBrake `--preset keine` und wuerde scheitern. Wer ueber
|
||||
"""Sonst bekaeme HandBrake `--preset keine` und würde scheitern. Wer über
|
||||
"Neu komprimieren" ausdruecklich doch komprimieren will, soll ein
|
||||
brauchbares Preset bekommen statt eines Fehlers."""
|
||||
from ripping import DEFAULT_HB_PRESET, PRESET_KEINE, preset_fuer
|
||||
|
||||
assert preset_fuer("uhd", {"transcodePresetUhd": PRESET_KEINE,
|
||||
"transcodePreset": "HQ 1080p30 Surround"}) == "HQ 1080p30 Surround"
|
||||
# Auch ohne allgemeines Preset kommt ein gueltiger Name heraus
|
||||
# Auch ohne allgemeines Preset kommt ein gültiger Name heraus
|
||||
assert preset_fuer("uhd", {"transcodePresetUhd": PRESET_KEINE}) == DEFAULT_HB_PRESET
|
||||
|
||||
|
||||
def test_reservewert_kollidiert_mit_keinem_echten_preset():
|
||||
"""Gegengeprueft gegen alle 90 Presets aus `HandBrakeCLI --preset-list`
|
||||
im Worker-Image (25.07.2026) - keines heisst so."""
|
||||
im Worker-Image (25.07.2026) - keines heißt so."""
|
||||
from ripping import PRESET_KEINE
|
||||
|
||||
assert PRESET_KEINE == "keine"
|
||||
|
||||
Reference in New Issue
Block a user