docs: SAVEPOINT v4.0-rc11

Der Schalter, den es nicht gibt — und vierzehn weitere Funde aus demselben
Rundgang. Jeder mit der Messung, an der er haengt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-08-30 15:11:08 +02:00
co-authored by Claude Opus 5
parent d3e86d2641
commit 9050fea3f0
+219 -1
View File
@@ -1,6 +1,224 @@
# SAVEPOINT — Rippy
## Aktueller Stand: v4.0-rc10makemkvcon ueberlebt Rippy nicht mehr (29.08.2026)
## Aktueller Stand: v4.0-rc11der Schalter, den es nicht gibt (30.08.2026)
> **Ein falscher Kommandozeilen-Schalter kostete einen fertigen 16,5-GB-Rip —
> und HandBrake meldete es als Erfolg.** Dazu vierzehn weitere Funde aus
> demselben Rundgang. 977 Tests gruen.
### Der Befund
Spartacus Disc 2, eine Blu-ray mit Kratzern. MakeMKV sichert 1 von 2 Titeln,
die Kompression startet — und ist in derselben Sekunde vorbei:
```
13:18:57 Kompression gestartet (1 Datei, Preset 'H.265 VCN 1080p')
13:18:57 Kompression fehlgeschlagen: HandBrake endete mit Code 0
```
Nachgestellt mit genau der Befehlszeile, die Rippy baute:
```
unknown option (--audio-codec)
HandBrake has exited. $? = 0
```
**Den Schalter `--audio-codec` gibt es bei HandBrakeCLI nicht** — er heisst
`-E` / `--aencoder`. Und ein unbekannter Schalter ist fuer HandBrake kein
Fehler: Rueckgabewert **0**. Rippy sah nur „Code 0" und keine Datei, riet auf
„Zielordner nicht beschreibbar" — und schickte die Suche in die falsche
Richtung.
Ein Test hat den Fehler festgeschrieben statt ihn zu finden:
`assert "--audio-codec" in cmd`. Ein Kommandozeilen-Schalter ist eine externe
Schnittstelle (Regel D) — er gehoert am echten Programm gemessen.
### Die Kompression
* **`--aencoder` statt `--audio-codec`.** Am mitgelieferten HandBrakeCLI
1.11.2 gemessen, danach mit einem 5-Sekunden-Encode auf der echten
Roh-Datei gegengeprueft.
* **HandBrakes letzte Worte werden aufgehoben** (12 Zeilen gepuffert, 4 in der
Meldung). Vorher wurde jede Zeile weggeworfen, die kein Fortschritt war —
bei Rueckgabewert 0 blieb damit gar keine Auskunft uebrig. Dazu eine eigene
Erkennung fuer `unknown option (...)`, die VOR allen anderen greift.
* **Ein `ü` im Pfad toetete die Kompression.** HandBrake schreibt zwei
Kodierungen in denselben Strom — denselben Pfad einmal als UTF-8, einmal als
CP850. In CP850 ist `ü` das Byte 0x81, und das ist in cp1252 (was
`text=True` auf einem deutschen Windows waehlt) **undefiniert**:
```
UnicodeDecodeError: charmap codec can't decode byte 0x81 in position 785
```
Betroffen war jeder Film mit „Glück", „Tür", „München", „Über", „Grün" —
dazu `ì`, `Å`, `É`, `Ø`. Die Entscheidung liegt jetzt als `HB_LESEN` im
gemeinsamen `rip/handbrake_aufruf.py`, weil `caps.py` HandBrake ebenfalls
aufruft. Bewusst NICHT binaer wie bei makemkvcon: HandBrake trennt seine
Fortschrittszeilen mit CR.
### Die Rohdaten
* **`F:\` wurde zu `F:`.** `rohdaten.kandidaten` strich den Schluss-Trenner ab
und verband mit dem Rest weiter. `F:` ohne Trenner ist unter Windows der
AKTUELLE Ordner auf Laufwerk F, nicht dessen Wurzel:
```
verbinden("F:", job_id) -> F:1aa41fef-... isdir: False
verbinden("F:\", job_id) -> F:\1aa41fef-... isdir: True
```
16,5 GB Rohschnitt unsichtbar, und der Wiederholen-Dialog bot nur „Neu
rippen" an: Stunden am beschaedigten Datentraeger fuer etwas, das dalag. Die
Falle steht woertlich im Kopf von `pfade.verbinden`.
* **Gesucht wurde unter der heutigen Einstellung, nicht unter der Wahl des
Jobs.** Der Rip-Dialog laesst pro Rip waehlen (seit v3.15); die Wahl steht in
`meta["work_dir"]`. Genau dafuer wurde `rohdaten.py` am 26.07. gebaut —
repariert wurde damals die Kandidatenliste, nicht der Aufrufer.
* **Zwei Speicher fuer dieselben Ordner.** Die Oberflaeche schreibt
`outputDir`/`workDir` in die Datenbank, `betrieb` liest
`storage.medien`/`storage.temp` aus der Konfigurationsdatei — und die
schreibt niemand. An der laufenden Instanz gemessen:
```
eingestellt E:\Rippy (beides)
angezeigt C:\Users\TobisPC\Videos\Rippy und ...\_arbeit
```
Neue Bruecke `betrieb.mit_einstellungen` — dieselbe Reihenfolge, die der
Worker seit jeher benutzt.
### Das Laufwerk
* **Rippy kannte den Grund und behielt ihn fuer sich.** Nach dem Rip mit 29
Lesefehlern und einem gescheiterten Auswurf beantwortete das Laufwerk nichts
mehr, was mit dem MEDIUM zu tun hat — die Geraete-Auskunft aber schon:
```
CreateFileW mit GENERIC_READ -> Win32-Fehler 1
IOCTL_STORAGE_CHECK_VERIFY2 -> Win32-Fehler 1
IOCTL_CDROM_DISK_TYPE -> Win32-Fehler 50
CreateFileW mit Zugriff 0 -> geht
IOCTL_STORAGE_QUERY_PROPERTY -> geht
```
Im UI stand eine vollstaendige Laufwerkskarte mit Modell und Seriennummer,
daneben „unknown" — und kein Rip startbar. Commander: *„jetzt erkennt rippy
die disk garnicht mehr (im log steht zwar erkannt, aber ein start des rips
ist nicht möglich)"*. Jetzt gibt es `ZUGRIFFS_GRUENDE` mit gemessenem
Klartext je Fehlernummer, der Grund reist im Laufwerks-Eintrag mit, und die
Wache schreibt ihn einmal je Wechsel ins Protokoll — samt „antwortet
wieder". Auch eine fehlgeschlagene Disc-Erkennung landet dort, statt nur auf
einer Konsole, die niemand sieht.
* **Der Linux-Treiber nannte ein unzugaengliches Laufwerk „empty"**, waehrend
Windows richtig „unknown" sagt. Dieselbe Lage, zwei Antworten. Angeglichen;
der Feld-Paritaetstest deckt das neue Feld mit ab.
* **ctypes ohne Typangaben.** `CreateFileW`, `DeviceIoControl` und
`CloseHandle` hatten weder `restype` noch `argtypes` — ctypes nimmt dann
32-Bit-`c_int` fuer einen 64-Bit-HANDLE, hin wie zurueck. Die Tuecke: Mit
`restype` aendert sich der Fehlerwert von -1 auf 0xFFFFFFFFFFFFFFFF, die
alte Pruefung haette stillschweigend aufgehoert zu greifen. Am echten
Laufwerk gegengeprueft, Fehlerpfad eingeschlossen.
* **Der teure Vor-Scan, den niemand las.** Bei jeder eingelegten Disc lief ein
`makemkvcon info` mit 120 s Zeitgrenze — auf einer Blu-ray 20 bis 120
Sekunden „Disc wird gelesen". Commander: *„das erkennen der disk dauert sehr
sehr lange. Das ging mal viel schneller."* Es ging schneller, weil der Zweig
unter Windows NIE lief (`shutil.which`, repariert am 28.08.). Und das
Ergebnis landete allein in `toc["tracks"]`, das niemand liest — der
Rip-Dialog holt seine Liste ueber `/devices/{id}/scan-tracks`, wenn sie
gebraucht wird.
### Drei Notbremsen
* **Endlosschleife vor jedem Rip.** `_frei_bytes` suchte den naechsten
vorhandenen Ordner selbst. `os.path.dirname("Q:\\")` gibt sich SELBST
zurueck — an einem freien Laufwerksbuchstaben gemessen, nach drei Runden
festgefahren. Ein Arbeitsverzeichnis auf einer abgezogenen Platte haette den
Job stumm haengen lassen, in `_platz_pruefen`, also VOR dem Rip.
`pfade.naechster_vorhandener` macht es seit V2-1 richtig; es war die ganze
Zeit da.
* **Laufwerks- und UNC-Wurzeln** bleiben in `naechster_vorhandener` jetzt
absolut — dieselbe Falle wie bei den Rohdaten, zweiter Fundort.
* **`rmdir /s /q` aus der Registry.** Das Aufraeum-Skript der Deinstallation
baut seinen Loeschbefehl aus `InstallLocation`, also aus dem `--ziel` beim
Installieren. Waere das eine Laufwerks-Wurzel, loeschte die Deinstallation
das Laufwerk; der noetige rstrip macht den Fall erst scharf. Nicht
beobachtet — aber nicht wiedergutzumachen.
### Lesefehler heissen jetzt Lesefehler
MakeMKV sicherte 1 von 2 Titeln, beendete sich mit 0, und Rippy schrieb „Rip
fertig". Dass ein Titel fehlt, stand nur in Zeilen, die niemand liest. Jetzt
gibt es eine Warnung nach dem Rip — auch und gerade dann, wenn er als Erfolg
endet — mit Abhilfe: reinigen, anderes Laufwerk. Und **die MSG-Nummer steht im
Protokoll**: MakeMKVs Texte sind uebersetzt, die Nummern nicht.
### Zwei Funde aus der Gegenprobe am laufenden Rippy
Die erste Fassung von rc11 war installiert, als der Commander meldete: *„nun
öffnen sich diverse fenster im hintergrund, gehen ganz kurz auf und dann
wieder zu. Das laufwerk hört auch einfach auf zu lesen."* Beides waren
Altlasten, die erst jetzt sichtbar wurden.
**Die aufblitzenden Fenster.** Prozesserzeugung 20 Sekunden lang mitgeschnitten:
```
14:54:40 timeout.exe timeout 4 ls -d C:\Users\...\d7ee6c06-...
14:54:40 WindowsTerminal.exe
```
`rohdaten.pruefen` fragt „gibt es dieses Verzeichnis?" mit `timeout N ls -d`.
Unter Linux ist das genau richtig: `os.path.isdir` kann an einem toten
CIFS-Mount im Kernel haengen (Zustand D, 26.07.2026), ein Kind-PROZESS laesst
sich abbrechen. Unter Windows ist es dreifach falsch — `timeout.exe` gibt es
dort, sie wartet aber nur Sekunden ab und kennt weder `ls` noch `-d`; sie
braucht eine Konsole, und die reisst Windows auf; und ihr Rueckgabewert ist
nie 0, also lautete die Antwort **„weg" fuer jedes Verzeichnis**. Rohdaten
waren unter Windows grundsaetzlich unsichtbar — der zweite, tiefere Grund fuer
„Auf der Platte liegt zu diesem Job nichts (mehr)". Neu: `nativ_nachsehen()`,
und dieselbe Absicherung fuer die zwei gleichartigen Aufrufe in `mounts.py`.
**Das Laufwerk hoert auf zu lesen.** Aus dem Protokoll:
```
12:49:52 bluray-Rip gestartet
12:50:09 [watcher] Laufwerk G: beantwortet keine Medien-Abfragen mehr
12:50:12 MSG 2003 SCSI-Fehler ILLEGAL REQUEST:INVALID FIELD IN CDB
12:50:12 MSG 5010 Das Oeffnen der Disk schlug fehl
12:50:12 makemkvcon endete mit Code 11
```
Der Waechter fragt alle drei Sekunden `device_info` ab — drei `CreateFileW`
plus IOCTLs auf ein Geraet, das waehrenddessen makemkvcon gehoert.
`_auto_prescan` haelt sich seit dem 29.08.2026 an die Regel *„Es gibt keinen
Grund, waehrend eines Rips zu scannen"*; die Laufwerksabfrage tat es nicht.
Jetzt gilt waehrend eines Rips der letzte bekannte Stand — das ist keine
Notluege, denn am Laufwerk aendert sich in der Zeit nichts.
Sichtbar wurden beide erst durch die neue Protokollzeile aus demselben
Rundgang. Ein Fehler, der sich meldet, sieht aus wie ein neuer.
### Geprueft, nichts gefunden
FLAC samt MusicBrainz-Tags mit Umlauten (landen korrekt als UTF-8) · die
makemkvcon-Aufrufe in `schluessel.py` (haben `errors="replace"` bereits) · die
Laufwerksliste in `main.py` (rstrip nur fuer den Anzeigenamen) · die uebrigen
`dirname`-Schleifen (es gab nur die eine) · `unbekanntes_preset` (Wortlaut und
Rueckgabewert 2 gemessen — korrekt).
### Zwei Tests, die gelogen haben
* `assert "--audio-codec" in cmd` — schrieb den Fehler fest, statt ihn zu
finden.
* `lambda: {}` als Doppelgaenger fuer
`get_settings(key="ui", bei_fehler_leer=False)` — brach, sobald ein Aufrufer
einen Parameter benutzte, und zeigte dann auf den Code statt auf sich
selbst. Ein Doppelgaenger muss die Schnittstelle abbilden, die er ersetzt,
nicht nur den einen Aufruf, den es gerade gibt.
---
## v4.0-rc10 — makemkvcon ueberlebt Rippy nicht mehr (29.08.2026)
> **Ein liegengebliebener `makemkvcon` haelt das Laufwerk fest — jeder spaetere
> Rip scheitert dann.** 954 Tests gruen.