Files
rippy/SAVEPOINT.md
T
2026-09-03 19:40:49 +02:00

5494 lines
281 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# SAVEPOINT — Rippy
## ÜBERGABE an die nächste Session (03.09.2026 spät — 5.6.1 gebaut und lokal installiert, NICHT veröffentlicht: Untertitel-Spuren wieder erkannt)
### Stand
**5.6.1 ist gebaut, am ausgepackten Paket bewiesen und auf DIESEM PC
installiert (03.09.2026 19:40, Installer-Exit 0 — Rippy 5.6.0 lief dabei mit fünf Prozessen ohne Rip oder Encode und wurde vom Installer beendet), NICHT im Kanal — dort liegt 5.6.0.** Der
Commander testet erst; auf sein Wort geht 5.6.1 in den Kanal (Weg wie bei
5.6.0, siehe die Übergabe darunter: ERST Setup und Blockmap, DANN
`latest.yml`, PATCH, anonym gegenmessen).
* Setup: `RippySetup-5.6.1.exe`, **131 566 691 Bytes**, SHA-256 `04806C8DF53A799FB38EE185E3FE49A8CA139D61CF6BAD041C39A4F6CAEF6ED4`
(Bau in `dist-setup-561/`, Kopie in `dist-setup/`).
* Paket-Smoke: `SMOKE: OK — fenster=geladen kern=pid:27472,node:24.18.1 datenbank=ok leine=gesetzt pong=ok version=5.6.1`.
* **450 Tests** in 45 Dateien, drei Typprüfungen, Bau
und Klick-Beweis (33 Schritte) grün.
* Commits: `6871734` (Fix, Test, Messung, Aenderungsnotizen, Version 5.6.1 — 17 Dateien) und dieser SAVEPOINT.
### Der Fund des Commanders (03.09.2026, 19:35): „die auswahl der untertitel ist weiterhin nicht drin"
Bild aus 5.6.0: Blu-ray „Digimon Adventure: Last Evolution Kizuna" (2020),
Titel 1 mit TON deu×2 und jpn×2, aber ohne UNTERTITEL-Chips. Gemessen
(Regel D) mit `makemkvcon64 -r --minlength=120 info disc:0` an der Disc in
G: — die volle Ausgabe liegt unter
`beweise/messungen/2026-09-03-makemkvcon-info-kizuna.txt` (375 Zeilen):
```
SINFO:1,1,1,6202,"Audio" SINFO:1,1,3,0,"deu" DTS-HD MA Surround 5.1 German
SINFO:1,3,1,6202,"Audio" SINFO:1,3,3,0,"jpn" DTS-HD MA Stereo Japanese
SINFO:1,5,1,6203,"Untertitel" SINFO:1,5,3,0,"deu" PGS German
SINFO:1,6,1,6203,"Untertitel" SINFO:1,6,3,0,"deu" PGS German (nur erzwungene) Attribut 22 = 6144
SINFO:1,7,1,6203,"Untertitel" SINFO:1,7,3,0,"deu"
```
**Ursache:** MakeMKV schreibt den Stream-Typ (Attribut 1) in seiner
Anzeigesprache. Deutsch eingestellt heißt es „Untertitel"; der Parser
(`kern/rip/parser.ts parseStreamInfo`) prüfte seit 26.07. auf das
englische „Subtitles" (damals an einem englisch eingestellten MakeMKV
gemessen). „Audio" heißt zufällig in beiden Sprachen gleich — deshalb
kamen die Tonspuren an, die Untertitel nicht. Die MakeMKV-Auswahlregel
(`spurauswahl.ts`) war nicht betroffen: MakeMKV nimmt alle Spuren mit, nur
der Dialog konnte sie nicht zeigen.
**Fix (5.6.1):** Der Typ-CODE im vierten Feld zählt — 6201 Video, 6202
Audio, 6203 Untertitel (`STREAM_CODE_*`, `StreamInfo.typCode`); das Wort
bleibt nur Notnagel für Ausgaben ohne Code. Test
`test/rip-parser-561.test.ts` mit den gemessenen Zeilen (deu×2/jpn×2 Ton,
deu×3 Untertitel; französisch „Sous-titres" per Code erkannt).
**Nebenbefund für später:** Attribut 22 trägt Flags — 6144 bei der Spur
„nur erzwungene", 0 bei der vollen; Attribut 30 die Beschreibung („PGS
German (nur erzwungene)"). Damit ließe sich die Option „einzelne Spuren
(voll / nur erzwungen)" bauen, die der Commander am 03.09. NICHT gewählt
hat („nur Extras, immer optional"). Heute: Chip „deu×3" = alle drei
kommen mit, die Vorwahl gilt je Sprache.
### Was am Gerät zu prüfen ist
* Dieselbe Disc noch einmal in den Dialog: UNTERTITEL-Chip „deu×3" muss
erscheinen; bei Rolle Extra dazu „BEIM ABSPIELEN AN".
* Alles aus der 5.6.0-Übergabe darunter (zwei Filme je Disc, Vorwahl im
Spieler, Restzeit, Hell-Modus mit Postern, Rettungs-Abbild, Automatik,
Benachrichtigung, Datenbank-Befund).
## Übergabe vom 03.09.2026 abends (5.6.0 — veröffentlicht im Tag `aktuell`; überholt durch 5.6.1)
### Wo die nächste Session startet
* **Arbeitsort:** `F:\Coding Stuff\rippy`, Branch `worktree-windows-electron`
(Spitze siehe `git log`). v5-Code in `rippy-windows/`; `main` ist reines
Docker-Rippy.
* **Bau:** wie in BAUEN.md — `npm install` + `node node_modules/electron/install.js`,
Playwright mit `PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1`, `vendor/` aus
`%LOCALAPPDATA%\Rippy\tools`.
* **Die drei Läufe:** `npm test` (446 Tests in 44 Dateien),
`npm run smoke` (Gerüst), `npm run klick` (33 Klick-Schritte, Bilder
`beweise/klick/*.png`, darunter `07-hell-modus.png` und `02-dialog-vorwahl.png`).
Alle drei in dieser Session grün.
* **Release veröffentlichen — für 5.6.0 ERLEDIGT (03.09. abends, Commander:
„Dann Deploy und aktualisierung bei mir aufm lokalen pc“). Der Weg fürs
nächste Mal, wieder NUR nach seinem Wort:** die
Dateien im Gitea-Tag `aktuell` ERSETZEN (`RippySetup-5.6.0.exe`,
`.blockmap`, `latest.yml`; `w0.yml` bleibt liegen). Dateien liegen in
`rippy-windows/dist-setup/` (Kopie) und `rippy-windows/dist-setup-560/`
(Original). Weg wie bei 5.5.0 (siehe unten „Veröffentlicht"): Token
`F:\Coding Stuff\Gittea Token\rippy.txt` über das PowerShell-Werkzeug
lesen (nie ausgeben), LAN-Adresse
`http://192.168.178.153:3000/api/v1/repos/Hitonabi/rippy/releases/1`,
ERST Setup und Blockmap hochladen, DANN `latest.yml` löschen (204) und
neu hochladen (201), Release-Name/Notizen PATCHen, anonym über
`https://git.tobisniceshomelab.ddnsfree.com/Hitonabi/rippy/releases/download/aktuell/`
gegenmessen. Das PowerShell-Werkzeug blockt Befehle mit `Remove-Item`
Skript ohne schreiben. **Im Kanal liegt 5.6.0.**
### Stand
**5.6.0 ist gebaut, am ausgepackten Paket bewiesen, auf DIESEM PC
installiert (03.09.2026 19:17, Installer-Exit 0, Rippy lief dabei nicht) und
seit dem Abend des 03.09.2026 IM KANAL `aktuell`.**
**Veröffentlicht (03.09.2026 abends, Commander: „perfekt, danke. Dann
Deploy und aktualisierung bei mir aufm lokalen pc“):** Release 1, Assets 40
(`RippySetup-5.6.0.exe`, 131 566 582 Bytes, HTTP 201), 41 (Blockmap,
137 537 Bytes, 201), 42 (`latest.yml`, 8 273 Bytes, 201 — das alte Asset 39
vorher gelöscht, 204); Release umbenannt in „Rippy 5.6.0 (Update-Kanal
aktuell)“ mit den 5.6.0-Notizen (PATCH 200). **Anonym extern
gegengemessen:** `latest.yml` HTTP 200 und Byte für Byte gleich dem Bau
(`cmp`), Setup HTTP 200 mit Content-Length 131 566 582, Blockmap 200 mit
137 537. Lokal noch einmal geprüft: `%LOCALAPPDATA%\Programs\Rippy\Rippy.exe`
Version 5.6.0.0 (Stand 19:16), kein Rippy-Prozess lief. Der zweite PC holt
sich 5.6.0 über die Update-Funktion (5.5.0 → 5.6.0).
* Setup: `RippySetup-5.6.0.exe`, **131 566 582 Bytes**, SHA-256
`4392CA5D36E165491085810D4181B22594BB29D62334ED1CDD0D2171264DA1FD` (Bau in `dist-setup-560/`).
* Paket-Smoke: `SMOKE: OK — fenster=geladen kern=pid:15172,node:24.18.1 datenbank=ok leine=gesetzt pong=ok version=5.6.0`.
* **446 Tests** in 44 Dateien (5.5.0: 413), drei
Typprüfungen und Bau grün.
* Klick-Beweis: 33 Schritte grün, Ausgang 0 — neu gegenüber 5.5.0:
beide Titel auf Hauptfilm → keine Vorwahl; Titel 1 auf Extra → „BEIM
ABSPIELEN AN" erscheint, `deu` gewählt; zwei „anderer Film …"-Knöpfe,
Suchfeld öffnet; Kompressions-Karte zeigt „noch … s" (ETA aus der
nachgebauten HandBrake-Zeile), die Kachel zeigt KEINEN vollen Block mehr,
sondern „Kompression fertig — das Ergebnis steht im Verlauf"; Hell-Modus
über Einstellungen → Allgemein (`html[data-design=hell]`, Bild), zurück
auf Dunkel; Vorgangs-Leiste in der Kachel (TITEL LESEN ✓, RIPPEN aktiv,
KOMPRIMIEREN/ABLEGEN offen, keine „PHASE x/4"-Texte) und in der
Kompressions-Karte (KOMPRIMIEREN aktiv).
* Commits dieser Session: `f5dab5d` (Kern, Fenster, Tests, Klick-Beweis, Aenderungsnotizen, Version 5.6.0 — 59 Dateien) und dieser SAVEPOINT.
### Was 5.6.0 enthält — die sechs Punkte des Commanders vom 03.09.2026
Seine Worte: „Bei Kompression ETA anzeigen / Wenn ein Job abgeschlossen
wird, wird Kompression beim Hero-Header und im Kompression angezeigt — nur
nötig bei Kompression, oder? / Beim Scrollen in den Einstellungen muss die
Bar oben sinnvoll mitscrollen / Untertitel müssen separat auswählbar sein
(das hatten wir besprochen) / White Mode / Verhalten, wenn eine Disc mehr
als einen Film hat, besprechen." Seine Entscheide in den Rückfragen: **nur
in der Kompressions-Karte; Untertitel-Vorwahl je Inhalt — „NUR für Extras
und IMMER OPTIONAL", weiche Spur, nie eingebrannt; je Titel eigener Film
im Dialog; Roh-Ordner UND Dateien tragen den Filmnamen.**
1. **Restzeit (ETA):** `kern/komprimieren/handbrake.ts etaAusZeile` liest
HandBrakes eigene Klammer — Formatstring aus dem Binary (1.11.2):
`Encoding: task %d of %d, %.2f %% (%.2f fps, avg %.2f fps, ETA %02dh%02dm%02ds)`.
`KompressionStand.restS` → Kompressions-Karte „noch ~12 min"
(`bausteine.restText`). In den ersten Sekunden ohne Klammer: „Restzeit
wird berechnet …".
2. **Kachel-Kurzzeile:** `LaufwerkKachel` zeigt nach dem Rip nur noch eine
Zeile (läuft / Platz n / fertig → Verlauf / Fehler bleibt sichtbar, R4).
3. **Kopfzeile bleibt stehen:** `App.tsx` header `sticky top-0 z-40` mit
`bg-buehne/85 backdrop-blur-md`; die Sektionswahl der Einstellungen
sitzt darunter (`lg:top-[76px]`).
4. **Untertitel-Vorwahl je Extra:** `RipAuftragTitel.untertitelVorwahl`
(`string` = diese Sprache vorgewählt, `null` = keine, fehlend = Rippys
Regel) → `RohDatei.untertitelVorwahl``auftrag.ts sprachwahl` stellt
sie nach vorn und setzt `untertitelStandard` (HandBrake
`--subtitle-default=1`, weiche Spur). Dialog: Select „BEIM ABSPIELEN AN"
nur bei Rolle Extra, Vorgabe „Rippys Regel (deu)".
5. **Mehrere Filme je Disc:** `RipAuftragTitel.film` (MetaVorschlag) aus dem
Dialog („anderer Film …" mit TMDb-Suche über `metadaten-suchen`);
`kern/ablauf/filme.ts filmGruppen` teilt am Rip-Ende: Titel eines
anderen Films ziehen per `renameSync` in einen eigenen Roh-Ordner
(`rohOrdnerName`), bekommen eigenen Vorgang (`<vorgang>-2`, `-3` …) und
eigenen Kompressions-Auftrag mit voller Zuordnung
(`erkennung.zuordnungAus` → TMDb-Details; Notnagel
`zuordnungAusVorschlag`). Behält KEIN Titel die Disc-Zuordnung, wird die
erste Gruppe zum Hauptvorgang. Die Vorschau im Dialog zeigt je Film
seine Zeile.
6. **Ordner und Dateien mit Filmnamen:** Roh-Ordner
`roh\Akira (1988)` bzw. `roh\Spartacus - Staffel 1 Disc 2`, bei
Wiederholung „(2)"; ohne Zuordnung weiter der Zeitstempel. Dateien
(`kern/ablage/dateinamen.ts`): `Akira (1988).mkv`, zwei Fassungen
`… - Fassung 1.mkv`, Extras `… - Extra 01.mkv` in `extras\`; Folgen
behalten den Namen bis zur Episoden-Zuordnung. Der Vorgangs-Name
(Zeitstempel) bleibt die Kennung — `Vorgang.rohOrdner` trägt den Pfad.
7. **Hell-Modus:** Einstellung `design` (dunkel / hell / system);
`App.tsx` setzt `data-design` auf `<html>`, `stil.css` überschreibt die
Farb-Tokens (`:root[data-design="hell"]`: warmes Papier, dunkleres Gold,
Tinte statt Weiß). Neuer Token `--color-tinte` ersetzt alle
`white/`-Alpha-Klassen (`bg-tinte/…`, `border-tinte/…`).
8. **Vorgangs-Leiste** (Commander mitten in der Session: „Phase 14 plus
Komprimierung — zu einem machen, der Phasen-Balken gefällt mir"; sein
Ja zu Variante A): `gemeinsam/vorgangsleiste.ts` (pur: Stationen
Titel lesen → [Rettungs-Abbild] → Rippen → Komprimieren → Ablegen,
`stationsZustand`, `leisteAusRip/-Vorgang/-Kompression`) und
`fenster/uebersicht/VorgangsLeiste.tsx` (Segment-Balken in der aktiven
Station, kompakt als Punkte). Kachel: Details der Laufwerks-Stationen,
nach dem Rip nur der Stand; Kompressions-Karte: Stationen 34 mit
Restzeit; Verlauf und Warteschlange: Punkte. Die Texte „PHASE x/4"
sind weg (`PHASEN_MONO`).
### ⚠ Am Gerät noch zu messen (nichts davon war in dieser Session möglich)
* **Zwei Filme auf einer echten Disc** (Double Feature): Roh-Umzug per
`renameSync` im Arbeitsordner, zwei Vorgänge, zwei Zielordner mit Poster
und NFO — Unit-Tests decken die Gruppen-Logik (`filme.test.ts`), nicht
den Lauf mit MakeMKV.
* **Untertitel-Vorwahl im Spieler:** Ob Jellyfin/VLC die als Default
markierte Spur bei einem Extra wirklich einblendet — HandBrake-Flag ist
am 01.09. gemessen (setzt default UND forced), das Abspielen nicht.
* **Restzeit an einem echten Encode** (die Klammer erscheint nach den
ersten Sekunden; im Klick-Beweis kommt sie vom Nachbau).
* **Hell-Modus mit Poster-Kulisse und echten Postern.**
* Weiter offen aus 5.5.0: Rettungs-Abbild an Spartacus Disc 2, Automatik an
echter Disc, Benachrichtigung live, Datenbank-Befund vom 01.09. (siehe
5.5.0-Übergabe unten).
### Bekannte Schwächen, bewusst in Kauf genommen
* Ein Roh-Ordner mit Filmnamen ist auch bei „Neu komprimieren" derselbe —
die Regel „ein Ordner je Vorgang" gilt weiter, nur der Name ist lesbar.
* Der zweite Film einer Disc bekommt Staffel/Folge nie (nur Filme, keine
Serien) und keinen Fingerprint (die Disc als Ganzes ist beim Hauptvorgang
gemerkt).
* Ohne Arbeitsordner schreibt HandBrake weiterhin direkt ins Ziel — bei
„Neu komprimieren" in denselben Ordner überschreibt der neue Encode die
alte Datei gleichen Namens erst am Ende des Laufs (Umzug), aber ohne
Arbeitsordner während des Laufs. Wer wiederholt, sollte den Arbeitsordner
gesetzt haben (5.5.0-Empfehlung).
* Das Fenster startet mit dunklem Hintergrund (`backgroundColor` in
`haupt/fenster.ts`) und schaltet erst mit den Einstellungen auf Hell —
ein kurzes Aufblitzen beim Start.
## Übergabe vom 02.09.2026 abends (5.5.0 — veröffentlicht im Tag `aktuell`; überholt durch 5.6.0)
### Wo die nächste Session startet
* **Arbeitsort:** `F:\Coding Stuff\rippy`, Branch `worktree-windows-electron`
(Spitze siehe `git log`). v5-Code in `rippy-windows/`; `main` ist reines
Docker-Rippy.
* **Bau:** wie in BAUEN.md — `npm install` + `node node_modules/electron/install.js`,
Playwright mit `PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1`, `vendor/` aus
`%LOCALAPPDATA%\Rippy\tools`.
* **Die drei Läufe:** `npm test` (413 Tests in 42 Dateien),
`npm run smoke` (Gerüst), `npm run klick` (29 Klick-Schritte durch
das echte Programm, jetzt auch durch die Erst-Einrichtung; Bilder
`beweise/klick/01…09*.png`). Alle drei in dieser Session grün.
* **Release veröffentlichen — für 5.5.0 ERLEDIGT (02.09. abends, Commander:
„veröffentlichen und installation lokal bei mir"). Der Weg fürs nächste
Mal, wieder NUR nach seinem Wort:** die Dateien im Gitea-Tag `aktuell`
ERSETZEN (`RippySetup-5.5.0.exe`, `.blockmap`, `latest.yml`; `w0.yml`
bleibt liegen). Dateien liegen in `rippy-windows/dist-setup/` (Kopie)
und `rippy-windows/dist-setup-550/` (Original). Token:
`F:\Coding Stuff\Gittea Token\rippy.txt` (40 Hex; über das
PowerShell-Werkzeug lesen, nie ausgeben). Weg wie am 01.09.: LAN-Adresse
`http://192.168.178.153:3000/api/v1/…/releases/1`, `latest.yml` erst
löschen (204), dann hochladen (201), danach ANONYM über die externe
HTTPS-Adresse gegenmessen. **5.4.0 war nie im Kanal — 5.5.0 ersetzt sie
direkt; im Kanal liegt bis dahin 5.3.2.**
### Stand
**5.5.0 ist gebaut, am ausgepackten Paket bewiesen, auf DIESEM PC
installiert (17:50, Installer-Exit 0, Rippy lief dabei nicht) und seit dem
Abend des 02.09.2026 IM KANAL `aktuell`.**
**Veröffentlicht (02.09.2026 abends):** Release 1, Assets 37
(`RippySetup-5.5.0.exe`, 131 560 997 Bytes, HTTP 201), 38 (Blockmap,
137 360 Bytes, 201), 39 (`latest.yml`, 6 524 Bytes, 201 — das alte Asset 35
vorher gelöscht, 204); Release umbenannt in „Rippy 5.5.0 (Update-Kanal
aktuell)" mit den 5.5.0-Notizen (PATCH 200). **Anonym extern
gegengemessen** (`https://git.tobisniceshomelab.ddnsfree.com/Hitonabi/rippy/
releases/download/aktuell/…`): `latest.yml` HTTP 200 und Byte für Byte
gleich dem Bau (`cmp`), Setup HTTP 200 mit Content-Length 131 560 997,
Blockmap 200 mit 137 360. Die alten Setups 5.1.05.3.2 liegen wie bisher
weiter im Release; `w0.yml` (36) unangetastet. Der zweite PC bekommt 5.5.0
über die Update-Funktion. Der PowerShell-Wächter des Werkzeugs blockt
Befehle mit `Remove-Item` — das Upload-Skript kommt ohne aus. Der Commander wollte 5.4.0 vor
der Freigabe noch umgebaut haben (Clean Code, UI/UX, Übersicht, FirstRun)
— das ist 5.5.0; 5.4.0 wurde dadurch nie veröffentlicht.
* Setup: `RippySetup-5.5.0.exe`, **131 560 997 Bytes**, SHA-256
`8E55C7797BC41C6A153B584CF58556104A4D67F8A2A2590D570079CA719A6181` (Bau in `dist-setup-550/`).
* Paket-Smoke: `SMOKE: OK — fenster=geladen kern=pid:23276,node:24.18.1 datenbank=ok leine=gesetzt pong=ok version=5.5.0`.
* **413 Tests** in 42 Dateien (5.4.0: 353), drei
Typprüfungen und Bau grün.
* Klick-Beweis: 29 Schritte grün, Ausgang 0 — Speicher-Karte
(Arbeitsordner und Ablage getrennt, Fußleiste FREI) → keine Bibliothek →
Rippen → Dialog → drei Titel → Sprach-Chips → jpn statt deu →
„Hauptinhalt + Extras" → Rollen auf Folge → Staffel 1, ab Folge 3 →
Vorschau → **Platz-Prüfung** → RIPPT → „Kompression eingereiht" →
Kompressions-Karte → „Komprimiert und abgelegt" → Dateien `S01E03/S01E04`
unter `Season 01` → **Roh lokal im Arbeitsordner, Zwischenordner
geräumt** → **Verlauf****Bilanz** → Roh-Dateien-Karte → Kompression
ohne „Allgemeines Preset", Chips Deutsch/Englisch → Werkzeuge mit
Durchsuchen, Karten Automatik/Benachrichtigung/Allgemein/Update → Roh
löschen → Ereignisse → **zweiter Start `RIPPY_START_BEREICH=einrichtung`:
Willkommen vor 7 Poster-Spalten → Prüfung → Ordner → Qualität → Titel →
Meldungen → Automatik → „Los geht's"**.
* Commits dieser Session: `3dc6798` (Kern, Oberfläche, Tests, Klick-Beweis, Aenderungsnotizen, Version 5.5.0 — 67 Dateien) und dieser SAVEPOINT.
### Was 5.5.0 enthält — die Wünsche des Commanders vom 02.09.2026
Seine Worte (gekürzt): „Bevor wir das Update freigeben … CleanCode:
Separation of Concerns / UI/UX: MakeMKV-Header weg aus der Übersicht,
„Bereit"-Parameter sinnvoll, Allgemeines Preset weg, Dropdowns für Ton
und Untertitel, Programm → Allgemein oder eigener Update-Block,
Durchsuchen bei Werkzeugen / Bibliothek: brauchen wir die wirklich? /
Übersicht ist zu leer: freier Speicher auf Server und Arbeitsordner /
alles auch in den FirstRun / angenehme Fenstergröße je Monitor / weitere
Ideen her." Seine Entscheide in den Rückfragen: **Bibliothek weg, dafür
Verlauf in der Übersicht; Arbeitsordner lokal, Ablage auf dem Server;
Fußleiste ersetzen; Oberfläche UND Kern aufteilen; Poster-Grid im
FirstRun; aus ARM übernommen: Automatik mit Countdown, Protokoll-Datei +
Ordner öffnen, Benachrichtigung aufs Handy, Statistik; Serien-Merker,
Platz-Prüfung, Warteschlange pausieren; Rettungs-Abbild mit Nullen — aber
NUR, wenn Rippy wirklich Fehler sieht.** Nicht gewählt: Zeitfenster,
Oberflächen-Test, Backup-Weg als eigener Modus.
1. **Kern aufgeteilt** (`kern/index.ts` 1244 → Bootstrap + Router):
`kern/kontext.ts` (KernKontext: db, anFenster, hinweis/fehler,
Protokoll), `kern/dienste/einstellungen.ts`, `werkzeuge.ts`,
`laufwerke.ts`, `erkennung.ts`, `vorgaenge.ts`, `speicher.ts`,
`benachrichtigung.ts`, `automatik.ts`.
2. **Oberfläche aufgeteilt** (`fenster/App.tsx` 2686 → Rahmen):
`bausteine.tsx` (Gemeinsames), `dialoge/` (AendernDialog,
TitelWahlDialog), `uebersicht/` (LaufwerkKachel, KompressionsKarte,
SpeicherKarte, VerlaufKarte, Uebersicht), `einstellungen/` (Karten.tsx
je Karte eine Funktion, SprachAuswahl, Einstellungen),
`einrichtung/` (ErstEinrichtung, PosterHintergrund).
3. **Übersicht:** MakeMKV-Kopf weg (→ Einstellungen → MakeMKV, plus LED
SCHLÜSSEL); Speicher-und-Bilanz-Karte (Arbeitsordner/Ablage mit
Füllbalken, Discs/gesichert/abgelegt/je Disc); Verlauf (letzte sechs
Vorgänge, Ordner öffnen, Sprung zu Roh-Dateien); Kompressions-Karte mit
**Pausieren/Weiter**; Fußleiste = sechs LEDs (KERN, DATENBANK,
WERKZEUGE, SCHLÜSSEL, FREI n GB, Version/UPDATE) statt PID und Ping.
4. **Einstellungen:** Ablage (+ Arbeitsordner, Auswurf), Kompression
(Presets je Typ, **SprachAuswahl** als Chips + Dropdown in
Wunschreihenfolge, kein Allgemeines Preset), Roh-Dateien, **Automatik**
(an/aus, Countdown), Metadaten, MakeMKV (Schlüssel-Stand, Neu prüfen,
Aktualisieren, eigener Key), Medienserver, **Benachrichtigung**
(Discord-Webhook, Telegram, Testnachricht), **Allgemein** (Autostart,
Protokoll öffnen, Ablage/Arbeitsordner im Explorer, Kern/DB/Protokoll-
Pfade), **Update** (eigene Karte), Werkzeuge (**Durchsuchen …** mit
exe-Dialog). Die Einstellungs-Schlüssel neu: `arbeitsordner`,
`automatik`, `automatikSekunden`, `benachrichtigungen`,
`discordWebhook`, `telegramToken`, `telegramChatId`; `transcodePreset`
ist aus der erlaubten Liste raus.
5. **Arbeitsordner** (`einstellungen.arbeitsordner()`, Roh unter
`<arbeit>\roh`): `ablegen.ts` encodiert nach `<arbeit>\fertig\<id>` und
zieht danach in die Ablage (`nachzuziehen`), räumt den Zwischenordner.
`speicher.ts` misst mit `fs.statfsSync` (geht hoch bis zum vorhandenen
Elternpfad), Takt jede Minute, LED-Warnung unter 60/20 GB.
6. **Platz-Prüfung** (`gemeinsam/platz.ts`, EINE Rechnung): Dialog zeigt
das Urteil vor dem Klick und sperrt den Knopf bei „hart"; der Kern
urteilt beim Start (`index.ts ripMitPlatz`, auch für die Automatik).
7. **Automatik** (`automatik.ts`): ab 90 % Erkennung, nicht schon
gerippt, Video-Disc → Titel lesen → Countdown (Vorgabe 30 s) → Rip mit
Vorschlag; Kachel mit Sekunden, „Jetzt", „Stopp"; wer selbst klickt
(Rippen, Ändern, Zuordnung), beendet den Countdown. Unsicher = nur der
Grund.
8. **Serien-Merker** (Einstellung `serienMerker` JSON je `tv:<id>`): nach
jedem Serien-Rip merkt sich Rippy Staffel und nächste Folge; die
nächste Disc derselben Serie bekommt „ab Folge n" vorbelegt (nur, wenn
Disc-Nr unbekannt oder > 1).
9. **Rettungs-Abbild** (`kern/rip/rettung.ts`, Pipeline-Phase `rettet`):
nur nach GESEHENEN Lesefehlern (`fehlerGesehen`: Defekt-Wächter,
Lesefehler, Titelfehler) erscheint „Rettungs-Abbild …"; Sektor-Leser
1 MiB → 64 KiB → 2048 B mit je zwei Versuchen, Nullen für den Rest,
Bericht `<roh>\rettung.iso.rettung.txt`; danach derselbe Rip aus
`iso:<pfad>`. Der Kern lehnt `rettung-start` ohne gesehene Fehler ab.
10. **Benachrichtigung** (`benachrichtigung.ts`): Discord-Webhook (POST
JSON `{content, username}`), Telegram (`bot<TOKEN>/sendMessage`,
`{chat_id, text}`); bei Rip fertig, Kompression fertig, Fehler,
Automatik-Start; Fehlschlag = Ereignis-Zeile, nie ein Abbruch.
11. **Protokoll-Datei** (`gemeinsam/protokoll.ts`): Haupt und Kern
schreiben `<userData>\logs\rippy-JJJJ-MM-TT.log` (14 Tage). Der Kern
schreibt beim Start „Datenbank geöffnet: <Pfad>" — die Zeile, die am
01.09. gefehlt hat.
12. **Fensterlage** (`haupt/fensterlage.ts`): 80 % des Arbeitsbereichs,
mittig, mindestens 1100 × 720; gemerkt in `<Profil>\fenster-lage.json`,
nur wiederhergestellt, wenn sie auf einem heutigen Bildschirm liegt.
13. **Erst-Einrichtung:** acht Schritte (Willkommen, Prüfung mit
Durchsuchen für makemkvcon, Ordner mit Ablage + Arbeitsordner + frei,
Qualität mit Encodern, Empfehlungen und Sprach-Chips, Titel, Meldungen,
Automatik/Auswurf/Roh-Regel, Fertig) vor dem **Poster-Grid**
(`PosterHintergrund`: 7 Spalten, CSS `poster-lauf`, Platzhalter bis
zum TMDb-Schlüssel, dann `tmdb.beliebtePoster` über `poster-laden`).
14. **Nachrichten** (`gemeinsam/nachrichten.ts`): neu `speicher`,
`automatik`, `poster-vorschau`; Fenster → Kern `kompression-pause`,
`automatik-stopp`, `automatik-jetzt`, `benachrichtigung-testen`,
`poster-laden`, `rettung-start`, `speicher-laden`; `bibliothek` und
`bibliothek-laden` sind weg; `vorgaenge` trägt `statistik` und
`kompression.pausiert`; `RipStatus` trägt `rettungMoeglich`/`rettung`.
Brücke neu: `dateiWaehlen`, `ordnerOeffnen`, `protokollOeffnen`.
### ⚠ Der Datenbank-Befund — weiter ungeklärt
Unverändert aus der 5.4.0-Übergabe: `%APPDATA%\Rippy\rippy.db` war bis
21:13 am 01.09. Schema 1 vom 30.08. ohne Einstellungen, obwohl 5.3.2 hier
lief. Seit 5.5.0 steht der Datenbank-Pfad unter Einstellungen → Allgemein
UND als erste Zeile in der Protokoll-Datei (`Protokoll öffnen`). **Beim
Commander-Test als Erstes:** Kommt die Erst-Einrichtung wieder? Dann ist
die Roaming-DB die lebende, einmal neu einrichten — die Protokoll-Datei
sagt, welche Datei Rippy wirklich öffnet.
### Was am Gerät noch zu messen ist (nichts davon war in dieser Session möglich)
* **Rettungs-Abbild an Spartacus Disc 2** (echter Defekt): Der Leser
öffnet `\\.\Q:` über `fs/promises.open` — am echten Laufwerk NICHT
gemessen (Unit-Test mit nachgebautem Gerät: 7 Fälle). Zu prüfen: Dauer,
ob Windows das Gerät direkt lesen lässt, ob MakeMKV das `iso:`-Abbild
ohne Hänger rippt. Braucht doppelten Platz im Arbeitsordner.
* **Automatik an einer echten, sicher erkannten Disc** (Klick-Beweis hat
sie aus): Countdown in der Kachel, Stopp/Jetzt, Start mit Vorschlag.
* **Benachrichtigung live**: Discord/Telegram nach Doku gebaut, NICHT
live gemessen — der Testknopf unter Einstellungen → Benachrichtigung ist
dafür da.
* **Poster-Grid mit echtem TMDb-Schlüssel** (`movie/popular`, 2 Seiten):
im Klick-Beweis nur Platzhalter.
* **Ablage auf `\\192.168.178.62\rippy` mit lokalem Arbeitsordner** im
echten Betrieb (statfs auf UNC ist am 01.09. gemessen: 2216 GB frei).
* Weiter offen aus 5.4.0: MakeMKV-Key läuft Ende September ab (Rippy holt
neu); BU40N am USB.
### Messungen dieser Session (Regel D)
* `fs.statfsSync` (Node 24): Laufwerke, Ordner und UNC liefern
bavail/bsize/blocks; nicht vorhandener Pfad wirft ENOENT →
`vorhandenerPfad` geht hoch (siehe Kopf `kern/dienste/speicher.ts`).
* Discord „Execute Webhook": JSON `content` ≤ 2000 Zeichen; Telegram
`sendMessage`: JSON `chat_id`, `text` — beides Doku, nicht live.
* makemkvcon: `iso:<Pfad>` als Quelle (Manpage) — `parser.ts quelle()`
liefert es für `.iso`.
* Playwright: `_electron.launch` ein zweites Mal im selben Skript mit
anderem `env` funktioniert (Erst-Einrichtung im Klick-Beweis).
### Bekannte Schwächen, bewusst in Kauf genommen
* Die Platz-Schätzung rechnet pauschal 25 % für die Kompression — bei
„NICHT komprimieren" nur Roh + 2 GB Luft.
* Der Serien-Merker merkt sich je Serie EINEN Stand (die zuletzt gerippte
Disc) — zwei Staffeln durcheinander gerippt, gewinnt die letzte.
* Die Automatik startet nur mit Rippys Vorschlag (Rollen, Sprachen aus
der Einstellung) — wer je Titel anders will, klickt selbst.
* Das Rettungs-Abbild liest über Node, nicht über MakeMKVs Backup-Modus:
langsamer bei heilen Bereichen, dafür unter Rippys Kontrolle.
* `kern/dienste/vorgaenge.ts` und `erkennung.ts` sind noch groß; ein
weiterer Schnitt lohnt erst, wenn sich dort etwas ändert.
## Übergabe vom 01.09.2026 spät (5.4.0 — gebaut, nie veröffentlicht; überholt durch 5.5.0)
### Wo die nächste Session startet
* **Arbeitsort:** `F:\Coding Stuff\rippy`, Branch `worktree-windows-electron`
(Spitze siehe `git log`). v5-Code in `rippy-windows/`; `main` ist reines
Docker-Rippy.
* **Bau:** `npm install` + `node node_modules/electron/install.js` (npm 11
blockt Electrons Install-Skript). **Playwright ist seit 5.4.0 devDependency**
(Commander-Ja nach Regel B; MIT; beim Installieren
`PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1` setzen — Electron braucht keinen
Browser-Download). `vendor/` aus `%LOCALAPPDATA%\Rippy\tools` befüllen.
* **Die drei Läufe:** `npm test` (351 Tests), `npm run smoke` (Gerüst),
**`npm run klick`** (NEU: der Klick-Beweis durch das echte Programm mit
nachgebauten Werkzeugen, Bilder in `beweise/klick/`). Alle drei sind
in dieser Session grün gewesen.
* **Release veröffentlichen — NUR nach dem Wort des Commanders
(„Update freigegeben"):** die VIER Dateien im Gitea-Tag `aktuell`
ERSETZEN (`RippySetup-5.4.0.exe`, `.blockmap`, `latest.yml`; `w0.yml`
bleibt liegen). Dateien liegen in `rippy-windows/dist-setup/` (Kopie)
und `rippy-windows/dist-setup-540b/` (Original des zweiten Baus). Token:
`F:\Coding Stuff\Gittea Token\rippy.txt` (40 Hex; über das
PowerShell-Werkzeug lesen). Weg wie am 01.09.: LAN-Adresse
`http://192.168.178.153:3000/api/v1/…/releases/1`, `latest.yml` erst
löschen (204), dann hochladen (201), danach ANONYM über die externe
HTTPS-Adresse gegenmessen.
### Stand
**5.4.0 ist gebaut, am ausgepackten Paket bewiesen und auf DIESEM PC
installiert (22:23), NICHT im Kanal.** Der Commander wollte es ausdrücklich
so: erst lokal, dann testen, dann veröffentlichen.
* Setup: `RippySetup-5.4.0.exe`, **131 546 539 Bytes**, SHA-256
`FF0DE0B8C0A6A0AEE2926C08E8D6A40F2CC651E6C4B4BBF7B6BF2DE2CC390BEB`
(zweiter Bau 22:30 in `dist-setup-540b/`, nach dem Fix `2c74dc6`; der
erste Bau 22:23 in `dist-setup-540/` ist überholt).
* Paket-Smoke: `SMOKE: OK — fenster=geladen kern=pid:25652,node:24.18.1
datenbank=ok leine=gesetzt pong=ok version=5.4.0`.
* **353 Tests** in 34 Dateien (vorher 261), Typprüfung und Bau grün.
* Klick-Beweis: 15 Schritte grün, Ausgang 0 — Rippen → Dialog → drei
Titel → Sprach-Chips → jpn statt deu → „Hauptinhalt + Extras" → Rollen
auf Folge → Staffel 1, ab Folge 3 → Vorschau `Serien\…\Season 01` →
RIPPT → „Kompression eingereiht, das Laufwerk ist frei" → Kompressions-
Karte → „Komprimiert und abgelegt" → Dateien `… S01E03.mkv` und
`… S01E04.mkv` unter `Season 01` → Roh-Dateien-Karte → Roh löschen →
Ereignisse. Bilder: `beweise/klick/01…05*.png`.
* Commits dieser Session: `3f7f962` (Kern), `e3ce822` (Oberfläche),
`6dd1b0b` (Klick-Beweis), `c46a280` (Version 5.4.0), `2c74dc6` (Neu
komprimieren ersetzt die alte Fassung erst NACH dem Encode), dazu die
SAVEPOINTs. Beim zweiten Installieren (22:30) LIEF Rippy bereits (fünf
Prozesse) — der Commander hatte die 22:23-Fassung offenbar schon
gestartet; der Installer hat sie ersetzt.
### Was 5.4.0 enthält — alle zwölf Punkte der vorigen Übergabe
Der Commander hat gefragt „Traust du dir alles auf einmal zu?" und die
Fassung als EINE gewollt. Seine Entscheidungen: Roh-Dateien **nach 7 Tagen,
nur bei gelungener Kompression**; Untertitel bei fremdem Ton **als Spur,
beim Abspielen vorgewählt**; Warteschlange = **nächste Disc rippen, während
die vorige komprimiert**; **Playwright ja**.
1. **Roh-Dateien aufräumen** — `kern/ablage/rohaufraeumen.ts`: Regel
(`rohAufbewahrung`: sofort / 7 / 14 / 30 Tage / behalten), Frist ab
Ende der Kompression, gelöscht wird NUR bei Phase fertig + komprimiert
+ jede fertige Datei vorhanden und nicht leer. Ordner unter `roh\`, die
kein Vorgang kennt (alles aus der Zeit vor 5.4.0!), werden nur gezeigt.
Belegung in Einstellungen → Roh-Dateien. Takt: 60 s nach Start, täglich,
und nach jeder gelungenen Kompression.
2. **Staffel und Folge von Hand** — Felder im Dialog, sobald eine Rolle
„Folge" ist; `ersteFolge` zählt fortlaufend in Disc-Reihenfolge, TMDb
warnt nur noch (`folgenPlausibel`).
3. **Wiederholen ab Kompression** — jeder Rip ist ein Datensatz
(Tabelle `vorgaenge`, Schema 4, Auftrag als JSON). „Neu komprimieren"
mit Preset-Wahl reiht den alten Auftrag wieder ein, die neue Fassung
ersetzt die alte im selben Zielordner.
4. + 12. **Sprachen je Titel** — SINFO-Sprachen reisen je Titel in den
Dialog (Chips, vorbelegt aus der Einstellung, sonst alles Vorhandene),
von dort in `RipAuftragTitel.audioSprachen/untertitelSprachen`, in den
Kompressions-Auftrag und in HandBrake. `sprachwahl()`: erste Tonspur
nicht in der Wunschsprache → Untertitel der Wunschsprache nach vorn und
`--subtitle-default=1`. Vor der Kompression prüft ein HandBrake-Scan
(`komprimieren/scan.ts`) die Roh-Datei; MakeMKVs Auswahlregel wird
gesetzt, wenn keine steht (`werkzeuge/spurauswahl.ts`).
5. **Vorschau**`gemeinsam/ablagepfad.ts`, zwei Zeilen im Dialog.
6. **Warteschlange**`kern/ablauf/warteschlange.ts` + `ablegen.ts`:
Rip endet mit den Roh-Dateien, Laufwerk frei, Auswurf (Einstellung
`autoAuswurf`, Vorgabe an), Kompression eine zur Zeit im Hintergrund,
Karte in der Übersicht, Abbruch je Auftrag, Wiederaufnahme nach Neustart.
Unkomprimierte Rips (4K, kein HandBrake) werden in den Zielordner
VERSCHOBEN — vorher blieben sie unter `roh\` liegen, ohne NFO, Poster,
Bibliothek.
7. **Klick-Beweis**`npm run klick` (siehe oben), Naht
`kern/werkzeuge/aufruf.ts`, Fakes `bau/klick/`.
8. **Laufwerks-Gesundheit**`kern/laufwerk/ereignisse.ts` liest
`wevtutil qe System /q:<XPath> /f:xml` (cdrom/disk, 24 h), Urteil
Disc/Hardware/ruhig; automatisch nach einem gescheiterten Rip, sonst
Knopf „Laufwerk prüfen".
9. **Durchgehender Fortschritt**`gewichteterFortschritt()` nach
Titel-/Dateigröße, Rip und Kompression.
10. **Aufräumen nach Abbruch** — halbe Dateien eines abgebrochenen ODER
gescheiterten Titels werden entfernt; ein Roh-Ordner ohne eine einzige
brauchbare Datei auch.
11. **Erkennung v2 + Kino-Banner v2**`zuordnung.nachpruefen()` nach dem
Info-Lauf (Laufzeit ±6 % bestätigt → 0,95; ≥ 25 % daneben → Vorschlag),
`strukturHinweis()` (≥ 3 gleich lange Titel = Serie), Stubs zählen nicht
(`istStub`), `bdmt_deu` als zweite Titel-Variante. TMDb-Details holen
per `append_to_response` Logo, Tagline, Bewertung, FSK (DE), Besetzung.
Kachel zeigt Logo statt Text, Meta-Zeile, Darsteller-Streifen, Hinweise.
Dazu aus der Liste „auch noch offen": Dialog-Zustand aus der Kachel
herausgehoben (Dialoge leben im App-Zustand), `7c489f8` ist mit drin,
Datenbank-Pfad steht unter Einstellungen → Programm, CSP `font-src data:`
(eingebettete Schrift-Teile waren blockiert — Konsolenfehler, unsichtbar).
### ⚠ Der Datenbank-Befund — ungeklärt, bitte als Erstes prüfen
Auf diesem PC (Konto TobisPC, einziges Konto) lag unter
`%APPDATA%\Rippy\rippy.db` bis 21:13 eine Datenbank von **Schema 1 vom
30.08. 16:50** ohne eine einzige Einstellung — obwohl 5.3.2 hier
installiert war, der Updater am 01.09. 20:01/20:27 Dateien lud und der
Commander Einrichtung, Erkennung und Rips bestätigt hat. Ein 10-s-Start
des installierten Rippy durch mich schrieb genau in diese Datei (Schema 3,
`letzter_start`). Kein anderes `rippy.db` auf C:/D:/E:/F: ist seit dem
30.08. geschrieben worden. Der Commander sagt: „Ja, alles bleibt
erhalten" (Einstellungen überleben den Neustart, Datenbank-LED grün).
Beides zusammen geht nicht auf — ich konnte es nicht auflösen.
**Was beim Commander-Test zu beobachten ist:** Erscheint beim Start von
5.4.0 die Erst-Einrichtung, dann IST die Roaming-Datenbank die lebende und
sie war leer — einmal neu einrichten (TMDb-Key liegt in
`F:\Coding Stuff\Gittea Token\tmdb.txt`), danach bleibt es. Erscheint sie
nicht, steht unter Einstellungen → Programm jetzt der echte Pfad
(„Datenbank: …") — den bitte notieren. Die Roh-Dateien-Karte zeigt
außerdem seine wirkliche Ablage und Belegung.
### Was am Gerät noch zu messen ist
* **MakeMKV-Spurauswahl an einer echten Disc:** nach dem nächsten Rip
`RIPPY_MESSUNG_MKV="<roh-datei>" npx vitest run test/messung.spuren.test.ts
--reporter=verbose` — zeigt, welche Sprachen wirklich in der Roh-Datei
sind. Die Regel `app_DefaultSelectionString` setzt Rippy beim Start
(Ereignis-Zeile „MakeMKV-Auswahlregel gesetzt"), wenn keine da war.
* Warteschlange mit zwei Discs hintereinander; Auswurf nach dem Rip.
* „Laufwerk prüfen" am BU40N (das Protokoll hat 338 × cdrom 7 und
8 × cdrom 11 in 30 Tagen — es wird etwas zeigen).
* Hero-Banner mit TMDb-Key (Logo, FSK, Besetzung) — im Klick-Beweis ohne
Key nicht sichtbar. Vorsicht bei „Spartacus: Gods of the Arena": TMDb
führt es als Extras-Staffel (S0) von „Spartacus" (46296); die direkten
Treffer sind leere Stubs und zählen seit 5.4.0 nicht mehr.
* Roh-Aufräumen nach 7 Tagen ist frühestens ab dem 08.09. beobachtbar;
die Regel „sofort" lässt sich sofort prüfen.
* Weiter offen wie zuvor: `extras/`-Ordner in Jellyfin bestätigen,
DVD-/Audio-CD-Durchlauf, main-Merge.
### Messungen dieser Session (Regel D)
* **HandBrake 1.11** (ffmpeg-Testdatei jpn/deu/deu/eng + deu/eng-Untertitel,
Ausgabe per ffprobe): `--audio-lang-list deu,eng` = je Sprache die
erste Spur; `--first-audio` dasselbe; `--all-audio` alle passenden;
Untertitel-Ausgabe in LISTEN-Reihenfolge; `--subtitle-default=1` setzt
default UND forced; `-N deu` allein fügt keine Spur hinzu.
`--scan --json` schreibt „JSON Title Set:" + Objekt + weiteren Text.
* **TMDb** (v4-Token): `movie/149?append_to_response=images,credits,
release_dates&include_image_language=de,en,null` liefert tagline,
runtime, vote_average, images.logos (iso_639_1 de/en/null, .png/.svg),
credits.cast (name, character, profile_path, order), release_dates DE
certification „16" (type 3 Kino, 5 Heimvideo, 6 TV). `tv/46296`:
episode_run_time [53], content_ratings DE „18", seasons. Logo-Größen
w45…w500, Profil w45/w185/h632.
* **wevtutil** (`/q:` XPath, `/f:xml`, `/rd:true`): cdrom 7 = fehlerhafter
Block, 11 = Controllerfehler, 51 = E/A-Fehler; TimeCreated UTC. Über Git
Bash zerlegt der Pfad-Konverter das `/q:`-Argument — im Kern geht es
ohne Shell (spawn), das ist der Grund.
* **makemkvcon** nimmt `--minlength=<s>` (nicht in der Kurzhilfe).
* **MakeMKV-Auswahlregel** (forum.makemkv.com, Thema 4386): Standard
`-sel:all,+sel:(favlang|nolang),-sel:(havemulti|havecore),=100:all,-10:favlang`;
favlang „always matches if no favorite language is set".
### Bekannte Schwächen, bewusst in Kauf genommen
* `--subtitle-default=1` markiert die Spur auch als forced (so schreibt es
HandBrakes Muxer). Jellyfin zeigt sie damit; wer sie abschaltet, kann das.
* Die Stub-Regel ist eng: nur Einträge OHNE Beschreibung, Backdrop UND
Bewertung zählen als leer. Als letzter Vorschlag zählt auch ein Stub.
* Die Belegung wird bei jedem Vorgänge-Stand durch einen Ordner-Walk
gemessen — bei sehr vielen Roh-Ordnern dauert das Sekunden.
* Der Klick-Beweis läuft nur unter Windows (Electron + koffi) und nicht in
der Linux-Ampel — wie der Smoke.
---
## Übergabe vom 01.09.2026 (Sessionende, VOR 5.4.0 — die zwölf Punkte sind seit 5.4.0 umgesetzt)
### Wo die nächste Session startet
* **Arbeitsort:** `F:\Coding Stuff\rippy`, Branch
`worktree-windows-electron``git pull` genügt. v5-Code in
`rippy-windows/`; `main` ist reines Docker-Rippy.
* **Bau-Vorbereitung:** `npm install` in `rippy-windows/`, danach
`node node_modules/electron/install.js` (npm 11 blockt Electrons
Install-Skript). Fürs Paketieren `vendor/` aus
`%LOCALAPPDATA%\Rippy\tools` befüllen.
* **Release veröffentlichen:** die VIER Dateien im Gitea-Tag `aktuell`
ERSETZEN. **Token: `F:\Coding Stuff\Gittea Token\rippy.txt`** (40 Hex).
`git credential fill` liefert NUR ein Passwort und taugt NICHT.
* **Vorgehen, das sich bewährt hat:** erst lokal installieren, den
Commander testen lassen, DANN veröffentlichen. 5.3.1 wäre sonst mit
einem Fehler in den Kanal gegangen.
### Stand
**5.3.2 ist im Kanal und läuft beim Commander.** Ein vollständiger Rip
ist erfolgreich durchgelaufen (seine Bestätigung). Titelauswahl,
Serien-Ablage, Defekt-Wächter, Selbst-Update: alles am Gerät bewiesen.
**Committet, aber NICHT veröffentlicht:** `7c489f8` — der Dialog-Fix
(Markieren schloss den Dialog). Kann mit der nächsten Fassung mitgehen.
### Was die nächste Session bauen soll (Reihenfolge = Empfehlung)
Der Commander hat alle Punkte gutgeheißen und zwei ergänzt.
1. **Roh-Dateien aufräumen** — jede Blu-ray lässt 30+ GB liegen, niemand
räumt auf. Einstellung *behalten / nach Kompression löschen / nach X
Tagen*, plus Anzeige der Belegung. Im Code steht seit W-3: „Löschen
bekommt erst mit der Einstellung im UI eine Stimme."
2. **Staffel und Folge von Hand setzen** — Rippy rät bewusst nicht; der
Nutzer weiß aber, dass auf Disc 2 die Folgen 34 sind. Feld im
Auswahl-Dialog: „Staffel 1, ab Folge 3".
3. **Wiederholen ab Kompression** — die Roh-Dateien liegen genau dafür
da. Heute kostet ein Preset-Wechsel 40 Minuten Neulesen.
4. **Sprachwahl vor dem Rip**`titelInfoLesen()` liefert Tonspuren und
Untertitel je Titel bereits MIT, niemand liest sie aus (der vierte
Fall von „gebaut, nicht verbunden"). Siehe auch Punkt 12.
5. **Vorschau: welche Datei landet wo** — Rolle, Serie, Staffel und
Extras entscheiden inzwischen einiges; zwei Zeilen Zielpfad im Dialog
ersparen Überraschungen.
6. **Warteschlange über mehrere Discs.**
7. **Ein Smoke-Beweis, der KLICKT** — beide Fehler vom 01.09. waren
Verkabelung; von 261 Unit-Tests sah sie keiner.
8. **Laufwerks-Gesundheit aus dem Windows-Protokoll** — bei einem
Ausfall die `cdrom`-Ereignisse lesen und sagen, ob Disc oder
Hardware. (Am 01.09. von Hand gemacht, war entscheidend.)
9. **Durchgehender Fortschrittsbalken** — seit Titel-für-Titel springt
er je Titel zurück auf null. Gewichtet nach Dateigröße durchlaufen.
10. **Aufräumen nach Abbruch** — halbe Dateien bleiben im Roh-Ordner.
**11. Metadaten v2 + Hero-Banner v2 (Commander-Wunsch, hoher Wert).**
*Erkennung:* Rippy rät heute allein am TITELTEXT. Seit 5.2.0 liegt
bessere Evidenz ungenutzt herum:
* **Laufzeit-Abgleich** — TMDb kennt die Laufzeit, der Info-Lauf misst
sie. Dieser Vergleich findet NICHT statt. Stärkster Bestätiger, der
zu haben ist; widerlegt auch falsche Treffer (95 vs. 158 Minuten).
* **Struktur als Fingerabdruck** — steht wörtlich im KONZEPT: „1 langer
+ viele kurze = Film; 613 gleich lange = Serienstaffel". Erkennt
Serien auch ohne bdmt.
* **Zeitpunkt** — erkannt wird beim EINLEGEN, also vor jeder Messung.
Nach dem Info-Lauf noch einmal rechnen: aus „wahrscheinlich" wird
„bestätigt", oder Rippy merkt selbst, dass es danebenlag.
* `bdmt_deu.xml` als zweite Titelvariante (heute nur `bdmt_eng`).
*Banner:* TMDb liefert in DERSELBEN Anfrage (`append_to_response`) noch
**Logo** (freigestellter Schriftzug — der größte optische Sprung),
**Tagline**, **Bewertung**, **FSK** (aus den deutschen Freigaben) und
**Besetzung** mit Portraits. Daraus ein Kino-Mix-Banner: Backdrop über
die volle Breite, Verlauf ins Bühnen-Schwarz, Logo, Zeile
`2011 · FSK 18 · 52 Min · Drama · ★ 7,9`, Beschreibung, Darsteller-Streifen.
Bilder von TMDb lädt Rippy längst (Poster), eine CSP ist nicht gesetzt.
**Vor dem Bauen den TMDb-Schlüssel besorgen** und die Antworten
MESSEN, statt Feldnamen zu raten (Regel D). Der Commander wurde gebeten,
ihn nach `F:\Coding Stuff\Gittea Token\tmdb.txt` zu legen — beim
Sessionstart nachsehen, ob er da ist.
**12. Sprachen je Titel im Auswahl-Dialog (Commander-Wunsch).**
Wörtlich: *„Du hast Feature 1 auf der disc. das hat aber z. B.
japanischen Ton und braucht dringend die deutschen untertitel. In der
auswahl soll das direkt auswählbar sein."*
Heute gibt es nur GLOBALE Einstellungen (`audioSprachen`,
`untertitelSprachen`) — dieselbe Vorgabe für jede Disc und jeden Titel.
Nötig: je gewähltem Titel die gemessenen Tonspuren und Untertitel zum
Anhaken. Die Daten sind schon da (`DiscInfo.sprachen` aus dem
Info-Lauf, `parseStreamInfo`/`sprachenZusammenfassen`); es fehlt die
Anzeige und der Weg in den Auftrag (`RipAuftragTitel` erweitern) bis in
`komprimieren()`. Hängt eng mit Punkt 4 zusammen — zusammen bauen.
### Der Bug, der noch offen sein KÖNNTE
`7c489f8` behebt die wahrscheinliche Ursache (Markieren über den
Dialogrand). **Nicht durch Klicken bestätigt** — ich kann das UI nicht
bedienen. Falls es weiter auftritt, ist der zweite Verdacht die
Kachelliste: sie hängt an `verbindung.laufwerke.length === 0`, ein
einziger leerer Takt der Wache räumt Kachel UND Dialog ab. **Gemessen
und vorerst widerlegt** (20 von 20 Abfragen lieferten das Laufwerk),
aber die Fragilität bleibt. Sauber wäre, den Dialog-Zustand aus der
Kachel herauszuheben.
### Hardware-Lage
* Das **BU40N hängt am USB** (`USBSTOR\CDROM&VEN_HL-DT-ST&PROD_BD-RE_BU40N`).
Bei Aussetzern zuerst einen Port direkt am Mainboard probieren.
* **Spartacus Disc 2 hat einen echten Defekt** (fester Offset 56881152 in
Folge 1). Disc 1 liest sauber.
* Der **MakeMKV-Beta-Key läuft Ende September ab** — Rippy holt den neuen
selbst, nur beobachten.
### Auch noch offen
* `extras/`-Ordnername ist aus Wissen gesetzt, NICHT gemessen — Jellyfin
muss bestätigen, dass es sie als Zugaben liest.
* Auswurf-Messung, DVD- und Audio-CD-Durchlauf, Jellyfin-Sichtbarkeit.
* main-Merge des Aufräum-Branches.
---
## Zwei eigene Fehler gefunden und behoben — Version 5.3.2 (01.09.2026, nachts)
> **Die Titelauswahl LÄUFT und ist am Gerät bestätigt.** Der Commander,
> Screenshot: *„Serie: 2 Folgen erwartet, 2 gefunden — passt."*
>
> ```
> [x] Titel 0 52:47 min 16,28 GB 7 Kap. [Folge]
> [x] Titel 1 54:09 min 16,67 GB 7 Kap. [Folge]
> 2 von 2 gewählt · zusammen 32,95 GB
> ```
>
> Die Längen liegen 2,5 % auseinander — weit innerhalb von `AEHNLICH`
> (15 %), deshalb „passt" ohne Unsicherheits-Warnung. Die Auslegung
> stimmt also an echten Zahlen, nicht nur im Test.
### Fehler 1 (5.3.1): Der Kern verwarf `titel-lesen` STILL
Der Dialog blieb ewig bei „Rippy liest die Titel …". Gemessen am
lebenden Objekt: `makemkvcon -r --noscan info dev:G:` antwortet in
**0,8 s** — der Info-Lauf hing nie.
Der Fehler lag in MEINEM Code von 5.2.0: `istFensterNachricht()` kannte
`'titel-lesen'` und `'rip-ueberspringen'` nicht. Beide standen in der
Union, aber nicht im Prüfer, und der Kern warf sie mit
`if (!istFensterNachricht(frage)) return` weg. **Ohne ein Wort.**
Auf drei Ebenen behoben:
1. Der Prüfer kennt beide.
2. Eine verworfene Nachricht ist LAUT (Protokoll + `kern-fehler` mit der
verworfenen Art).
3. **Neuer Wächter-Test:** liest das Schema, vergleicht jede `art:` der
Unions mit dem jeweiligen Prüfer, und prüft, dass der Kern nicht mehr
still verwirft. **GEGENPROBE gemacht** — mit zurückgenommener
Reparatur schlägt er fehl (`expected [ 'titel-lesen' ]`), mit
Reparatur grün. Kein Deko-Test.
Dazu: `DiscInfo.ursachen` reicht die kritischen MSG-Nummern durch. Beim
5042-Zustand steht jetzt die Abhilfe im Dialog statt „keine Titel
gefunden" — das zeigte auf die DISC, dabei fehlte das LAUFWERK.
### Fehler 2 (5.3.2): Ein alter Titel-Lauf legte sich über die Liste
*„Er liest es korrekt ein, ich bekomme auch die auswahl aber kurz danach
kommt dieser fehler"* — „keine Antwort nach 300 s", MINUTEN nach der
fertigen Liste. Mit EINEM Lauf unmöglich (der Timeout wird bei `close`
gelöscht), also waren zwei unterwegs.
Ich hatte nie bedacht, was passiert, wenn jemand zweimal auf „Rippen"
drückt — bei etwas, das zwei Minuten dauert und dabei nur „liest …"
anzeigt, ist das der Normalfall, keine exotische Bedienung.
Behoben je Laufwerk: Ein neuer Lauf **löst den alten ab**
(AbortController → `titelInfoLesen` killt das Kind, lehnt mit der Marke
`ABGELOEST` ab). Nur die neueste Laufnummer darf antworten; ein
abgelöster Lauf **schweigt**. Nebeneffekt: nie zwei `makemkvcon` auf
derselben Disc.
### Setup 5.3.2
* **131 526 975 Bytes**, SHA-256
`74BBB686BD7EF95C1EFF8076FCA564BF83E8836301C939C38596EA03C2D33DEE`
* Paket-Smoke GRÜN, **261 Tests**, Ampel GRÜN (`6d14921`), im Release
`aktuell` anonym gegengeprüft.
* **Vorgehen war: erst lokal installieren, testen lassen, dann
veröffentlichen** — auf Wunsch des Commanders. Hat sich bewährt: 5.3.1
wäre sonst mit Fehler 2 in den Kanal gegangen.
### ⚠ Offen
* **Der komplette Durchlauf steht noch aus.** Auswahl-Dialog ist am Gerät
bestätigt; ob die Folgen wirklich unter `Serien/…/Season 01/` landen
und was Rippy zu den Episoden-Nummern sagt, ist ungeprüft.
* Der `extras/`-Ordnername ist weiterhin aus Wissen gesetzt, nicht
gemessen — Jellyfin muss das bestätigen.
* Spartacus **Disc 2** hat einen echten Defekt (fester Offset 56881152 in
Folge 1). Disc 1 liest sauber.
---
## Serien-Staffelablage + Film-Extras — Version 5.3.0 (01.09.2026, nachts)
> Commander: *„Mach das gerne noch mit den Serien."* und *„Für FILME
> sollte so eine auswahl auch gelten z. B. Nur hauptfeature oder
> hauptfeature + extras usw. Rippy muss das auch selbst zusammenbasteln."*
### DAS MUSTER DIESER SITZUNG — zum dritten Mal
Auch hier lagen die Teile **fertig und getestet** herum und waren nur
nicht verbunden: `serienOrdner()`, `matcheEpisoden()`,
`episodenUmbenennen()` (`ablage/struktur.ts`) und `tvStaffel()`
(`metadaten/tmdb.ts`). Deshalb landeten Folgen unter `Filme/`, und
Jellyfin sah lauter Einzelfilme statt einer Staffel.
Vorher schon so gefunden: `titelInfoLesen()` (rief niemand auf) und die
Preset-Empfehlung (lief, war aber unsichtbar). **Lehre für die nächste
Sitzung: Bevor etwas neu gebaut wird, erst nachsehen, ob es schon da ist
und nur der Draht fehlt.** Der Docker-Zweig hat viel vorgelegt, was beim
v5-Neubau mitkam, aber nie angeschlossen wurde.
### Die Rolle bestimmt das Ziel
Jeder Titel im Auftrag trägt eine `TitelRolle` (EINE Definition in
`gemeinsam/nachrichten.ts`):
```
hauptfilm -> Filme/<Titel> (Jahr)/<Titel> (Jahr).mkv
extra -> Filme/<Titel> (Jahr)/extras/…
folge -> Serien/<Titel>/Season NN/… S01E02.mkv
```
* Rippy schlägt vor (Serie → Folge, Film → Hauptfilm, Rest → Extra), der
Dialog lässt es je Titel ändern. Neuer Schnellwahl-Knopf
„Hauptinhalt + Extras".
* Die Zuordnung Datei→Rolle ist **exakt, nicht aus Dateinamen geraten**:
Je makemkvcon-Lauf kommt genau ein Titel dazu, und genau der bekommt
die Rolle seines Auftragseintrags (`rolleJeDatei`).
* Der `extras/`-Ordner entsteht nur, wenn es wirklich Extras gibt.
### Episoden-Nummern: bewusst zurückhaltend
`RipAuftragTitel.dauerS` reist mit, die Staffel-Laufzeiten kommen über
einen **injizierten** Rückruf (`episodenLaufzeiten`, im Kern aus
`tvStaffel`) — so bleibt die Pipeline ohne Netz prüfbar.
`matcheEpisoden()` benennt **nur bei EINDEUTIGER Zuordnung** um. Sonst
behalten die Dateien ihre MakeMKV-Namen, und Rippy nennt den Grund
(„keine Staffel-Laufzeiten von TMDb" bzw. „nicht eindeutig"). Lieber gar
nicht als falsch — ARMs offene Wunde #395 war genau dieses Ratespiel.
Die Staffel kommt aus dem Disc-Titel (`discZusatzAbtrennen`, seit 5.1.1),
sonst 1.
### Setup 5.3.0
* **131 526 532 Bytes**, SHA-256
`A4613214F18481B03D6D11DDAE4344C8537A12825C086E89039380C62767043F`
* Paket-Smoke GRÜN (`version=5.3.0`), **257 Tests**, Ampel GRÜN
(`7ce0688`), im Release `aktuell` anonym gegengeprüft.
### ⚠ Offen — beides braucht Hand am Gerät
* **Weder Auswahl-Dialog noch Serien-Ablage sind je an echter Hardware
gelaufen.** Logik getestet, Verkabelung typgeprüft; der erste echte
Durchlauf steht aus. Das Laufwerk war im 5042-Zustand und sollte nach
dem halbstündigen Hämmern nicht weiter belastet werden.
* **Der `extras/`-Ordnername ist aus Wissen gesetzt, NICHT gemessen.**
Ob Jellyfin ihn als Zugaben liest, kann nur die Bibliothek des
Commanders beantworten. Falls nicht: `featurettes` oder
`behind the scenes` sind die Alternativen.
* Bei zwei ähnlich langen Spartacus-Folgen wird die Episoden-Zuordnung
vermutlich NICHT eindeutig — dann liegen sie richtig einsortiert im
Staffelordner, heißen aber noch wie MakeMKV sie nannte. Das ist das
gewollte Verhalten, kein Fehler.
---
## Titelauswahl + Defekt-Wächter — Version 5.2.0 (01.09.2026, nachts)
> Commander: *„es fehlt generell noch komplett die auswahl WAS man rippen
> möchte. Bei Serien eben genau das die folgen, bei filmen z. B. nur das
> hauptfeature oder auch die extras."*
>
> Bis hierher übergab die Pipeline fest `titel: 'all'` — Rippy rippte
> **immer alles**, samt Trailer, Werbung und Alterskennzeichen.
### Der Anlass: eine defekte Blu-ray
Beim Rip der Spartacus-Disc hing MakeMKV eine halbe Stunde an EINER
Stelle. Gemessen (01.09.2026):
```
Offset '56881152' — immer derselbe, 39× „MEDIUM ERROR:L-EC UNCORRECTABLE"
makemkvcon64 — 1,7 s CPU in 30 Minuten (es WARTETE)
Windows-Systemlog — 41 cdrom-Ereignisse in 10 Minuten (ID 7 + ID 11)
```
**Wichtige Korrektur einer eigenen Fehldiagnose:** Erst hielt ich die
USB-Stromversorgung für die Ursache (das Laufwerk hängt am USB, ID 11 =
Controllerfehler). Der FESTE Offset widerlegt das — Stromprobleme werfen
Fehler an wechselnden Stellen. Es ist ein physischer Defekt auf der Disc;
die Controllerfehler sind die FOLGE des halbstündigen Hämmerns. Der Code
wusste es sogar schon: `makemkv.ts` notiert seit 30.08. „MakeMKV kann 1
von 2 Titeln sichern … Spartacus Disc 2".
### Der Umbau: Titel für Titel statt `all`
Beide Wünsche brauchen denselben Unterbau, deshalb in einem Zug. Zwei
Bausteine lagen fertig herum und waren nur **nicht verbunden**:
`titelInfoLesen()` (Titel mit Dauer/Größe/Kapiteln) rief NIEMAND auf, und
`buildRipArgs(…, titel)` konnte längst einen einzelnen Titel.
### Neu `kern/rip/titelwahl.ts` (pur, ohne Laufwerk)
* **Serie** (von der Disc BEZEUGT, bdmt nennt Folgen): so viele Titel wie
Folgen erwartet; passen die Längen nicht zusammen, sagt Rippy das.
* **Film**: der längste Titel.
* **Sammeltitel-Falle** (`SAMMEL_TOLERANZ`): Ein Titel, der so lang ist
wie die anderen ZUSAMMEN („alle Folgen am Stück"), wäre der längste und
würde die Film-Auswahl gewinnen — Rippy hätte alles doppelt gesichert.
* **Angel-Falle** (`FILM_GLEICHAUF`): Mehrere fast gleich lange Fassungen
(Seamless Branching). Rippy nimmt die mit den meisten Kapiteln UND sagt,
dass es unsicher ist.
* **Unklar heißt: nichts vorausgewählt.** Lieber fragen als raten.
**Bewusst NICHT geraten:** Die bdmt-`titleNumber` (1, 2, 51, 101 …) sind
Blu-ray-Playlist-Nummern, MakeMKVs Titelnummern sind eigene. Sie
gleichzusetzen wäre Raterei — benutzt wird nur die ANZAHL als Prüfsumme.
### Der Defekt-Wächter
* `offsetAusMeldung()` liest die Stelle als **letzte gequotete Zahl**
damit sprachunabhängig (deutsch „bei Offset", englisch „at offset").
* `RipAuswertung` zählt Wiederholungen derselben Stelle; ab `HAENGT_AB`
(12 Meldungen ≈ 6 Versuche) gilt der Lauf als hängend. **Fortschritt
räumt den Verdacht wieder ab.**
* Das Fenster zeigt Klartext + Knopf **„Titel überspringen"** →
`Pipeline.ueberspringen()` beendet nur den laufenden makemkvcon
(eigener AbortController je Titel), der nächste Titel läuft weiter.
* Ein gescheiterter Titel beendet den Auftrag NICHT mehr — die Gründe
werden gesammelt und am Ende genannt.
### Ablauf im Fenster
„Rippen" → Info-Lauf (20120 s, mit Ansage) → Auswahl-Dialog mit Dauer,
Größe, Kapiteln und Begründung je Titel → Schnellwahl „Rippys Vorschlag /
Alles / Nichts" → rippen. Der Info-Lauf startet **erst beim Klick**, nie
beim Einlegen (der 30.08.-Fund „das Erkennen dauert sehr sehr lange").
### Setup 5.2.0
* **131 526 561 Bytes**, SHA-256
`C426A8460DDBE43C899933956F2A7A97A96083638D1C910F482802D9FAD8946A`
* Paket-Smoke GRÜN (`version=5.2.0`), **254 Tests** (vorher 237; 17 neue
in `titelwahl.test.ts`, alle beim ersten Lauf grün), Ampel GRÜN
(`a8e8b2b`), im Release `aktuell` anonym gegengeprüft.
### ⚠ Offen
* **Der Auswahl-Dialog ist noch nie an echter Hardware gelaufen** — das
Laufwerk war im 5042-Zustand und sollte nicht weiter belastet werden.
Logik getestet, Verkabelung typgeprüft, erster echter Durchlauf steht aus.
* **Serien-Ablage fehlt weiterhin:** Folgen landen unter `Filme/`. Für
Jellyfin & Co. bräuchte es `Serien/<Titel>/Staffel X/` mit `SxxEyy`.
Bewusst als eigener Brocken zurückgestellt.
* **Das BU40N hängt am USB** (`USBSTOR\CDROM&VEN_HL-DT-ST&PROD_BD-RE_BU40N`)
— bei künftigen Aussetzern zuerst Port direkt am Mainboard probieren.
* Die Spartacus-Disc hat einen echten Defekt in Folge 1. Zum Prüfen der
neuen Auswahl: NUR Folge 2 anhaken, dann wird der Defekt gar nicht erst
angefahren.
---
## Windows-Rand, Hardware-Empfehlung, Wizard — Version 5.1.5 (01.09.2026, nachts)
> **Zuerst der Beweis, dass 5.1.1 wirkt.** Der Commander legte die
> Spartacus-Disc mit dem neuen UI ein, im Protokoll steht:
>
> ```
> Laufwerk G: erkannt als „Spartacus: Gods of the Arena" · 90 %
> ```
>
> Nicht der Film von 1960, sondern die Serie — die vorhergesagten 90 %,
> an der echten Disc. Der Fund ist damit am lebenden Objekt erledigt.
Vier Punkte auf Zuruf:
### 1. Der Windows-11-Rand — er kam wirklich von Windows
Ein rahmenloses Fenster behält unter Windows 11 den **DWM-Rand**: eine
feine Linie, die der Fenstermanager zeichnet, nicht die Seite. Mit CSS
nicht erreichbar. Neu `haupt/fensterrand.ts`:
`DwmSetWindowAttribute(DWMWA_BORDER_COLOR = DWMWA_COLOR_NONE)`.
**Gemessen: HRESULT 0**, auch im gepackten Programm (keine
`Fensterrand bleibt`-Warnung im Paket-Smoke).
Runde Ecken bleiben (Windows-11-üblich); `eckenKantig = true` schaltet
sie ab, wenn gewünscht. Ältere Windows antworten mit Fehler-HRESULT —
wird als Wert zurückgegeben und protokolliert (R4), dort gibt es den
Rand ohnehin nicht.
**Regeländerung, bewusst und gemeldet:** Das ist der DRITTE erlaubte
koffi-Ort. Wächter R1 kennt jetzt `leine.ts`, `fensterrand.ts` und
`kern/laufwerk/win32.ts`. Die Regel („Win32 nur an benannten Orten")
bleibt und hält alles andere weiter dicht. Grund: `DwmSetWindowAttribute`
braucht das Fensterhandle und kann nur im Haupt liegen.
### 2. Die Preset-Empfehlung gab es SCHON
Commander: *„Die alte Rippy version konnte automatisch anhand der
gescannten hardware encoder das beste preset wählen."* — Diese auch.
`presetEmpfehlung()` wählt seit dem Kino-Mix nach den gemessenen
Backends (VCN → NVENC → QSV → CPU); in den Einstellungen stand
„automatisch: H.265 VCN 1080p", also seine AMD-Karte.
**Das Problem war Sichtbarkeit, nicht Logik.** Die Erst-Einrichtung hat
jetzt einen Schritt „Qualität": gemessene Encoder als Badges, die
Empfehlung für DVD / Blu-ray / 4K daneben, und eine ehrliche Warnung,
wenn nur CPU-Encoder da sind. Die Logik selbst blieb unangetastet.
### 3. Feld „Update-Adresse" ist raus
Der Schlüssel `updateUrl` gilt weiter (Datenbank), § 6.8 prüft ihn
unverändert — nur das Eingabefeld ist weg.
### 4. Erst-Einrichtung geprüft — fünf Befunde
* **„Dieses Setup stellt vier Dinge ein"** — es stellte ZWEI ein.
* Der Ablage-Schritt zeigte eine **leere Zeile**, wenn nichts gewählt
war. Es gibt aber eine Vorgabe (`kern/index.ts`: `Videos\Rippy` im
Benutzerprofil) — die steht jetzt da.
* **Kein Wort zum TMDb-Schlüssel**, obwohl Schritt 1 „Rippy erkennt, was
drauf ist" verspricht. Ohne Schlüssel heißen Filme wie das Disc-Label.
Jetzt ein eigener Schritt mit Eingabefeld.
* Rohe HTML-Checkbox statt des Kino-Mix-Schalters.
* Kein Hinweis, dass Rippy im Infobereich weiterlebt.
Neu **sechs Schritte, drei Fragen** — und die Zahl im Text stimmt.
**Dazu `#einrichtung` als Beweis-Anker** (`RIPPY_START_BEREICH`): Der
Wizard war nach dem ersten Start überhaupt nicht mehr erreichbar und
damit nicht mehr prüfbar. Beweis: `beweise/ui-515-wizard.png`.
### Setup 5.1.5
* `dist-setup-515/RippySetup-5.1.5.exe`, **131 529 635 Bytes**, SHA-256
`A7E8B51350D2C78AC6BDA2DF6881E640D3B4D84C935B4B395529433849B18D15`
* Paket-Smoke GRÜN (`version=5.1.5`), 237 Tests, Ampel GRÜN (`663127f`)
* Im Release `aktuell`, anonym gegengeprüft: Content-Length =
`latest.yml`; Changelog vollständig mit Umlauten durchgereicht.
### ✅ DER SELBST-UPDATE-BEWEIS IST ERBRACHT
Der Commander, 01.09.2026: *„Also das upate funktioniert!"* — 5.1.4 fand
5.1.5 selbst, lud sie, installierte sie und kam in der neuen Fassung
zurück. **Ohne Handanlegen.**
Damit ist der offene Punkt 1 aus der Übergabe vom 31.08. erledigt, und
die Kette ist zum ERSTEN Mal komplett bewiesen — nicht nur der Kanal
(Download, Prüfsummen, Changelog), sondern auch der letzte Meter, das
Installieren. Genau der Meter, an dem es von 5.1.2 bis 5.1.3 still
gescheitert war.
**Ab jetzt gilt wieder die einfache Regel:** neues Release = die vier
Dateien im Gitea-Tag `aktuell` ersetzen, fertig. Jede installierte Rippy
ab 5.1.4 zieht selbst nach.
### Weitergabe an andere Rechner
Die Setup-EXE genügt — **außer MakeMKV**, das ist Pflicht und liegt aus
Lizenzgründen nicht bei (makemkv.com/download; Rippy findet es danach
selbst und holt den Beta-Schlüssel automatisch). HandBrakeCLI und FLAC
sind im Paket. Nur **5.1.4 oder neuer** weitergeben: Ältere können sich
nicht selbst aktualisieren.
---
## Die eigene Leine erschlug den Update-Installer — Version 5.1.4 (01.09.2026, nachts)
> Commander: *„Das Update auf Version 5.1.3 geht nicht. Rippy wird zwar
> sauber beendet aber startet NICHT neu in der neuen version."*
>
> **Die Update-Kette hat ab 5.1.2 nie funktioniert.** Bewiesen war immer
> nur der KANAL (Download, Prüfsummen, Changelog) — der letzte Meter, das
> INSTALLIEREN, war nie gemessen. Genau die Sorte Annahme, vor der
> AGENTS.md warnt.
### Die Ursache, auf drei Beinen belegt
1. **Bibliothek:** `electron-updater` startet den Installer per
`spawn(…, { detached: true })` (`BaseUpdater.js`, an der installierten
Fassung nachgelesen). `detached` setzt unter Windows **kein**
Breakaway.
2. **Leine:** `leine.ts` setzt `KILL_ON_JOB_CLOSE`, und Kinder erben die
Mitgliedschaft. Der Installer war damit Mitglied der Arbeitsgruppe.
3. **Platte:** `pending\RippySetup-5.1.3.exe` lag geladen da (18:11:47),
installiert war weiter 5.1.2.
Der Installer starb also in genau der Sekunde, in der Rippy sich
beendete, um ihm Platz zu machen.
### Der Fix — die Leine bleibt, der Installer klinkt sich aus
* `leine.ts` setzt zusätzlich `JOB_OBJECT_LIMIT_BREAKAWAY_OK`. Das ändert
für sich genommen NICHTS (siehe Messung, Fall 2).
* Neu `ausserhalbDerLeineStarten()``CreateProcessW` mit
`CREATE_BREAKAWAY_FROM_JOB | CREATE_NO_WINDOW`. Liegt in `leine.ts`,
weil dort die Arbeitsgruppe verwaltet wird und Wächter R1 koffi nur
dort und in `kern/laufwerk/win32.ts` erlaubt.
* `update.ts` installiert jetzt SELBST: `autoInstallOnAppQuit` ist AUS,
der Pfad kommt aus `downloadUpdate()` (AppUpdater.d.ts: „Paths to
downloaded files"), die Argumente sind die gemessenen
(`--updated /S --force-run`). Gestartet bei `will-quit` und per Knopf.
* Neu `kommandozeile.ts` (pur, ohne koffi/Electron): `CreateProcessW`
nimmt EINE Zeichenkette — ohne die Regeln von `CommandLineToArgvW`
zerfiele ein Pfad wie `C:\Users\Tobi Neu\…` in zwei Argumente.
* **Warum nicht `shell.openPath` wie bei MakeMKV (§ 9)?** Der entkommt der
Leine nur, WEIL er Adminrechte verlangt und über den AppInfo-Dienst
startet. Rippys Installer ist bewusst benutzerlokal
(`isAdminRightsRequired: false`) — er liefe wieder als Kind.
### Die Messung: `beweise/leine-breakaway.js`
```
1) Leine wie bisher, Kind normal -> stirbt WIE ERWARTET
2) Leine erlaubt Ausklinken, Kind normal -> stirbt WIE ERWARTET
3) Leine erlaubt Ausklinken, Kind klinkt sich aus -> lebt WIE ERWARTET
```
**Fall 2 ist der wichtige:** Das bloße Erlauben weicht § 3.3 NICHT auf —
Kern, makemkvcon und HandBrakeCLI bleiben angeleint, die rc10-Waisen-
Falle ist unverändert zu.
**Der erste Messlauf zeigte ein falsches Negativ** („Ausklinken wirkt
nicht"). Grund: Der Enkel teilte sich die KONSOLE des Elternprozesses und
starb mit ihr, nicht mit der Arbeitsgruppe. Erst `CREATE_NO_WINDOW` misst,
was gemessen werden soll. **Lehre: Auch der Versuchsaufbau kann die
falsche Sache messen** — der Flag steht deshalb auch im echten Aufruf.
### Setup 5.1.4 und Release
* `dist-setup-514/RippySetup-5.1.4.exe`, **131 528 605 Bytes**, SHA-256
`2933CA098421A3C5C5807E32BB718B18C4A7FB9F1AB6D70C9B1D164A15F21B84`
* Paket-Smoke GRÜN: `leine=gesetzt … version=5.1.4` — die Leine steht
auch mit dem neuen Recht.
* Im Release `aktuell`, anonym von außen gegengeprüft: Content-Length =
`latest.yml` = lokale Datei; `releaseNotes` durchgereicht.
* 237 Tests, Ampel GRÜN (`051cab3`).
### ⚠ Der Sprung AUF 5.1.4 ist einmalig Handarbeit
Eine installierte 5.1.05.1.3 kann sich nicht selbst aktualisieren — der
Fehler steckt in IHR. `RippySetup-5.1.4.exe` einmal von Hand ausführen.
**Ab 5.1.4 läuft die Kette von allein**; das ist dann der erste echte
Selbst-Update-Beweis und steht noch aus (z. B. mit einer kleinen 5.1.5).
---
## Eigenes Fenster + Layout, das mitwächst — Version 5.1.3 (01.09.2026, abends)
### Zuerst: „hier zieht garnichts hoch" — doch, es zog
Der Commander meldete, das Update komme nicht an. **Es kam an.** Gemessen
in `%LOCALAPPDATA%\rippy-updater\pending\`:
```
RippySetup-5.1.2.exe 131 526 566 B geladen 17:57:10
SHA-256 27D1BA58…6E3C03 = exakt die hochgeladene Datei
update-info.json {"fileName":"RippySetup-5.1.2.exe", …}
```
Der Kanal arbeitete also einwandfrei. Zwei Gründe, warum davon nichts zu
sehen war:
1. Die installierte **5.1.0 kennt die Update-Karte nicht** — der Code
dafür kommt erst mit 5.1.2 mit.
2. **Installiert wird beim Beenden — und Rippy lebt im Tray.** Das
Fenster zu machen beendet Rippy NICHT (§ 4.1 Punkt 3), also kam
`autoInstallOnAppQuit` nie zum Zug. Nötig ist ein echtes Beenden
über das Tray-Symbol.
**Lehre (passt zu AGENTS.md „nicht aus einem Zustandswert auf einen
Mechanismus schließen"):** „Es passiert nichts" hieß hier nicht „der
Kanal ist kaputt", sondern „die Wirkung ist an eine Bedingung geknüpft,
die niemand kennt". Der Ordner `rippy-updater\pending` beantwortet die
Frage in zehn Sekunden.
### Der UI-Umbau (`827910f`, Ampel GRÜN, 230 Tests)
Commander: *„Momentan ist alles nach links gerückt, nichts skaliert mit
der fenstergröße und es ist alles gequetscht."*
**Ursache:** `<main>` hatte WEDER Zentrierung NOCH Maximalbreite — die
Karten darin aber einen harten Deckel von `max-w-2xl` (672 px). In einem
1280er Fenster klebte damit alles links, rechts stand nichts.
* `<main>` und Kopfzeile teilen sich `mx-auto max-w-[1600px]` mit
mitwachsendem Innenabstand.
* Der 672-px-Deckel der Einstellungs-Spalte ist weg — drei Encoder-Felder
stehen jetzt nebeneinander statt untereinander.
* Die Sektionswahl der Einstellungen wird im schmalen Fenster zur Zeile
über den Karten (`lg:flex-row`), statt 176 px vom knappen Platz zu
nehmen.
* Info-Kacheln: zwei Spalten schmal, vier breit (vorher immer vier).
* Startgröße 1000×700 → **1280×820**, Mindestmaß 720×480 → 860×560.
### Eigene Titelleiste (§ 4.2)
Commander: *„wäre sogar richtig cool wenn du den rahmen wegbekommst"*.
`frame: false`, und die Kopfzeile IST die Titelleiste: Minimieren /
Maximieren / Schließen oben rechts in Windows-Maßen (46 × 32), gezogen
wird an der Kopfzeile, Doppelklick maximiert.
* **Größe ändern bleibt möglich** — belegt aus Electrons eigener
Typdefinition (`electron.d.ts`): `thickFrame` ist standardmäßig `true`,
erst ein `false` würde „disable window resizing via dragging the window
edges". Wir lassen den Standard.
* **Schließen nimmt GENAU den Weg des alten Fensterkreuzes**
(`fenster.close()` → der bestehende close-Horcher versteckt nur):
Rippy lebt im Tray weiter, ein laufender Rip merkt nichts.
* Ziehfläche ist `.ziehbar` in `stil.css`; alles Bedienbare darin trägt
`.nicht-ziehbar`, sonst verschluckt der Griff den Klick.
* `fensterMaximiert` im `HauptStatus` steuert das Knopf-Symbol und wird
auch bei Doppelklick/Tastenkürzel nachgeführt (`maximize`/`unmaximize`).
Beweis-Bilder: `beweise/ui-513-uebersicht.png` (kein Rahmen, eigene
Knöpfe, volle Breite), `beweise/ui-513-einstellungen.png`.
### Setup 5.1.3 und Release
* Gebaut nach `dist-setup-513/` (EBUSY-Sperre, BAUEN.md), Paket-Smoke
GRÜN mit `version=5.1.3`
* **131 527 863 Bytes**, SHA-256
`53CC7A5A505D44149E833ED261ACAB5E8DB1ED47F27BB144D1A89B6F076E3C03`
* Im Release `aktuell`, anonym von außen gegengeprüft: Content-Length =
Zahl in `latest.yml` = lokale Datei; `releaseNotes` durchgereicht,
Umlaute intakt (1561 B).
### ⚠ Offen für die nächste Sitzung
* **Der Selbst-Update-Beweis am lebenden Objekt.** Beim Beenden über das
Tray installiert die wartende 5.1.2, beim nächsten Start findet sie
5.1.3 — und DANN ist die goldene Karte zum ersten Mal zu sehen. Genau
der Ablauf, den der Commander sehen wollte.
* **Das Laufwerk ist wieder im 5042-Zustand** (`MediaLoaded: False`,
`G:\` antwortet nicht) — für den Disc-Test Disc per Taste am Laufwerk
auswerfen und neu einlegen.
* Der Spartacus-Durchlauf mit dem neuen UI steht noch aus.
---
## Update sichtbar gemacht — Version 5.1.2 (01.09.2026, abends)
> Der Commander: *„irgendwie fehlt in rippy selbst der Button Auf
> updates prüfen' oder auch direkt beim start ne meldung ey jo update
> verfügbar' — wäre noch cool wenn das kommt inkl. changelog"*.
>
> Zu Recht: Der Kanal arbeitete korrekt, war aber **unsichtbar**. Der
> Update-Stand lag im Haupt und ging nur beim Laden des Fensters EINMAL
> mit. Fand die Prüfung etwas, änderte sich am Bildschirm nichts — der
> einzige Hinweis war ein Satz unter „Update-Adresse", den man kennen
> musste, um ihn zu finden.
### Was jetzt da ist (`c0a7618`, Ampel GRÜN, 230 Tests)
* **Goldene Karte oben in der Übersicht**, sobald etwas gefunden wird:
neue Versionsnummer, laufende Version, Ladebalken, Änderungsnotizen.
Dazu der Windows-Toast beim Fund.
* **Knopf „Jetzt auf Updates prüfen"** in den Einstellungen. Während
eines Rips AUS, mit Begründung im Tooltip — Rippy sucht dann bewusst
nicht (§ 6.8), das ist keine Panne.
* **Knopf „Jetzt neu starten und installieren"** auf der Karte, sobald
das Paket geladen ist. Sagt NEIN mit Grund statt still nichts zu tun.
* `setzen()` ruft IMMER `beiAenderung` → jede Änderung am Stand meldet
sich sofort ans Fenster. Kein Zustand mehr, den niemand erfährt.
* `UpdateStand` ist jetzt EINE Definition in `gemeinsam/nachrichten.ts`.
### Der Changelog — an der Bibliothek geprüft, nicht geraten (Regel D)
`app-builder-lib/out/publish/updateInfoBuilder.js`, `getReleaseInfo()`
liest genau den Dateinamen `release-notes.md` und legt den Inhalt als
`releaseNotes` in `latest.yml`; `builder-util-runtime` deklariert
`UpdateInfo.releaseNotes` als `string | Array<ReleaseNoteInfo> | null`.
`neuerungenText()` verträgt beide Formen, wirft HTML-Marken raus und
deckelt auf 4000 Zeichen. Quelle des Textes: **`bau/release-notes.md`**
— die ist bei jedem Release zu pflegen, sie IST die Meldung im Fenster.
Nachgemessen am fertigen Bau UND anonym vom Server: Der Block
`releaseNotes: |` steht in `latest.yml` (1191 B statt 337 B), Umlaute
unversehrt.
### Das Setup 5.1.2 und das Release
* `rippy-windows/dist-setup-neu/RippySetup-5.1.2.exe` — **wegen der
EBUSY-Sperre in ein frisches Verzeichnis gebaut** (BAUEN.md), der
Paket-Smoke auf `dist-setup/` hatte `app.asar` verriegelt.
* Größe **131 526 566 Bytes**, SHA-256
`27D1BA58BF613C43BBCBF8CB819401A6D3CD6F6E59F09AE76BDC350F0CC9DA8E`
* Paket-Smoke GRÜN, meldet `version=5.1.2`
* Im Release `aktuell`: EXE + blockmap neu, `latest.yml` und `w0.yml`
ersetzt. Anonym von außen gegengeprüft — Server-Content-Length =
Zahl in `latest.yml` = lokale Datei.
* **Die EXEn von 5.1.0 und 5.1.1 liegen bewusst weiter im Release**
(Rückfallebene). Wenn der Platz knapp wird: 5.1.0 kann weg, der
Commander hat sie auf dem Desktop.
### ⚠ Zu wissen: Die Karte ist erst AB 5.1.2 sichtbar
Eine installierte 5.1.0/5.1.1 zeichnet die Karte noch nicht — der Code
dafür kommt ja erst mit diesem Update mit. Sie aktualisiert sich still
auf 5.1.2, wie bisher. **Erst ein Update VON 5.1.2 AUF etwas Neueres
zeigt die Meldung.** Angeboten, noch nicht entschieden: eine winzige
5.1.3 nachlegen, damit der Commander den Ablauf einmal komplett sieht.
---
## Disc-Erkennung repariert — Version 5.1.1 (01.09.2026)
> Der Commander legte **Disc 2 von „Spartacus: Gods of the Arena"** ein,
> Rippy meldete den **Film „Spartacus" von 1960**. Er schlug vor, Discs
> künftig über ihre EAN/Barcode-Nummer zu erkennen. Die Messung zeigte:
> Das braucht es nicht — **die Disc trug die richtige Antwort die ganze
> Zeit selbst mit sich, Rippy las nur eine Zeile davon.**
### Warum der Barcode nicht die Antwort war
Der Barcode steht auf der HÜLLE, nicht auf der Scheibe. Ein Laufwerk
liest die Scheibe. Für DVD und Blu-ray gibt es keinen Weg an die EAN.
(Ausnahme, notiert und nicht gebaut: Audio-CDs tragen im Subchannel eine
Media Catalog Number — das IST der UPC/EAN. Als Auffangnetz für
unbekannte MusicBrainz-Kennungen später denkbar; TMDb und OMDb können
ohnehin keine Barcode-Suche, dafür bräuchte es eine Produktdatenbank als
Zwischenstufe mit fraglicher Abdeckung deutscher Editionen.)
### Was tatsächlich kaputt war (Laufwerk G:, `SPARTACUS_GOTA_D2`, UDF, 33 759 100 928 B)
Rippy las korrekt `Spartacus: Gods Of The Arena - Disc 2` und baute
daraus Suchvarianten — der Doppelpunkt-Vorspann stand auf **Platz 2**:
```
[0] Spartacus: Gods Of The Arena - Disc 2
[1] Spartacus <- hier war Schluss
...
[5] Spartacus: Gods Of The Arena <- der richtige, nie gefragt
```
TMDb hat einen Film, der wörtlich „Spartacus" heißt → wörtlicher
Treffer → **0,95** → fertig. Der Fix vom 30.08. (`Reihenfolge JE
KANDIDAT`) griff nicht, weil er die Kandidaten-LISTE nicht anfasste; der
damalige Test benutzte einen geschönten Titel (ohne Doppelpunkt, ohne
`- Disc 2`) und lief deshalb grün.
### Die vier Ursachen (`38a3a87`, Ampel GRÜN, 225 Tests)
1. **Die Sicherheit maß die ART des Treffers, nie die QUALITÄT der
Frage.** Neu: `abdeckung()` + `MINDEST_ABDECKUNG` (0,5) — wer mehr
als die Hälfte des Titels wegwerfen musste, bekommt höchstens einen
Vorschlag (0,60) und das UI fragt nach. Maßstab ist der Titel OHNE
Verpackungs-Zusatz.
2. **Verpackungs-Zusätze galten als Wortmüll.** Neu:
`discZusatzAbtrennen()` trennt `- Disc 2` / `_D2` / `S1` ab UND merkt
sie. Vier Fallen sind mit Tests abgesichert: `Rocky 2` (nackte Zahl),
`Kill Bill Teil 2` (Teil/Part sind Titel-Wörter), `Ocean's 11`
(Apostroph-Wortgrenze — die Ein-Buchstaben-Formen `d`/`s` verlangen
deshalb einen Trenner davor), `Pulp Fiction`.
3. **Der Titelvergleich war zeichengenau.** Ein Volume-Label KANN keinen
Doppelpunkt tragen, TMDb schreibt ihn — der exakt richtige Treffer
fiel an einem Satzzeichen durch. Neu: `gleichBedeutend()`. Vergleicht
die Wort-FOLGE, nicht die Wort-MENGE (sonst wäre `New York, New York`
gleich `New York`).
4. **Rippy öffnete das Inhaltsverzeichnis der Disc und las nur die erste
Zeile.** Neu: `inhaltAusBdmt()` liest auch `<di:titleName>`.
`EP 1`/`EP 2` beweisen die Serie, `DUB GER 1`/`101 LOGO`/`104 GER FSK`
nicht (`istFolgenName` ist bewusst eng). Ab zwei Folgen ist „Serie"
BEWIESEN — dann sind die Film-Zweige der Kaskade AUS.
Disc-Nummer, Staffel und Folgenzahl stehen jetzt auch im Fenster
(Kachel neben der Erkennung) und überleben eine eigene Titelwahl.
### Der Beweis an der echten Disc
Neu: `test/messung.disc-erkennung.test.ts` (nur Windows, nur auf
Verlangen — `RIPPY_MESSUNG_DISC=G npx vitest run …`):
```
Titel "Spartacus: Gods Of The Arena - Disc 2" (Quelle: bdmt)
Disc 2 | Staffel — | Folgen 2 | Serie bewiesen: true
Inhalt: EP 1 | EP 2 | MM ITA | ILSM | DUB GER 1 | DUB GER 2 | DUB FRE 1
| DUB FRE 2 | DUB ITA | 101 LOGO | 102 DUTCH BREIN
| 103 DUTCH AGE | 104 GER FSK | 105 WARNINGS
Varianten: "…- Disc 2" -> "…Arena" -> "Spartacus Gods…" -> … -> "Spartacus"
```
Deckt sich mit der Disc-Struktur: genau zwei große Ströme (`00047.m2ts`
15,76 GB, `00048.m2ts` 14,89 GB), alles andere unter 200 MB.
**Geänderter Bestandstest:** Der alte Spartacus-Test erwartete 0,80. Er
erwartet jetzt 0,90 — durch den satzzeichen-toleranten Vergleich ist es
ein WÖRTLICHER Serientreffer. Die Absicht bleibt, die Prüfung wird
strenger.
### Das Setup 5.1.1
* `rippy-windows/dist-setup/RippySetup-5.1.1.exe`
* Größe **131 526 527 Bytes**
* SHA-256 `04CB1D302616FB67DE217AE26D50B567350177CAE9801C3FDC6FE9EA04C26F88`
* sha512 (in `latest.yml`) `GiUmrSDr/c/fTai0pJhZEr5dZuy1pUyApcFnGO1x9Kv1aKBLFXTZch8VU7rSp1wvOhZh687DhOklE4v5MIo/Pg==`
* Paket-Smoke GRÜN: `fenster=geladen kern=pid,node:24.18.1 datenbank=ok
leine=gesetzt pong=ok version=5.1.1`
* `latest.yml` und `w0.yml` liegen daneben, beide auf 5.1.1
### Das Release ist DRAUSSEN und nachgemessen
Hochgeladen ins Release `aktuell` (ID 1, Ziel
`worktree-windows-electron`), alle vier HTTP 201/204:
`RippySetup-5.1.1.exe` + `.blockmap` neu, `latest.yml` und `w0.yml`
gelöscht und ersetzt (Namenskollision zwingt zum Löschen zuerst).
**Die 5.1.0-EXE liegt bewusst weiter im Release — sie ist die
Rückfallebene**, und `latest.yml` verweist ohnehin namentlich.
Nachgemessen **anonym und von außen**, also genau so, wie eine
installierte Rippy fragt:
```
…/releases/download/aktuell/latest.yml HTTP 200, 337 B, version: 5.1.1
…/releases/download/aktuell/RippySetup-5.1.1.exe
HTTP 200, Content-Length 131 526 527
(= die Zahl in latest.yml = die lokale Datei)
…/releases/download/aktuell/…exe.blockmap HTTP 200, 137 490 B
…/releases/download/aktuell/w0.yml HTTP 200, 337 B
```
**§ 3.6 ⚠ ist beantwortet — mit NEIN:**
`…/releases/latest/download/latest.yml` liefert **HTTP 404**. Gitea 1.27
bedient das GitHub-Muster NICHT. Das feste Tag `aktuell` war also nicht
die Rückfallebene, sondern der einzige Weg — und ist genau der, den die
App über `electron-builder publish` eingebacken hat. Der Punkt kann aus
den offenen Fragen gestrichen werden.
**Zugang:** Der Upload-Token liegt in `F:\Coding Stuff\Gittea Token\rippy.txt`
(40 Hex, Recht `repository: Lesen und Schreiben`). Der über
`git credential fill` erreichbare Zugang taugt NICHT für die API — das
ist ein 13-stelliges Passwort, Gitea antwortet damit HTTP 401.
### ⚠ OFFEN: der Selbst-Update-Beweis am lebenden Objekt
Alles ist gemessen — außer dem einen Schritt, den nur die installierte
App gehen kann: **Findet die installierte 5.1.0 das Update von selbst?**
Sie prüft beim Start und einmal täglich, lädt im Hintergrund, NIE
während eines Rips, und installiert beim nächsten Beenden
(`autoInstallOnAppQuit`). Zu tun: Rippy einmal neu starten, kein Rip
dabei, dann beenden und wieder starten — danach muss in den
Einstellungen `5.1.1` stehen.
---
## ÜBERGABE an die nächste Session (31.08.2026, Sessionende)
> Diese Session ist abgeschlossen und ihr Arbeitsordner
> (`.claude/worktrees/rippy-v5-windows-4e23db`) wird abgeräumt — ALLES
> Wichtige liegt in Git, im Gitea-Release oder auf dem Desktop.
### Wo die nächste Session startet
* **Arbeitsort:** `F:\Coding Stuff\rippy` (Haupt-Checkout, Branch
`worktree-windows-electron` MIT Upstream — `git pull` genügt).
v5-Code in `rippy-windows/`; main ist reines Docker-Rippy.
* **Bau-Vorbereitung einmalig** (der Session-Ordner samt node_modules,
vendor/ und Bau-Caches ist weg): `npm install` in `rippy-windows/`
(npm 11 blockt Electrons install.js — Ausweg in BAUEN.md), fürs
PAKETIEREN `vendor/` aus `%LOCALAPPDATA%\Rippy\tools` befüllen
(BAUEN.md § „Das Setup bauen").
* **Neues Release veröffentlichen** = die VIER Dateien im Gitea-Tag
`aktuell` ERSETZEN (exe, .blockmap, latest.yml, w0.yml) — die
installierten Apps ziehen selbst nach. Schreiben an die Gitea-API:
`git credential fill` (host git.tobisniceshomelab.ddnsfree.com)
liefert Nutzer+Token.
### Was fertig und wo es ist
* **Rippy v5.1.0** komplett: W-0…W-7, drei Commander-Funde behoben,
MakeMKV-Selbst-Update (§ 9), Kino-Mix-Design durchgängig,
Einstellungen als Karten-Seite. 209 Tests, Ampel grün je Schritt.
* **Setup:** `RippySetup-5.1.0.exe` liegt auf dem DESKTOP des
Commanders UND im Gitea-Release `aktuell` (SHA-256 147D6C7E…C94F).
* **Update-Kanal live und gemessen** (latest.yml anonym, sha512/size
passen; w0.yml als Brücke für 5.0.0-w0-Altinstallationen).
* **main bereinigt** (2b77ab0, 10.039 Zeilen Windows-Standalone) und
auf die VM DEPLOYT (Container healthy, UI/API 200). Gitea hat nur
noch `main` + `worktree-windows-electron`; fünf gemergte
Alt-Branches gelöscht; lokaler Checkout synchron, ~545 MB Müll weg.
### Offene Punkte (Reihenfolge = Vorschlag)
1. **Erster echter Selbst-Update-Beweis:** 5.1.1 bauen (irgendeine
kleine Änderung), die vier Dateien im Tag `aktuell` ersetzen — die
installierte 5.1.0 muss es selbst finden, laden und beim nächsten
Start installieren (§ 6.8-Livetest; misst auch, ob Gitea 1.27 das
releases/latest/download-Muster bedient — § 3.6 ⚠).
2. **Hand am Gerät:** voller Blu-ray-Durchlauf mit neuem UI (die Disc
STECKT schon im Laufwerk, es ist wach), Auswurf-Messung
(`messung.auswurf`), DVD- und Audio-CD-Durchlauf,
Jellyfin-Sichtbarkeit.
3. **MakeMKV-Beta-Key läuft Ende September ab** — Rippy prüft täglich
und holt den neuen selbst; nur beobachten. Der Update-Knopf wird
scharf, sobald der Hersteller eine Fassung > 1.18.4 ankündigt.
4. UI-Feinschliff nach echter Nutzung (z. B. Bibliothek-Detailseite,
Rip-Nachbearbeitung „Titel nachträglich ändern — Ablage wandert mit").
### Session-Ordner abräumen (nach dem Schließen dieser Session)
```
git -C "F:/Coding Stuff/rippy" worktree remove --force ".claude/worktrees/rippy-v5-windows-4e23db"
```
---
## DEPLOYT — Update-Kanal live, altes Windows-Rippy gelöscht (31.08.2026, nachts)
> Der Abschluss des Tages, alles gemessen:
>
> **1. Gitea-Release `aktuell` ist live** (Release-ID 1, Ziel
> `worktree-windows-electron`): `RippySetup-5.1.0.exe` + `.blockmap` +
> `latest.yml` + `w0.yml`, alle HTTP 201. Nachgemessen aus App-Sicht
> (anonym, extern): `…/releases/download/aktuell/latest.yml` → 200 mit
> `version: 5.1.0`, sha512 und `size: 131370618` — die EXE antwortet
> per HEAD mit exakt dieser Content-Length. **`w0.yml` ist die Brücke
> für die alten 5.0.0-w0-Installationen** (Vorab-Versionen suchen auf
> dem w0-Kanal): gleiche 5.1.0-Daten, einmal mitziehen, danach läuft
> alles über latest.yml.
>
> **2. Kein „unsichtbarer Nutzer", kein Token — mit Absicht:** Das
> Repo ist ÖFFENTLICH lesbar (anonym HTTP 200, intern wie extern
> gemessen). Die App fragt anonym nach Updates; ein Token im Code wäre
> aus jeder installierten Rippy.exe auslesbar und damit ein Risiko
> ohne Nutzen. Sollte das Repo je privat werden, ist der saubere Weg
> ein eigener Lese-Nutzer + Token in der `updateUrl`-EINSTELLUNG (DB,
> nie im Code/Repo) — notiert, nicht gebaut.
>
> **3. So updatet sich Rippy selbst** (§ 6.8, alles schon im Code):
> Prüfung beim Start und täglich, NIE während eines Rips (nicht mal
> der Download), Installation beim nächsten Beenden/Start
> (`autoInstallOnAppQuit`). Die Standard-Adresse steckt via
> electron-builder `publish` in der App; nur https:// wird akzeptiert.
> Ab jetzt gilt: **neues Release = die vier Dateien im Tag `aktuell`
> ERSETZEN**, fertig — jede installierte App zieht nach.
>
> **4. Altes Windows-Rippy GELÖSCHT:** `main` per Fast-Forward auf den
> geprüften Aufräum-Stand `2b77ab0` (Ampel war grün; 10.039 Zeilen,
> Teil A raus, Docker-Teil B unangetastet). Anschließend VM-Deploy
> (`git pull --ff-only && docker compose up -d --build` auf Arcane) —
> die VM hing zuvor auf `05ab655`.
>
> v5 selbst lebt weiter auf `worktree-windows-electron` (Spitze diese
> Doku) — main bleibt reines Docker-Rippy (Entscheid 7/§ 11).
---
## Einstellungen neu + Version 5.1.0 (31.08.2026, nachts)
> Der Commander fand den Einstellungs-Tab „sehr mager" — jetzt ist er
> eine erklärende Karten-Seite im Kino-Mix (`b64ddf5`, 209 Tests, Dev-
> und Paket-Smoke grün): links Sektions-Navigation, rechts je Karte
> EIN Satz Einordnung plus die Felder. Neu darin: die
> Ablage-Struktur-Vorschau (Filme/Musik/ISO/roh), die gemessenen
> Encoder als Badges, eine eigene MakeMKV-Karte (Live-Schlüsselstand +
> Update-/Prüf-Knopf + Dauerlizenz), Schalter statt nackter
> Checkboxen, Klartext-Fundstellen für die API-Keys — und erstmals die
> drei **„Eigener Pfad"-Felder** (`werkzeug.*`), die der Katalog seit
> je unterstützt, denen aber das UI fehlte.
>
> **Version 5.1.0** (erste Fassung ohne Vorab-Suffix): Der Bau erzeugt
> damit erstmals `latest.yml` — der Update-Kanal aus § 6.8 ist bereit;
> die w0.yml-Kanalfalle ist Geschichte. Setup:
> `dist-setup/RippySetup-5.1.0.exe`, SHA-256
> `147D6C7E457EBB073E4EB5606B4CB0AA4514F7BE17AE91C9A68D8B0F17B1C94F`.
>
> Handwerk: `#einstellungen`/`#bibliothek` als Startanker
> (RIPPY_START_BEREICH) und RIPPY_SMOKE_WARTE_MS fürs Beweis-Bild —
> die Einstellungen und die Encoder-Messung treffen erst NACH den
> 750 ms des Standard-Screenshots ein (zweimal „leeres Feld" gejagt,
> dann gemessen: reines Foto-Timing, die App war korrekt).
> Beweis-Bild: `beweise/ui-einstellungen-neu.png`.
---
## Das Kino-Mix-Design des Commanders (31.08.2026, abends)
> Der Commander wollte am Design „extrem schrauben". Auf einem
> Design-Canvas (Claude-Artifact „Rippy Redesign") standen drei
> Richtungen zur Wahl — Kinosaal, Schaltpult, Galerie; er wählte
> **einen Mix aus Kinosaal + Schaltpult** („Das passt erstmal so").
> Der Mix ist jetzt DURCHGÄNGIG in der App (`b9de2ba`, 209 Tests,
> Dev- und Paket-Smoke grün): Übersicht, Einstellungen, Bibliothek,
> Erst-Einrichtung, Dialoge. Setup: `dist-setup/RippySetup-5.0.0-w0.exe`,
> SHA-256 `58056BF8C996451DDA182554391B996E11EB0A4A0A57036FD24389DFC34E355C`.
### Was der Mix ist
* **Vom Kinosaal:** warmes Bühnen-Schwarz (oklch, nie kaltes Grau) mit
Saallicht-Glühherden und Filmkorn, Gold als einziger Akzent, Poster
mit Goldkante und großem Schatten, Plakat-Titel in Bricolage
Grotesque, Poster-Regal als Bibliothek.
* **Vom Schaltpult:** REC-Glühen in der Kopfzeile bei laufendem Rip,
Info-Kacheln unterm Titel (MEDIUM/GRÖSSE/ERKENNUNG/**PRESET** — mit
der echten Preset-Kette eigene Wahl → allgemein → Empfehlung →
Default), die 20-Segment-Aussteuerungsanzeige in Gold mit großer
Mono-Prozentzahl, Mono-Protokollzeilen mit `>`-Präfix, und die
**LED-Systemleiste** in der Fußzeile (KERN/DATENBANK/LEINE/PONG)
statt vier Status-Kacheln — Systemfehler werden zur roten Karte
oben in der Übersicht (R4 bleibt gewahrt).
### Handwerkliches
* Schriften (Bricolage Grotesque, Instrument Sans, JetBrains Mono)
kommen als fontsource-Pakete INS BUNDLE — OFL-Lizenzen liegen unter
`bau/lizenzen/SCHRIFT-*`, QUELLEN.md ergänzt. Zur Laufzeit lädt
Rippy weiterhin nichts aus dem Netz; CSP unverändert.
* Farb- und Schrift-Tokens sind EINE Quelle: `fenster/stil.css`
(@theme) — buehne/karte/samt, gold(-hell/-tief), rec, schnee/dunst/
nebel, gruen/warn/rot. Fensterfarbe beim Start: #171412.
* Design-Arbeitsdateien liegen in `design-entwuerfe/` (im Artifact
gesichert, per .gitignore vom Repo ferngehalten).
* Beweis-Bild: `beweise/ui-kino-mix.png` — dabei nebenbei gesehen:
**Das Laufwerk ist aufgewacht** (5042-Zustand vorbei) und meldet
eine eingelegte Blu-ray (33,76 GB); die PRESET-Kachel zeigt live
`H.265 VCN 1080p`.
---
## MakeMKV-Selbst-Update + Kino-Look (31.08.2026)
> Zwei Pakete in einem Wurf (`d5ea48c`, Ampel GRÜN, 209 Tests, Dev- und
> Paket-Smoke grün): Rippy kann MakeMKV jetzt SELBST aktualisieren —
> der Ein-Klick-Ausweg aus Fund 1 — und das Fenster hat den Kino-Look.
> Neues Setup am dokumentierten Ort: `dist-setup/RippySetup-5.0.0-w0.exe`,
> SHA-256 `75C1B613A2DD6E33C09764838F7B68B21EFB0BB8B2CCB78B2EC0971A49602706`.
### MakeMKV-Selbst-Update (§ 9) — `kern/werkzeuge/beschaffen.ts`
* **Quellenkette nachgemessen (31.08.):** Hersteller-Seite weiterhin
HTTP 525 (tot), Forum 200/0,2 s → 1.18.4, Archiv 200/7,8 s → 1.18.2.
Regeln wie in der Vorlage `tools/beschaffen.py`: Hersteller maßgeblich,
sonst HÖCHSTE Nummer; 8-s-Zeitgrenze, 2 Versuche.
* **Geprüft wird, was ankommt:** MZ-Kopf + Versions-Ressource
(CompanyName „GuinpinSoft inc", ProductName „MakeMKV", FileVersion
„v1.18.4" — an der echten Datei gemessen, MakeMKV signiert nicht).
Download in `.neu`, erst nach Prüfung getauscht; Mindestgröße 1 MB
fängt Fehlerseiten mit HTTP 200.
* **Gestartet wird über das Haupt** (`shell.openPath` → UAC): MakeMKVs
Installer verlangt Adminrechte (CreateProcess bräche mit Fehler 740 ab,
beschaffen.py-Messung); durch die Erhöhung läuft er über den
AppInfo-Dienst und hängt bewusst NICHT an der Prozess-Leine. Das Haupt
startet nur `.exe`-Dateien aus Rippys eigenem Downloads-Ordner.
* **Danach wartet der Kern** (alle 15 s, max. 20 min) darauf, dass die
Versions-Ressource der gefundenen makemkvcon-EXE wechselt — dann läuft
die Schlüsselkette automatisch neu und die Werkzeuge werden neu gemessen.
* **Ehrlicher Zweig:** installierte == neueste angekündigte Fassung
(heute der Fall: beide 1.18.4) → KEIN sinnloser Download, sondern der
Satz „sobald der Hersteller eine neuere veröffentlicht, holt dieser
Knopf sie". `SchluesselStand` trägt jetzt die Version als Feld.
* Neue Nachrichten: `makemkv-update`/`schluessel-pruefen` (Fenster→Kern),
`beschaffung-status` (Kern→Fenster), `installer-starten`/
`installer-ergebnis` (Kern↔Haupt). Tests: `test/beschaffung.test.ts`
(Quellenketten, höchste-Version, MZ/Ressourcen-Prüfung, Quellen-Ausfall).
### Kino-Look — `fenster/App.tsx`
* Laufwerks-Kachel als Bühne: TMDb-**Backdrop** als Panorama (w780,
abgedunkelte Verläufe), Poster (w342), großer Titel, Beschreibung
(2 Zeilen), Badges für Typ/Größe/Sicherheit, Phasen-Label
(„rippt (verlustfrei)" …), Prozent groß + Restzeit, **aufklappbares
Protokoll** (die Statuszeilen des Vorgangs, clientseitig gesammelt).
* Bibliothek als **Poster-Regal** (Grid, Hover-Zoom, Tooltip mit
Ablageort/Datum/Dauer) — dafür Schema 3: Spalte `poster_pfad` samt
Migration per `PRAGMA table_info` (Bestandseinträge bleiben, nur ohne
Poster). Die Pipeline schreibt den Posterpfad jetzt mit.
* Schlüssel-Karte mit „MakeMKV aktualisieren …"-Knopf (nur bei
abgelehntem Key), „Neu prüfen", Beschaffungs-Statuszeile + Balken,
MakeMKV-Version als Badge.
* `MetaTreffer`/`DiscInfoStand` um `backdropPfad`/`beschreibung`
erweitert (OMDb-Weg ohne Backdrop — fällt aufs Poster zurück).
* Beweis-Bild: `beweise/ui-kino-look.png` (Übersicht im Smoke-Profil —
zeigt auch den 5042-Klartext, siehe unten).
### Beobachtungen dieser Runde
* **Das Laufwerk ist WIEDER im 5042-Zustand** („beantwortet keine
Medien-Abfragen mehr") — wie vor dem Commander-Test. Die Kachel sagt
jetzt die Abhilfe im Klartext (Taste am Laufwerk / Neustart). Für den
nächsten Rip-Test muss das Laufwerk erst wieder aufwachen.
* **`win-unpacked`-Sperre verstanden und dokumentiert (BAUEN.md):** Nach
einem Paket-Smoke hält ein Filter-Treiber `app.asar` mitunter
stundenlang (kein Prozess sichtbar; `dist-setup` von gestern war heute
wieder frei). electron-builder scheitert dann mit `EBUSY` — bei
**Exit-Code 0**! Ausweg dokumentiert: frisches Ausgabeverzeichnis,
Setup-EXE nach `dist-setup/` kopieren. `.gitignore` deckt jetzt
`dist-setup*/`.
---
## Erster Commander-Test bestanden — drei Funde, drei Fixes (30.08.2026, spät)
> Der Commander hat v5 am echten Gerät getestet: **„lief alles sofort"**
> — und drei Dinge gemeldet. Alle drei sind behoben (`c5dd90c`, Ampel
> grün, 192 Tests) und als Testfälle mit den LIVE gemessenen Fixtures
> festgehalten. Neues Setup: `dist-setup-frisch/RippySetup-5.0.0-w0.exe`,
> SHA-256 `C8225690F02AC544E3E25957E7D34BADBC296C31D44362905A5A0D1DE2104F72`.
### Fund 1 — „Der MakeMKV-Key ist korrekt, wird aber als ungültig angezeigt"
Beides stimmt, und die Messung zeigt warum: Der aktuelle Forum-Key ist
in Ordnung — **MakeMKV 1.18.4 ist ZU ALT für ihn** (makemkvcon meldet
5020 UND 5021 zusammen; live am Gerät nachgestellt). Die alte Prüfung
brach beim ersten Code ab und beschuldigte den Schlüssel. Jetzt: Alle
Codes werden gesammelt, **5021 schlägt 5020**, die MakeMKV-Version wird
aus MSG 1005 mitgelesen, und die Kachel sagt den wahren Satz samt
Abhilfe (MakeMKV von makemkv.com aktualisieren — danach hinterlegt
Rippy den Key automatisch neu). Der Beta-Modus ohne Key rippt weiter —
deshalb ist der Zustand jetzt GELB (Warnung), nicht rot. Wichtig:
Rippy NIMMT den abgelehnten Key bewusst ZURÜCK — mit abgelehntem Key
verweigert MakeMKV alles, ohne läuft es (gemessen).
### Fund 2 — „Komprimieren auf der CPU statt der GPU"
Der alte Default `H.265 MKV 1080p30` ist ein CPU-Preset. Neu:
**gemessene Preset-Empfehlung** (`gemeinsam/preset-empfehlung.ts`) aus
den echten Backends und der echten Preset-Liste des eigenen HandBrake —
VCN → NVENC → QSV → CPU, nur Namen, die wirklich existieren. Auf dem
Commander-PC heißt das: DVD/Blu-ray → `H.265 VCN 1080p`, 4K →
`H.265 VCN 2160p 4K` (die RX 9070 XT hat den Spartacus-Beweis in
2,1 min geschafft). Kette: eigene Wahl → allgemeines Preset →
Empfehlung → CPU-Default; die Auswahlfelder zeigen
„automatisch: <Empfehlung>".
### Fund 3 — „Keine Alternative als Metadaten wählbar"
Gebaut (§ 5 Schritt 4): **„Ändern …"** an der Laufwerks-Kachel öffnet
die TMDb-Suche (Filme + Serien, mit Postern); ein Klick übernimmt —
die Wahl gilt als 100 % und steuert Ablage-Ordner, NFO und Poster.
Dazu: Poster in der Kachel (CSP um image.tmdb.org erweitert) und eine
Restzeit-Schätzung am Fortschritt.
### Notizen
* Das UI ist bewusst die W-5-Grundausstattung plus diese Runde — ein
größerer optischer Ausbau (Kino-Look wie die Web-Fassung) ist ein
eigenes Paket, wenn gewünscht.
* `dist-setup/` (der ALTE Bau) hält Windows gerade eine Dateisperre auf
`win-unpacked\resources\app.asar` (kein Prozess sichtbar — vermutlich
Virenscanner); deshalb liegt der frische Bau in `dist-setup-frisch/`.
Nach einem Neustart lässt sich `dist-setup/` löschen.
---
## Rippy v5 — ALLE Etappen W-0 bis W-7 gebaut, Ampel grün (30.08.2026, abends)
> **An einem Tag von der leeren Wiese zum installierbaren Programm.** Alle
> acht Konzept-Etappen sind gebaut, jede mit grüner Ampel gepusht
> (Commits `c539e65` W-0 → `a45069a` W-1 → `844bca3`+`ca338ad` W-2 →
> `3dc296f`+`e840b7f` W-3 → `5f386e5`+`a9fa8fa` W-4 → `c78ff22` W-5 →
> `2d9cef2` W-7 → `9fa3df5` W-6). **185 Tests grün**, Smoke-Beweis auch
> aus dem GEPACKTEN Programm grün. Parallel ist Entscheid 7 umgesetzt:
> Branch `aufraeumen-windows-standalone` (`cedab61`) räumt den
> Windows-Anteil aus `main` (10.039 Zeilen; eigener SAVEPOINT-Eintrag
> dort). **Der Commander merged ihn nach eigener Prüfung nach `main`.**
### Die Beweise des Tages (alles gemessen, nichts behauptet)
* **Setup gebaut:** `rippy-windows/dist-setup/RippySetup-5.0.0-w0.exe`
— 130,8 MB, SHA-256
`07D39D6BAB74A47C21AA53D847FB5A5DB6990AE9FA1CBC7BE814B51050883115`,
mit Blockmap; die mitgelieferten Werkzeuge (HandBrakeCLI, flac samt
libFLAC.dll) und die Lizenztexte liegen drin. Das GEPACKTE Programm
besteht den Smoke und startet mit der Erst-Einrichtung.
* **W-3 end-zu-end am echten Material:** Der 17,71-GB-Spartacus-
Rohschnitt (die Datei, die in rc11 am falschen Schalter starb) wurde
mit Preset „H.265 VCN 1080p" in **2,1 Minuten auf 1,17 GB**
komprimiert und liegt als `E:\Rippy-v5-Beweis\Filme\Spartacus - Gods
of the Arena - Disc 2 (2011)\` samt `movie.nfo`.
* **W-4 live gegen die echten APIs** (Schlüssel aus den
Docker-Settings der VM, nicht persistiert): Akira→95 % ·
Evangelion 2.22→80 % (der dokumentierte Fund) · Spartacus GotA→
**Serie** 90 % · „Bd Evg D2"→ehrlicher 60-%-Vorschlag. Zwei
Kaskaden-Schwächen dabei LIVE gefunden und gehärtet (gekürzte
Kandidaten schlugen die spezifische Serie; OMDb erfand zu Kryptischem
selbstbewusst Filme — jetzt Wort-Jaccard-Gate).
* **W-1 am echten BU40N:** Laufwerk, Typ (bluray, 33,76 GB), Modell,
Serial — exakt die `beweise/`-Werte, jetzt aus dem produktiven
Treiber. `taskkill /F` aufs Haupt → der Kern stirbt mit (Leine).
* **MusicBrainz-DiscID** gegen die echte AC/DC-Kennung
(`3KVSIWn_…`) getestet — samt der 150-Frames- und 2048er-Fallen.
### Was NUR eine Hand am Gerät noch beweisen kann
Das Laufwerk steckt seit dem abgebrochenen rc11-Rip vom Mittag im
**5042-Zustand**: MakeMKV sieht GAR KEIN Laufwerk (leere DRV-Liste),
und inzwischen verweigert auch der Storage-Stack die Medien-Abfragen —
Rippy v5 zeigt dazu den Klartext-Grund samt Abhilfe in der
Laufwerks-Kachel (statt eines stummen „unknown"). **Abhilfe: Disc über
die Laufwerkstaste auswerfen und neu einlegen, oder das USB-Kabel kurz
ab- und anstecken.** Danach stehen bereit:
`RIPPY_MESSUNG_RIP=1`-Lauf (voller Blu-ray-Rip, `messung.rip.test.ts`)
und der Auswurf-Beweis (`messung.auswurf.test.ts`). Ebenfalls offen:
ein DVD- und ein Audio-CD-Durchlauf (kein Medium da), „ein Film ist in
Jellyfin sichtbar" (Jellyfin-Zugang), die automatische
MakeMKV-Beschaffung im Setup (heute: klarer Hinweis auf makemkv.com)
und die `latest.yml`-Messung am ersten echten Gitea-Release.
### Nächste Schritte für den Commander
1. Laufwerk wecken (siehe oben), dann in Rippy v5 „Rippen" drücken —
oder die zwei Messläufe aus `rippy-windows/BAUEN.md` fahren.
2. `aufraeumen-windows-standalone` prüfen und nach `main` mergen
(Ampel ist grün; die VM merkt davon nichts, bis dort gepullt wird).
3. Fürs erste echte Release: Version ohne `-w0` bauen (dann entsteht
`latest.yml` statt `w0.yml`), die drei Dateien ins Gitea-Release-Tag
`aktuell` laden.
4. **Termin § 9.1:** MakeMKVs Beta-Key läuft Ende September ab — die
Schlüsselkette prüft täglich und warnt im Dashboard.
---
## Etappe W-0 — das Gerüst steht (30.08.2026)
> **Rippy v5 hat sein Fundament.** Auf dem Branch `worktree-windows-electron`
> steht jetzt neben dem Konzept das erste lauffähige Programm: Electron 44,
> durchgehend TypeScript, drei Prozesse, eine Datenbank, die Prozess-Leine.
> Kein Python, kein Docker-Code, kein HTTP-Server — genau wie
> `KONZEPT-WINDOWS.md` es festlegt. 18/18 Tests grün, Smoke-Beweis grün.
### Was gebaut wurde (`rippy-windows/`)
* **Drei Prozesse, wie im Konzept § 4.1:** Das Haupt (`src/haupt/`) verwaltet
Fenster und Kern; der Kern (`src/kern/`, ein Electron-utilityProcess) macht
die Arbeit; das Fenster (`src/fenster/`, React) zeigt nur an. Fenster und
Kern reden über einen **direkten MessagePort** — kein localhost, kein Port,
kein Polling.
* **Die Datenbank läuft** — `node:sqlite`, wie in `beweise/` gemessen, und
sie hat genau **einen** Zugriffsort (`kern/speicher/db.ts`, § 6.7). Der
Kern schreibt beim Start eine Einstellung und liest sie zurück; „geöffnet"
allein gilt nicht als Beweis.
* **Die Prozess-Leine ist scharf:** Das Haupt legt die Windows-Arbeitsgruppe
aus `beweise/leine.js` an und hängt sich selbst hinein — alles, was Rippy
je startet, stirbt mit ihm. Eine bewusste, im Code begründete Abweichung
von der Ordnerliste § 4.2: Die Leine liegt in `haupt/leine.ts`, nicht im
Kern — das Job-Handle muss bei dem Prozess liegen, dessen Tod alles
mitreißen soll (§ 4.1: „ALLES stirbt mit ihm").
* **Wächter-Tests nach § 4.3** — mechanisch, wie versprochen: koffi nur an
den zwei benannten Orten (R1) · kein leerer catch, kein catch, das leere
Listen erfindet (R2/R4) · kein HTTP-Server (§ 4.1) · `node:sqlite` nur in
`db.ts` (§ 6.7). Dazu Tests für Datenbank und Nachrichten-Schema.
* **Sicherheitsrahmen:** contextIsolation, Sandbox, kein Node im Fenster,
strikte CSP im gebauten HTML, Einzelinstanz-Sperre, Kern-Neustart-Wache
(stirbt der Kern, startet ihn das Haupt neu — höchstens dreimal je Minute,
danach steht der Fehler sichtbar im Fenster).
### Der Beweis
`npm run smoke` startet das ECHTE Programm und prüft alle fünf Punkte
maschinell — Ausgabe vom 30.08.2026 auf dem Commander-PC:
```
SMOKE: OK — fenster=geladen kern=pid:13676,node:24.18.1 datenbank=ok leine=gesetzt pong=ok version=5.0.0-w0
```
Dazu nachgemessen, nicht geglaubt: `taskkill /F` auf den Haupt-Prozess
(ohne `/T` — der Absturz-Fall, der in rc10 verwaiste makemkvcon
hinterließ) → **der Kern ist danach tot.** Der Wirkungs-Test mit einem
echten Werkzeug-Kind folgt in W-2, wenn es erstmals eines gibt.
Das W-0-Fertigkriterium aus § 12 — „Ein Fenster geht auf, der Kern läuft,
ein Testlauf ist grün" — ist damit erfüllt.
### Die Ampel prüft v5 mit
`ci.yml` läuft jetzt fest auf **Node 24** (vorher: Zufalls-Node des
Runners; `docker/ui` unter Node 24 vorher lokal nachgemessen). Die Ampel
baut und testet `rippy-windows/` auf Linux mit — nur der Smoke bleibt ein
Windows-Beweis und steht deshalb hier mit Ausgabe.
### Stolperdrähte, dokumentiert in `rippy-windows/BAUEN.md`
npm 11 blockt Electrons Install-Skript — nach `npm install` fehlt die
`electron.exe`, `node node_modules/electron/install.js` holt sie nach
(§ 3.5, beim Bau erneut bestätigt) · `@vitejs/plugin-react` 6 verlangt
Vite 8, deshalb die 5er-Linie · TypeScript fest auf 5.9.3 gepinnt (die
neue Go-Fassung 7.x ist eine bewusste Entscheidung für später).
### Was W-0 absichtlich NICHT kann
Kein Tray, kein Autostart, kein Update, keine Laufwerks-Erkennung — das
sind W-1 bis W-6. Bis das Tray kommt (W-5), gilt: Fenster zu = Rippy zu.
### Nächste Schritte
1. **W-1 — Laufwerk:** `kern/laufwerk/win32.ts` aus dem § 3.2-Beweis,
Wache im 3-Sekunden-Takt, Disc-Typ, Auswerfen mit Nachsehen.
2. **Parallel, eigener Branch von `main`:** das Aufräumen nach § 11
(A → C, Remote-Worker bleibt) — noch nicht begonnen.
3. **Termin im Blick (§ 9.1):** MakeMKVs Beta-Key läuft **Ende September
2026** ab.
---
## v4.0-rc11 — der 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.
### Gemessen, nicht vermutet
Elternprozess startet makemkvcon, dann wird er hart beendet (`taskkill /F`
ohne `/T` — genau das, was beim Dienst-Stopp und beim Drueber-Installieren
passiert):
```
ohne Leine makemkvcon PID 15368 vor dem Kill: True danach: True
mit Leine makemkvcon PID 43608 vor dem Kill: True danach: False
```
Windows raeumt Kindprozesse nicht auf. Zwei solche Waisen standen waehrend
der Messungen auf diesem Rechner.
### Zwei Riegel
* **Die Leine** — eine Arbeitsgruppe (Job Object). Stirbt Rippy, sterben
makemkvcon, HandBrake und flac mit. Auch beim Absturz, auch per
Taskmanager. Einmal beim Start gesetzt, deckt sie jeden Werkzeugaufruf ab.
* **Der Aufraeumer** — beendet beim Start alles, was kein Elternprozess mehr
haelt. Fuer das, was eine aeltere Fassung hinterlassen hat. Nur
**elternlose**: Ein makemkvcon eines laufenden Rippy bleibt unangetastet,
und `makemkv.exe` (die Oberflaeche) steht gar nicht erst auf der Liste.
Am echten Fall nachgestellt — Waise erzeugt, Rippy gestartet, Waise weg:
```
1. Waise nach dem Drueber-Installieren: PID 36400 lebt = True
2. Rippy erkennt sie als herrenlos: True
3. Beim Start weggeraeumt: makemkvcon64.exe
4. Waise lebt danach noch: False
```
Dienst, Fenster und das Aufraeum-Skript der Deinstallation loesen sich aus der
Gruppe heraus — die drei muessen Rippy ueberleben.
---
## v4.0-rc9 — der Rip, der sich selbst dazwischenfunkte (29.08.2026)
> **Ein zweiter Rip startete waehrend der Kompression — in ein Laufwerk, dessen
> Schublade gerade herausfuhr.** 941 Tests gruen.
### Was der Commander sah
makemkvcon endete mit Code 11 — letzte Meldung:
Das Öffnen der Disk schlug fehl — keine MKV-Datei entstanden
Und dazu: *„Der Rip an sich war bereits fertig, bei der Komprimierung passiert
das."* Das klang nach einem Fehler in der Kompression. Es war keiner.
Entscheidend war, was in der Meldung **fehlte**: kein „Ursache:". Haette
MakeMKV den Geraetepfad nicht gekannt, stuende dort Meldung 2024. Bleibt genau
eine Erklaerung — es lag keine Disc im Laufwerk.
### Der Ablauf
1. Rip fertig → Rippy wirft die Disc aus (so ist es eingestellt)
2. Der Job geht auf `transcoding` — **und ab hier meldete Rippy das Laufwerk
als frei.** Die Pruefung kannte nur „wartet" und „laeuft"
3. Die Disc-Wache sieht den Auswurf als Statuswechsel und meldet „eingelegt"
4. Die Vollautomatik startet einen **zweiten** Rip — auf die herausfahrende
Schublade
Der zweite Rip lief ins Leere. Auf dem Bildschirm sah das aus, als sei die
Kompression gescheitert. Sie lief die ganze Zeit ungestoert weiter.
### Drei Aenderungen
* **Die Automatik haelt sich zurueck, bis der Vorgang durch ist** — die
Kompression zaehlt jetzt dazu. Von Hand darf der Commander weiterhin alles,
und die Disc laesst sich waehrend der Kompression weiterhin auswerfen: Da
haelt niemand das Laufwerk.
* **Erst nachsehen, dann rippen.** Liegt keine Disc drin, sagt Rippy das in
einem Satz — statt zwei Minuten in makemkvcon zu laufen und mit Code 11 zu
enden. Wenn das Nachsehen selbst scheitert, wird trotzdem gerippt: „ich
weiss es nicht" darf nie zu „es geht nicht" werden.
* **Meldung 5010 wird uebersetzt.** MakeMKVs Sammelmeldung „Das Oeffnen der
Disk schlug fehl" sagt fuer sich nichts.
---
## v4.0-rc8 — Windows vollständig (29.08.2026)
> **Der Rundgang durch die Docker-Reste ist durch, und die Audio-CD-Lücke ist
> zu.** 915 Tests grün.
### Die letzte Lücke: Audio-CDs
`cdparanoia` und `abcde` gibt es für Windows nicht — ein Nachbau ihrer
Shell-Logik wäre ein zweites Projekt gewesen. Gebaut ist stattdessen der Weg,
den Windows selbst anbietet:
* **Lesen** macht Rippy selbst über zwei Win32-Steuercodes. Eine Audio-CD hat
kein Dateisystem; die `Track01.cda`, die Windows zeigt, sind 44 Byte große
Platzhalter. Die Musik liegt roh in 2352-Byte-Sektoren.
* **Kodieren** macht der FLAC-Encoder, den Rippy beim Einrichten holt
(offizieller Xiph-Spiegel, Fassung 1.5.0). Kein Pflichtwerkzeug: Ohne ihn
läuft alles außer Audio-CDs.
Gemessen: FLAC 1.5.0 geladen, zwei Spuren gerippt, **und FLAC selbst bestätigt
seine Dateien** (`flac -t`, Code 0). Am echten Laufwerk gegengeprüft, dass das
Inhaltsverzeichnis gelesen wird — die eingelegte Blu-ray meldet sich korrekt
als *Daten*-Track und wird nicht als Musik behandelt.
### Und die Titel dazu
Die Dateien heißen nicht mehr `Track 01.flac`. MusicBrainz erkennt eine CD an
der **Lage ihrer Spuren** — daraus wird ein Fingerabdruck gerechnet, und der
führt zu Album, Interpret, Jahr und jedem Titel.
Belegt ohne Audio-CD, weil MusicBrainz zu jeder bekannten Kennung die
Spurlage herausgibt, aus der sie gerechnet wurde:
```
Spurlage von „Back in Black" (abgefragt) → 3KVSIWn_ewv9z0cCizmOJzDWtsQ-
MusicBrainz sagt dazu → 3KVSIWn_ewv9z0cCizmOJzDWtsQ-
```
Die ganze Kette am Stück gemessen — echte Spurlage, echte Abfrage, echter
Encoder:
```
Ordner ACDC - Back in Black
Dateien 01 - Hells Bells.flac, 02 - Shoot to Thrill.flac, …
Tags TITLE / ARTIST / ALBUM / ALBUMARTIST / DATE / TRACKNUMBER
(mit metaflac aus der fertigen Datei zurückgelesen)
```
Zwei Fallen dabei: Die Kennung rechnet **mit** den 150 Frames Vorlauf, das
Lesen **ohne** — wer das verwechselt, bekommt eine Kennung, die niemand kennt,
und zwar ohne Fehlermeldung. Und `AC/DC - Back in Black` hätte als Ordner
einen Unterordner aufgemacht; die Band heißt nun mal so.
Zwei Fallen stecken in der Sache, beide dokumentiert und beide im Code
begründet: Die Leseadresse zählt in **2048er**-Einheiten, obwohl ein
Audio-Sektor 2352 Bytes hat. Und `flac.exe` braucht `libFLAC.dll` daneben —
mit nur der exe endet jeder Aufruf mit „DLL nicht gefunden", ohne eine Zeile
Ausgabe.
### Was der Rundgang sonst noch fand
**`/dev/{name}` in drei Endpunkten.** Das UI ruft sie mit der Kennung `G` auf,
gebaut wurde `/dev/G` — **Auswerfen und „Disc scannen" antworteten unter
Windows immer mit 404.** Beide Treiber lösen die Kennung jetzt selbst auf.
**`os.path.isdir("/app")` zum zweiten Mal**, jetzt in `ablauf.py`: Rippy hielt
sich für einen *fremden* Worker.
**`shutil.which` in `schluessel.py`** — ausgerechnet im Modul, das es nur unter
Windows gibt. Die Disc-Schlüssel-Automatik für 4K-UHD lief nie an.
Dazu ein Wächter-Test: Er prüft ab sofort **mechanisch**, dass im Windows-Weg
kein Container-Pfad ohne Begründung steht.
### HandBrakes „Code 0"
Code 0 heißt **Erfolg**. Rippy meldete trotzdem „fehlgeschlagen", weil die
Datei nicht am erwarteten Ort lag: HandBrake bestimmt den Container aus dem
**Preset**, nicht aus der Endung. Jetzt wird er erzwungen.
## Letzter Stand davor: v4.0-rc7 — vier Befunde, drei mit derselben Wurzel (29.08.2026)
> Setup auf dem Desktop, `7b0c41d`, **852 Tests grün**.
### Deine vier Fragen
**Drüberinstallieren ging nicht zuverlässig.** Rippy startet mit Windows, läuft
also fast immer — dann ist `Rippy.exe` gesperrt, und die neue Fassung landete
als `Rippy.exe.neu` daneben, mit der Meldung *„wird beim nächsten Start
übernommen"*. Die hat niemand eingelöst: `.neu` kam im ganzen Projekt genau
einmal vor, an der Stelle, die es schrieb. Jetzt wird der laufende Rippy
vorher beendet; erhalten bleiben Einstellungen, Jobs und Keys.
**Updater auf dein Gitea:** sinnvoll, aber erst nach einem echten
Release-Weg — und mit leer vorbelegter Quell-Adresse, damit ein
weitergegebener Rippy nicht bei einem Fremden nach `192.168.178.153` sucht.
Wartet auf dein Ja.
**Drei Linux-Reste** im Windows-Betrieb: Rippy hielt sich für einen *fremden*
Worker (`/app` als Container-Kennzeichen), die Schlüssel-Auskunft blieb
dauerhaft „unbekannt", und die Rohdaten-Suche fand nie etwas — „Rohdaten
mitlöschen" löschte nichts.
**Durchsuchen-Knopf** in Ablage und Arbeitsverzeichnis, wie im Installer.
### Das Bildschirmfoto: drei Fehler, eine Ursache
TYP „DISC" STARTZEIT „1.1.1970" STATUS „running" Aktiv (0)
⚠️ **Mein Fehler vom selben Vormittag.** Ich hatte `type` und `startTime` ins
Job-Ereignis aufgenommen — mit Feldnamen, die es in der Datenbankzeile nicht
gibt (dort: `disc_type`, `created_at`). Statt eines *fehlenden* Feldes kam ein
*leeres*, und `new Date(null)` ist der 1.1.1970. Aus „offensichtlich kaputt"
wurde „sieht plausibel aus".
Daran hingen zwei weitere Symptome: Weil der Status roh als `running` ankam
statt als `processing`, gab es keinen „aktiven Job" — also keine Kachel, also
**keinen Abbrechen-Knopf** und „Aktiv (0)". Der Knopf steht jetzt zusätzlich
in der Job-Zeile.
Mein Test hat nichts davon gefunden: Ich hatte seine Beispielzeile selbst
erfunden, mit meinen falschen Feldnamen. **Ein Test, der dieselbe Annahme
macht wie der Code, prüft nichts.**
### Und die Disc: drei Listen, nur eine mit Disc
`/devices` hängte die erkannte Disc an, der Ereignis-Wächter und der
Schnappschuss nicht — und das Dashboard liest den Schnappschuss. Ein Test
zählt jetzt die Aufrufe; bei zwei ist er rot.
### Nachtrag: die Wartezeit ist jetzt sichtbar
> „das die disc erkennung noch läuft muss sichtbar sein"
Die Erkennung dauert rund zwei Minuten (`makemkvcon info` läuft in seine
120-Sekunden-Grenze; gemessen: Disc nach 119 s da). Zwei Minuten, in denen
**nichts** zu sehen war — das sah aus wie ein leeres Laufwerk.
Der Zustand war sogar schon da: Rippy merkt sich beim Start „läuft gerade".
Nur ließ die Laufwerksliste so einen Eintrag komplett weg, damit keine
halbfertige Disc-Karte erscheint. Richtig gedacht, falsch gelöst.
Jetzt: eine Karte mit Spinner („Disc wird gelesen — Laufwerk G:") und eine
eigene Zeile im Server-Status — ohne einen Titel zu behaupten, den es noch
nicht gibt.
**Der Haken dahinter:** Genau während der Erkennung hält makemkvcon das
Laufwerk, `/devices` braucht dann **14 s** statt der 5 s Zeitgrenze. Der
Schnappschuss meldete „konnte nicht nachsehen", und nach einem frischen Laden
blieb der Bildschirm leer — ausgerechnet in der Phase, die sichtbar sein
soll. Rippy antwortet dort jetzt ohne Laufwerkszugriff: letzter bekannter
Stand plus die aktuelle Marke.
### Beobachtung am Rande
Beim Testen blieben zweimal **verwaiste `makemkvcon64`-Prozesse** aus
abgebrochenen Läufen stehen. Solange die leben, blockieren sie **jeden**
Laufwerkszugriff. Nicht angefasst — sag Bescheid, wenn Rippy die beim Start
aufräumen soll.
### Was offen bleibt
**Dein MakeMKV 1.18.4 ist zu alt** für den aktuellen Beta-Key (unverändert
seit rc5); makemkv.com antwortet weiter mit HTTP 525.
## Letzter Stand davor: v4.0-rc6 — leerer Bildschirm behoben (29.08.2026)
> **Dein Befund war zweimal richtig und einmal irreführend:** Der Bildschirm
> war wirklich leer — aber der Rip lief. Man sah ihn nur nie.
### Der leere Hintergrund
Nachgestellt auf einem Testdienst hier, nicht bei dir. Nach dem Klick auf
„Rippen starten" stand in der Browser-Konsole:
TypeError: Cannot read properties of undefined (reading 'toUpperCase')
Und währenddessen, direkt an der Schnittstelle gemessen:
status: processing · progress: 12
**Er startet also sehr wohl.** Die Oberfläche war nur weg, bevor sie es zeigen
konnte.
Die Ursache: Das Ereignis „Job angelegt" trug nur Status, Fortschritt, Titel
und Fehler — **keinen Typ**. Die Oberfläche fügt so einen halben Job in ihre
Liste ein, das Live-Log liest `job.type.toUpperCase()`, und React baut bei
einem Fehler im Zeichnen den **ganzen** Baum ab. Eine Fehlergrenze, die das
auffängt, gab es in diesem Projekt nirgends — deshalb leerer Bildschirm ohne
jede Meldung.
Drei Reparaturen, weil es drei Fehler waren:
1. **Das Ereignis trägt den Job** — Typ, Laufwerk und Startzeit fahren mit.
Sie ändern sich nie, kosten also kein zusätzliches Ereignis; ohne die
Startzeit stand in der Jobliste sekundenlang „Invalid Date".
2. **Die Oberfläche verträgt sein Fehlen.** Zeile 108 derselben Datei hatte
die Absicherung längst, Zeile 48 nicht.
3. **Eine Fehlergrenze.** Jetzt steht da, was los ist, die Navigation bleibt
bedienbar, und der Hinweis sagt das Wichtigste: *laufende Rips gehen
weiter*.
Warum es niemand gefunden hat: Die Tests reichten der Vergleichsfunktion
immer ihre eigenen Wörterbücher herein — **die Kurzform selbst war nie
geprüft.** Jetzt bewacht ein Vertrag die Felder mechanisch.
### Der zweite Fund: `F:\app\temp`
Beim Aufräumen des Testlaufs fand ich 436 MB Rohdaten in einem Ordner namens
`app` auf dem Laufwerk, von dem Rippy gerade lief. Zwei weitere
Container-Wurzeln:
RAW_DIR = /app/temp/raw
MEDIA_ROOT = /app/media
Und schlimmer als der falsche Standard: Die Prüfung verwarf **auch eine
ausdrückliche Wahl**. `D:\Roh` liegt nicht unter `/app/media`, also fiel es
still zurück.
**Damit kam der Arbeitsordner, den du gestern bestellt hast, unter Windows nie
an.** Der Dialog zeigte ihn, das Setzen ging, der Worker ignorierte ihn — ohne
ein Wort. Dasselbe galt fürs Ziel: Deine UNC-Freigabe liegt unter keiner
lokalen Wurzel, die fertige Datei wäre in `X:\app\media\bluray` gelandet.
Jetzt kommen beide Wurzeln aus dem Betrieb. Im Container ändert sich nichts.
### Aufgeräumt
Ich habe für die Prüfung zweimal einen echten Rip auf deinem Laufwerk
gestartet und beide sofort abgebrochen. Die 436 MB Rohdaten und der Ordner
`F:\app` sind entfernt, die Testdienste beendet.
## Letzter Stand davor: v4.0-rc5 — **Rippy rippt** (29.08.2026)
> **Der erste bewiesene Rip unter Windows.** Dein Fehlerbericht enthielt drei
> Fehler auf einmal — alle drei gefunden, behoben und an deinem Laufwerk
> nachgemessen.
### Der Beweis
ripping.run_makemkv(\\.\G:, …, titel="3")
Status success · Code 0 · „Evangelion 2.22_t03.mkv" 217,9 MB
MSG:5036 „Das Kopieren wurde abgeschlossen. 1 Titel wurden gesichert."
Bewusst der kürzeste Titel (127 s), damit die Probe Sekunden dauert und deine
Platte nicht 33 GB kostet. Die Probedatei ist wieder gelöscht.
### 1. Die Quellenangabe — das war der Abbruch
dev:\\.\G: → „Unknown device" → Das Öffnen der Disk schlug fehl
dev:G: → 68 Titel, „erfolgreich abgeschlossen"
Rippy übergab MakeMKV den Geräte-Namensraum `\\.\G:`. Den braucht Windows für
die Laufwerksabfragen — MakeMKV kennt ihn nicht, es will den Buchstaben. Unter
Linux (`/dev/sr0`) war dieselbe Zeile richtig. Sie stand an **vier** Stellen.
### 2. „Ö" statt „Ö"
Das ist `Ö` als UTF-8, gelesen als Windows-Zeichensatz. Rippy stellt seine
Ausgabe beim Start auf UTF-8 (sonst stirbt der Start an einer Umlaut-Zeile),
las MakeMKVs Antwort aber im Zeichensatz des Systems. Jetzt wird die Kodierung
bestimmt statt angenommen.
### 3. Die Ursache fehlte im Fehlertext
Rippy suchte in MakeMKVs Meldungen nach **englischen** Textbausteinen. Bei dir
meldet MakeMKV deutsch — also traf keiner, und übrig blieb die nichtssagende
letzte Zeile. Jetzt zählen die Meldungs-Nummern; die gelten in jeder Sprache.
### Nebenbefund: der Beta-Key lag zweimal am falschen Ort
Erst schrieb Rippy ihn nach `/root/.MakeMKV` — ein Container-Pfad. Nach dessen
Reparatur blieb „Testzeitraum abgelaufen" trotzdem stehen. Nachgesehen:
C:\Users\TobisPC\.MakeMKV\ nur _private_data.tar
HKCU\Software\MakeMKV app_UpdateLastCheck, app_SiteInfoString, …
**Unter Windows hält MakeMKV seine Einstellungen in der Registry.** Rippy hat
den Key also zweimal brav gespeichert — beide Male dorthin, wo ihn niemand
liest.
### ⚠️ Was du wissen musst: dein MakeMKV ist zu alt für den Beta-Key
Mit dem Key an der richtigen Stelle antwortete MakeMKV:
5020 Der hinterlegte Aktivierungsschlüssel ist ungültig.
5021 Diese Programmversion ist zu alt.
Dein MakeMKV **1.18.4** ist älter als der aktuelle Beta-Key verlangt. Ein
Update ist derzeit nicht zu bekommen: makemkv.com antwortet weiter mit HTTP
525, und die Ausweichquellen kennen als höchste Fassung genau 1.18.4.
**Das blockiert dich nicht** — der Beweis-Rip oben lief ohne Key. Aber für
4K-UHD wird der Key gebraucht.
Wichtiger noch: **ein abgelehnter Schlüssel ist schlimmer als gar keiner.**
Ohne Key las MakeMKV die Disc noch, mit dem abgelehnten verweigerte es alles
(0 Titel). Rippy prüft deshalb jetzt nach dem Ablegen und nimmt einen
abgelehnten Schlüssel wieder zurück. Deine Registry ist wieder so, wie sie
vorher war.
### Mein Fehler in dieser Sitzung
Ich hatte die Meldungsnummer 5021 aus dem Kopf mit „Volume-Key unbekannt"
beschriftet — falsch, sie heißt „Programmversion zu alt". Und meine eigene
Messausgabe zeigte nur **meine Beschriftung** statt MakeMKVs Text, sodass es
fast durchgegangen wäre. Genau der Fehler, den ich sonst anprangere. Der echte
Wortlaut steht jetzt im Code.
## Letzter Stand davor: v4.0-rc4 — Cover, Arbeitsordner, aufgeräumt (28.08.2026)
> **Drei Befunde aus deinem ersten echten Durchlauf, alle drei behoben und
> gemessen.** Neues Setup liegt auf dem Desktop.
### ZUSTAND, gemessen
| | |
|---|---|
| Repo | `35370fd` auf `main` |
| **Ampel** | **GRÜN** |
| Tests | **794 grün** + 19 übersprungen (Sitzungsbeginn: 378) |
| Deine Platte | **1.074 MB freigeräumt** (19 liegengebliebene Ordner) |
| VM (Arcane) | **`05ab655`** — weit zurück, siehe Warnung unten |
### 1. „Was ist mit dem Cover auf Windows Rippy?"
Es gab keins, weil es keinen Treffer gab — nicht wegen Windows. Der Pre-Scan
verlangte, dass der TMDB-Titel **wörtlich** dem Disc-Titel gleicht. An deiner
Disc gemessen:
'Evangelion 2.22' 1 Treffer Evangelion: 2.0 You Can (Not) Advance
'Evangelion' 20 Treffer irgendein Evangelion
Der eine Treffer auf den vollen Disc-Titel **war** der richtige Film, mit
Poster — und flog raus, weil „Evangelion 2.22" nicht gleich „Evangelion: 2.0
You Can (Not) Advance" ist.
Jetzt zählt nicht die Ähnlichkeit, sondern wie **genau gefragt** wurde: Wer
auf den vollen Disc-Titel höchstens drei Treffer bekommt, hat gesucht wie
jemand, der weiß was er sucht. Wer zwanzig bekommt, hat geraten. Dieselbe
Disc liefert jetzt:
Evangelion: 2.0 You Can (Not) Advance · 2009 · 80 % sicher
Poster, Hintergrundbild, Genres, deutsche Beschreibung
### 2. „Warum heißt das hier noch container platte?"
Weil da ein Container-Pfad fest im Code stand — und zwar derselbe, an dem
auch dein zweiter Punkt hing: *„Wäre es möglich das Arbeitsverzeichnis zu
ändern? momentan geht das nicht."*
`MEDIA_ROOT = "/app/media"` war **gleichzeitig Vorgabe und Pfadgrenze**. Auf
deinem PC gibt es den Ordner nicht, also:
* die Liste der Arbeitsverzeichnisse blieb **leer** — im Auswahlfeld stand
genau ein Eintrag, und das war „Container-Platte". Eine Auswahl, die nichts
auswählt.
* der Ordner-Browser antwortete auf **jeden** Pfad mit einem Fehler.
Kein Absturz, keine Meldung. Nur eine Bedienung, die stillschweigend nichts
konnte.
Die Grenze bleibt, wo sie hingehört: Im Container hängt Rippy im Netz, dort
darf die Oberfläche nicht überall hinsehen. Auf deinem PC bedient sie dich —
also gilt sie dort nicht. Auf deinem Rechner gemessen:
Laufwerk C: 58,2 GB frei Laufwerk X: 2233,4 GB frei
Laufwerk D: 844,5 GB frei Laufwerk Y: 2233,4 GB frei
Laufwerk E: 773,1 GB frei Laufwerk Z: 2233,4 GB frei
Laufwerk F: 1510,6 GB frei
Dazu: im Rip-Dialog ein Knopf **„Als Arbeitsordner"** im Ordner-Browser (für
einen Ort, den keine Liste kennt), und in den Einstellungen ein echtes
Pfadfeld statt der Auswahl — mit den Laufwerken als Ein-Klick-Wahl darunter.
### 3. „Und manchmal kommt dieser fehler."
Failed to remove temporary directory: …\Temp\_MEI0000b0882
Nachgesehen: In deinem Temp-Ordner lagen **20 solcher Ordner mit zusammen
1,1 GB**. Der aus deiner Meldung ließ sich hinterher problemlos löschen — die
Sperre war also nur vorübergehend, ein Wettlauf.
Die Ursache: Rippy startet sich selbst zweimal (einmal als Dienst, einmal als
Fenster — Tray und Fenster brauchen je einen eigenen Haupt-Thread). Dabei gab
es den Kindern seinen eigenen Auspack-Ordner mit. Die liefen dann **im Ordner
des Elternprozesses** — und der beendet sich als Erster und will ihn löschen.
Das war mehr als eine Meldung: Wäre das Löschen **teilweise** geglückt,
hätten Dienst und Fenster mitten im Betrieb ihre eigenen Dateien verloren.
Jeder Prozess packt jetzt seinen eigenen Ordner aus und räumt ihn selbst
weg; beim Start werden liegengebliebene entfernt — aber nur, wenn sie
nachweislich niemandem mehr gehören. **Deine 1.074 MB sind schon weg.**
### 4. Arbeitsordner jetzt auch IM Setup
Bisher fragte das Setup nur nach Programmordner und Ablage — der Arbeitsordner
tauchte erst nach der Installation auf. Das ist die falsche Reihenfolge: Wer
erst nach der ersten vollen Platte erfährt, wo 100 GB Rohdaten landen, hat es
zu spät erfahren.
Jetzt steht das Feld im Setup, mit Durchsuchen-Knopf. Leer lassen heißt
weiterhin „neben die Ablage".
### 5. Der MakeMKV-Beta-Key — du hattest recht
> „der könnte theoretisch auch automatisch ausgelesen werden, ich glaube das
> web rippy kann das"
Kann es, seit es die Datei gibt: `makemkv_key.py` holt den Key täglich aus dem
Forum. Nachgemessen: **Antwort in 3,3 Sekunden, gültiger Key mit 62 Zeichen**,
und in deinen Einstellungen lag schon einer.
Zu sehen war davon nichts — kein Knopf, keine Meldung. Eine Automatik, die man
nicht sehen kann, ist für dich keine. Dazu eine echte Lücke: Die Schleife
startet erst 60 Sekunden nach dem Server. Wer gleich nach dem Einrichten eine
Blu-ray einlegt, rippt ohne Key.
Jetzt: **das Setup holt ihn selbst**, und in den Einstellungen steht ein Knopf
„Jetzt aus dem Forum holen" samt Rückmeldung, ob sich etwas geändert hat.
Das ist die kostenlose öffentliche Beta-**Lizenz der Software** — kein
Disc-Schlüssel. Rippy liefert und verteilt keine.
### Nebenbefunde
Zwei Tests scheiterten in jeder Umgebung ohne `pywebview` — und ich hielt sie
erst für meinen eigenen Fehler. Ein Test, der nur an einer Stelle greift, ist
eine halbe Zusage.
Und ich habe in `35370fd` fünf Dateien versehentlich von LF auf CRLF
umgestellt: 1363 geänderte Zeilen für 30 echte. Inhaltlich war nichts kaputt,
aber ein unlesbarer Diff ist eine verlorene Prüfmöglichkeit. In `d0281f7`
zurückgedreht — als reiner Zeilenenden-Commit, damit die Funktionsänderung
daneben lesbar bleibt.
## Letzter Stand davor: v4.0-rc3 — Windows testbereit (28.08.2026)
> **Der Commander testet gerade selbst.** Setup auf dem Desktop, Rechner
> sauber, Ampel grün. Die VM bleibt bewusst liegen — „geht ja erstmal um
> windows".
### ZUSTAND, gemessen
| | |
|---|---|
| Repo | `be3fac5` auf `main`, Arbeitsstand sauber |
| **Ampel** | **GRÜN** |
| Tests | **774 grün** + 19 übersprungen (Sitzungsbeginn: 378) |
| `RippySetup.exe` | **56,5 MB**, 16:07 Uhr, auf dem Desktop |
| Laufwerk | am PC — LG `HL-DT-ST BD-RE BU40N`, Seriennr. `0025114C0149` |
| MakeMKV | **nicht installiert** (makemkv.com liefert HTTP 525) |
| VM (Arcane) | **`05ab655`** — weit zurück, siehe Warnung unten |
### Was diese Sitzung noch gebracht hat
**Ein echtes Setup** (`einrichtung.py`, `setup_fenster.py`): acht
Voraussetzungs-Prüfungen, Zielordner UND Ablage, Port, Fortschrittsbalken.
Nur ein FEHLER blockiert, jeder Befund sagt was zu tun ist.
**MakeMKV-Ausweichquellen.** makemkv.com antwortet dauerhaft mit 525, das
Forum wackelt (522/Zeitablauf/OK im Wechsel). Kette: Hersteller → Forum →
Internet Archive, die höchste Versionsnummer gewinnt. Geprüft wird die
Versions-Ressource der geladenen Datei (`GuinpinSoft inc`), weil MakeMKV
seinen Installer **nicht signiert**.
**Der Installer braucht `ShellExecute`, nicht `Popen`.** `CreateProcess`
zeigt keine UAC-Abfrage, es bricht mit ERROR_ELEVATION_REQUIRED ab. Deshalb
erhöht sich NUR dieser eine Aufruf — das ganze Setup erhöht zu starten würde
die eingebundenen Netzlaufwerke unsichtbar machen (X:, Y:, Z:; auf diesem
Rechner gemessen, `EnableLinkedConnections` nicht gesetzt).
### ⚠️ DER SCHWERSTE FUND: die Metadaten-Suche war seit V2-1 TOT
clients/tmdb.py:27 from db import get_settings
clients/omdb.py:36 from db import get_settings
clients/thetvdb.py:16 from db import get_settings
`docker/api/db.py` gibt es seit `dd1d0b7` nicht mehr. Jede Abfrage starb
beim Erzeugen des Clients, und `_auto_prescan` verschluckte es. **Das gilt
für die VM genauso** — sie steht auf `05ab655`, also nach dd1d0b7.
Dazu drei weitere Docker-Annahmen, die unter Windows alles blockierten:
Redis als Pflicht statt Beschleunigung, `mount -t udf` für den Disc-Titel,
und `shutil.which("makemkvcon")` — das findet unter Windows nie etwas.
Ergebnis an der echten Disc: `Neon Genesis Evangelion` (1995), Confidence
0,8, Fingerabdruck `BD_EVG_D2|48149364736`.
### ~~BEKANNTE LÜCKE: Audio-CDs laufen unter Windows NICHT~~ — geschlossen (29.08.2026)
`cdparanoia` (Titelliste) und `abcde` (Rippen) sind Linux-Werkzeuge und
werden nicht mitgeliefert. Die CD wurde erkannt, aber nicht gerippt.
**Seit dem 29.08.2026 geht es** — nicht mit einem Nachbau von abcde, sondern
über den Weg, den Windows selbst anbietet. Siehe den aktuellen Stand oben.
### Die Lehre dieser Sitzung, viermal bezahlt
`os.path` richtet sich nach der laufenden Maschine — falsch überall dort, wo
über Pfade einer ANDEREN gerechnet wird. Viermal einzeln repariert
(katalog, verknuepfungen, betrieb, einrichtung), jetzt an einer Stelle:
`rippy/pfade.py`. **Der Pfad entscheidet, nicht der Rechner.**
## Letzter Stand davor: v4.0-rc2 — Windows ist ein eigenes Produkt (28.08.2026)
> **Rippy für Windows ist keine Docker-Installation im Fenster mehr.** Es
> weiß, worauf es läuft, es fragt beim Einrichten, und es bringt seine
> Werkzeuge mit.
### ZUSTAND, gemessen
| | |
|---|---|
| Repo | `151376a` auf `main` |
| **Ampel** | **GRÜN** |
| Tests | **698 grün** + 18 übersprungen (Sitzungsbeginn: 378) |
| `RippySetup.exe` | **56,4 MB**, liegt auf dem Desktop |
| Laufwerk | **am PC**`\.\G:`, BLU-RAY, bereit |
| MakeMKV | **nicht mehr installiert** (siehe unten) |
### Was der Commander gemeldet hat — und was dahinter steckte
**1. „Du hast quasi nur die Docker-Installation für Windows gebaut."**
Im Windows-Fenster stand `Worker erreichbar: 0 von 1`, `Container-Platte:
unbekannt`, `Prüfen: docker compose ps`. Kein Satz davon ergibt dort einen
Sinn.
Die Ursache war nicht die Anzeige: **Das UI hat nie erfahren, worauf es
läuft.** `GET /betrieb` meldet jetzt FÄHIGKEITEN (`externe_worker`,
`freigaben_einhaengen`, `container_pfade`, `werkzeuge_verwalten`) — kein
Modus-Name, weil das UI sonst aus einem Namen auf Verhalten schließen müsste.
Dashboard, Einstellungen und Anleitung richten sich danach.
**2. „Beim Setup passiert gar nichts."**
Jetzt: acht Voraussetzungs-Prüfungen, Zielordner UND Ablage zur Wahl, Port,
drei Schalter. Nur ein FEHLER blockiert, eine Warnung nicht — und jeder
Befund sagt, was zu tun ist.
**3. „Handbrake und MakeMKV MÜSSEN mitgeliefert werden."**
HandBrakeCLI liegt bei (GPL-2 erlaubt es), MakeMKV wird beim Einrichten vom
Hersteller geholt (proprietär, keine Weitergabe erlaubt).
### Drei Fehler, die nur die andere Plattform zeigte
`os.path` richtet sich nach der laufenden Maschine — falsch überall dort, wo
über Pfade einer ANDEREN gerechnet wird:
* `katalog.py` baute `C:\Program Files (x86)\MakeMKV/makemkvcon64.exe`
* `verknuepfungen.py` gab für den `.lnk`-Arbeitsordner einen leeren String
* `betrieb.py` hielt `D:` und `E:` für dasselbe Laufwerk
Dreimal dasselbe Muster. Jetzt an EINER Stelle: `rippy/pfade.py`, mit der
Regel im Kopf — **der Pfad entscheidet, nicht der Rechner.**
### Und der teuerste Befund des Tages
`%ProgramFiles(x86)%` wurde auf einer echten Maschine **nie** aufgelöst.
Windows legt Umgebungsvariablen GROSS in `os.environ` ab, das Muster stand
hübsch geschrieben, der Vergleich war exakt. Die bekannten Installationsorte
fielen aus der Kandidatenliste; MakeMKV wurde nur über die Registry gefunden.
Aufgefallen ist es erst, als zum ersten Mal eine ECHTE Umgebung eingesetzt
wurde. Der Test spritzte genau die Schreibweise ein, die im Muster stand —
**er war grüner als die Wirklichkeit.**
### ⚠️ MakeMKV ist auf dem Commander-PC nicht mehr installiert
Weder Dateien noch Registry-Eintrag. Weder die Deinstallation noch die
Werkzeug-Kette fassen fremde Programme an — vermutlich hat der Commander es
selbst entfernt, um den Fall „MakeMKV fehlt" zu testen (dazu hatte ich
geraten). Der Assistent zeigt ihn korrekt als Warnung mit Ausweg.
## Letzter Stand davor: v4.0-rc — Windows ist ein echtes Programm (28.08.2026)
> **Rippy läuft auf Windows ohne Docker, in einem eigenen Fenster, findet und
> aktualisiert seine Werkzeuge selbst — und meldet sich bei Windows an wie
> jedes andere Programm.**
### ZUSTAND, gemessen
| | |
|---|---|
| Repo | `ca212ff` auf `main` |
| **Ampel** | **GRÜN** (Lauf 321) — davor fünf Läufe rot, siehe unten |
| Tests | **582 grün** + 18 übersprungen (Sitzungsbeginn: 378) |
| `RippySetup.exe` | **32,0 MB**, liegt auf dem Desktop |
| Fenster | WebView2 **151.0.4129.107**, 1280×860, Oberfläche im Bild belegt |
| Konsolenfenster | **keins** — PE-Subsystem 2 (GUI) statt 3 |
| Werkzeuge auf diesem PC | 5 Encoder gefunden, darunter **AMD VCE** |
| VM (Arcane) | **noch auf `05ab655`** — alles ab `b723fee` ist NICHT deployt |
| Echter Rip auf Windows | **ungeprüft** — das Laufwerk hängt an der VM |
### ⚠️ DIE AMPEL WAR FÜNF LÄUFE LANG ROT, UND ICH HABE ES ÜBERSEHEN
Läufe 181186 (ab `288f9ee`). Unter Windows war alles grün, auf dem
Linux-Runner fielen **28 Tests** aus. Der schwerste Befund:
docker/api/main.py:54
AttributeError: module 'rippy.drives.linux' has no attribute 'drive_status'
`main.py` holt sich beim Import `device_discovery.drive_status`. Der
Windows-Treiber hat die Funktion; `linux.py` hatte sie nicht mehr, seit
`detection` (und damit `fcntl`) bewusst nicht mehr oben importiert wird —
sonst wäre `main.py` unter Windows nicht ladbar gewesen. **Der API-Container
wäre auf der VM gar nicht hochgekommen.** Sichtbar wurde es nur, weil die VM
elf Commits zurückhängt.
Drei weitere Fehler derselben Art — richtig unter Windows, falsch auf Linux:
* `katalog.py` baute Windows-Pfade mit `os.path.join`. Auf Linux wurde daraus
`C:\Program Files (x86)\MakeMKV/makemkvcon64.exe`.
* `verknuepfungen.py` nahm `os.path.dirname` für den Arbeitsordner einer
`.lnk` — die zeigt aber IMMER auf einen Windows-Pfad.
* `waechter.py` las `0.0` als Zeitpunkt statt als „noch nie geprüft". Auf
einer frisch gestarteten Maschine ist `time.monotonic()` klein, also fiel
die erste Laufwerks-Abfrage aus.
**Die Lehre, die im Code steht:** Alle vier haben jetzt Tests, die auf JEDER
Plattform laufen — Attrappe für `detection`, nachgestellte Uhr bei 0,5 s,
Pfad-Tests für beide Trenner. Ein Test, der nur auf einer Plattform greift,
ist eine halbe Zusage.
### Was jetzt geht
Doppelklick RippySetup.exe -> installiert nach %LOCALAPPDATA%\Rippy
Desktop-Symbol + Startmenue-Eintrag
Eintrag in "Programme und Features"
Autostart (HKCU\...\Run)
Ergebnis in einem Meldungsfenster
Doppelklick Desktop-Symbol -> eigenes Fenster, keine Adresszeile,
kein Browser, KEIN CMD-Fenster
Taskmanager -> "Rippy.exe" mit Beschreibung
Programme und Features -> Rippy 2.0.0, Deinstallieren raeumt auf
### Die Entscheidung dieser Sitzung: WebView2, nicht Electron
Der Commander fragte: *„Warum nutzen wir für Windows weiterhin einen Browser?
Warum nutzen wir kein Electron oder sowas und machen daraus einen echten
Client."*
Erste Hälfte: berechtigt, umgesetzt. Zweite Hälfte: abgelehnt, **gemessen**:
| | Zusatz | Was mitkommt |
|---|---|---|
| pywebview + WebView2 | **2,7 MB** in der EXE | nur die Anbindung |
| Electron | ~150210 MB | zweites Chromium **und** zweite Laufzeit neben Python |
Windows 11 liefert die Laufzeit mit. Nachgemessen, was sie rendert:
`Chrome/151.0.0.0 … Edg/151.0.0.0`, fetch ✓, EventSource ✓, CSS Grid ✓.
Dasselbe Chromium wie in Electron — nur ohne es zweimal mitzuschleppen.
Vollständige Begründung: `src/rippy/fenster.py`, `KONZEPT-V2.md` § 10
Entscheid 4.
### DER FUND: ein Dienst, der gesund meldete und keine Oberfläche mehr hatte
Beim Nachsehen im laufenden Betrieb — nicht in einem Test:
/api/health HTTP 200
/ HTTP 404 im Fenster: {"detail":"Not Found"}
Der Server war in Ordnung. Sein Entpack-Verzeichnis war es nicht:
_MEI000074b02 31 Eintraege, 5 Ordner <- der laufende Dienst, kein ui
_MEI000082e82 44 Eintraege, 16 Ordner <- vollstaendig
Eine PyInstaller-Onefile-EXE liest bei **jeder** Anfrage aus `%TEMP%\_MEIxxxxx`.
Ein Temp-Verzeichnis ist kein Ort für etwas, das eine Woche liegen bleiben
soll — und der Ausfall ist der schlimmstmögliche: Die API antwortet weiter,
der Dienst gilt als gesund, nur die Oberfläche ist weg.
**Genau davor warnte `ROADMAP.md` beim Bau-Verfahren** („one-dir statt
one-file"). Die Abweichung bleibt, die Lücke ist geschlossen: Der Installer
legt die Oberfläche neben das Programm, `daemon._ui_pfad()` nimmt diese Kopie
zuerst. Zwei Tests halten es fest. Zeigt sich derselbe Ausfall an den
API-Modulen, ist one-dir die richtige Antwort.
### Der Vorfall, der den PC des Commanders getroffen hat
Ein Test rief `installieren()` auf. Seit dem Verknüpfungs-Feature legt das
Verknüpfungen auf dem **echten** Desktop an — die kennen kein `tmp_path`. Der
Test überschrieb damit das funktionierende Desktop-Symbol durch eines, das auf
eine 2-KB-Attrappe in `…\Temp\pytest-of-…` zeigte. Windows meldete beim Klick:
*„Diese App kann auf dem PC nicht ausgeführt werden."*
Aufgeräumt hatte der Test nur Registry und Autostart. **Ein Test, der Spuren
außerhalb von `tmp_path` hinterlässt, ist kein Test, sondern ein Eingriff.**
Jetzt zwei Sicherungen statt einer: `verknuepfen=False` **und** die
Anlege-Funktion ist ersetzt. Dazu ein eigener Wächter-Test.
### Gebaut in dieser Sitzung (11 Commits)
- **V2-4 Windows-Treiber** (`b723fee`, `3d7f3b1`, `9d95c95`) — Win32 per
ctypes, an einer echten Disc bewiesen
- **Daemon unter Windows** (`e5254c2`) — `rippyd` ohne Docker
- **RippySetup.exe** (`059183c`) — ein Programm, drei Betriebsarten
- **Werkzeug-Kette** (`288f9ee`, `f4a8d77`) — finden, holen, aktuell halten;
Rippy ist **standalone**, wie der Commander es verlangt hat
- **Verknüpfungen + `--oeffnen`** (`99c0586`)
- **Test-Vorfall behoben** (`069fc46`)
- **Echtes Fenster + UI-Kopie** (`c01ef10`)
### WAS ALS NÄCHSTES ANSTEHT
- [ ] **Laufwerk an den PC** — ein echter Rip unter Windows ist noch nie
gelaufen. Alles davor ist bewiesen, das nicht.
- [ ] **Deploy auf Arcane** — die VM steht auf `05ab655`, elf Commits zurück.
- [ ] **V2-5 Docker neu**: ein Image, drei Profile, Host-Mounts.
- [ ] **V2-6 Headless** — zurückgestellt, aber nicht gestrichen
(ausdrücklich: *„soll aber nicht vernachlässigt werden"*).
- [ ] **V2-7**: neue Funktionen inkl. Schlüsselkette in drei Stufen.
- [ ] Offen aus V2-4: `WM_DEVICECHANGE` statt Poll, `SetThreadExecutionState`.
## Letzter Stand davor: v4.0-beta — V2-0 bis V2-3 stehen, Polling ist weg (28.08.2026)
> **Vier Etappen gebaut, alle gruen, alles deployt.** Und ein Vorfall, den es
> ohne den Deploy nie gegeben haette — der aber eine echte Altlast aufgedeckt hat.
### ZUSTAND, gemessen
| | |
|---|---|
| Repo | `05ab655` auf `main` |
| VM | **deployt und laufend** — alle 5 Container up, UI 200, API 200 |
| Tests | **378 gruen** + 3 uebersprungen (Sitzungsbeginn: 301) |
| Ereignis-Waechter live | `gesund: true`, `alter_sekunden: 0.8` |
| SSE-Strom live | `event: snapshot` mit vollem Job-Datensatz belegt |
| Doppelte Module | **0** (Sitzungsbeginn: 6) |
| UI-Taktgeber | **7 von 9 weg** — 121 Anfragen/min je Tab -> ~2 einmalige Abrufe |
### ⚠️ DAS OPTISCHE LAUFWERK HAENGT NICHT MEHR AN DER VM
`ls /dev/sr*` findet nichts, `lsscsi` ist leer. Rippy laeuft deshalb gerade als
**reine Komprimier-Maschine** — das ist ein vorgesehener Betriebsfall, aber
rippen kann sie so nicht. Wieder anstecken (Proxmox-USB-Passthrough), dann:
./deploy/geraete-override.sh && docker compose -p rippy up -d
### DER VORFALL: ein fehlendes Laufwerk legte ALLES stumm
Der Deploy von V2-3 brach mit:
Error response from daemon: error gathering device information while
adding custom device "/dev/sr0": no such file or directory
Danach waren api, worker UND ui unten; nur postgres und redis liefen. Ein
`devices:`-Eintrag in compose ist eine **Startbedingung** — und keiner der drei
Container braucht zum STARTEN ein Laufwerk.
**Der Widerspruch war aelter als der Vorfall:** `install.sh` sagt bei fehlendem
Laufwerk ausdruecklich *„laeuft trotzdem durch, dann ist das eine reine
KOMPRIMIER-Maschine"* — `docker-compose.yml` sah das anders. Jeder Deploy nach
einem Abstecken haette das ausgeloest.
**Behoben an der Ursache:** `devices:` ist aus `docker-compose.yml` raus.
`deploy/geraete-override.sh` ermittelt die Knoten dieses Hosts (sr + passender
sg ueber die SCSI-Adresse in /sys abgeglichen, Logik aus install.sh uebernommen)
und schreibt `docker-compose.override.yml` — die zieht Compose von allein dazu.
Das Skript schreibt die Datei **auch dann, wenn kein Laufwerk da ist**; sonst
bliebe eine alte Override mit /dev/sr0 liegen und der Fehler waere derselbe, nur
schwerer zu finden (die Datei ist gitignored, taucht also in keinem Diff auf).
`deploy.sh` ruft es vor dem Start auf.
### Gebaut in dieser Sitzung
- **V2-0** Monorepo: drei Zwillingsdateien zusammengefuehrt
- **V2-1** die vier Ports, ein Store (db.py x2 -> rippy/store), ein
Laufwerks-Treiber (der Auswurf lag zweimal da, mit VERSCHIEDENEN Vertraegen)
- **V2-2** SQLite hinter demselben Port, Auftrags-Queue mit Lease
(ersetzt die Zombie-Jagd), Konfigurationsschicht mit einer Praezedenz
- **V2-3** Ereignis-Bus, SSE-Endpunkt `/events`, Waechter als Bruecke zum
Worker, UI auf den Strom umgestellt
### Vier Fehler, die unterwegs gefunden wurden
1. **`try/except ImportError` haette einen falschen Modulpfad verschluckt** —
auf Linux haette der Worker still jeden Rip verweigert. Waechter:
`src/rippy/test_paket.py` (beim Wegnehmen des Moduls rot gesehen).
2. **`gui.py` von CRLF auf LF gekippt** — 1520 Zeilen Diff fuer eine Zeile.
Mein Schreib-Helfer las im Textmodus. Zurueckgedreht.
3. **Ampel-Lauf 170 rot**: Mein Snapshot-Test brauchte eine Datenbank, die
Ampel hat keine. Im Container nachgestellt (Python 3.12, ohne DB): 402 gruen.
4. **`asyncio.get_event_loop()` in einem Worker-Thread** — das ist NICHT die
laufende Schleife, die Coroutine waere nie gelaufen. Schleife wird jetzt im
Startup festgehalten.
Dazu zwei Formatfehler: `/logs` bildet `ts` -> `timestamp` ab, mein Snapshot
lieferte die rohe DB-Zeile (haette „Invalid Date" gezeigt, NUR im Live-Betrieb);
und ein Docstring versprach eine Ereignis-Reihenfolge, die der Code nicht hielt.
### WAS ALS NAECHSTES ANSTEHT
- [ ] **Laufwerk wieder an die VM** (oder klaeren, wo es ist) — ohne das kann
Rippy nicht rippen, und der Windows-Treiber aus V2-4 laesst sich ohne
echtes Laufwerk nicht beweisen, nur behaupten.
- [ ] **V2-4 Windows**: Win32-Treiber, Dienst + Tray, PyInstaller-EXE.
Inno Setup ist auf dem Commander-PC nicht installiert — die EXE kommt
per PyInstaller (womit auch die bestehende RippyWorkerSetup.exe gebaut ist).
- [ ] **V2-5 Docker neu**: ein Image, drei Profile, Host-Mounts statt
Container-Mounts.
- [ ] **V2-7**: neue Funktionen inkl. Schluesselkette in drei Stufen.
## Letzter Stand davor: v4.0-alpha — Rippy v2 beginnt, Etappe V2-0 steht (28.08.2026)
> **Zwei Dinge sind passiert: Das Konzept für Rippy v2 liegt vor und ist vom
> Commander entschieden — und die erste Etappe ist gebaut, geprüft und grün.**
### ZUSTAND, gemessen
| | |
|---|---|
| Repo | `b98dc5e` auf `main`, Ampel **GRÜN** (Gitea-Lauf b98dc5ee: success) |
| VM | **NICHT deployt** — bewusst, siehe „Was als Nächstes ansteht" |
| Tests lokal | **290 grün**, 1 übersprungen (ohne die zwei Linux-only-Module) |
| Doppelte Module im Repo | **0** — vorher 3 (`detection`, `makemkv_daten`, `notify`) |
| Zeilen netto | **492** (234 hinzu, 726 weg) |
### 1. Das v2-Konzept: drei Betriebsmodi statt einem
Der Commander wollte Rippy in drei Ausprägungen: Docker (wie heute), eine
native Windows-App **ohne Docker**, und eine Headless-Linux-Anwendung — alle
drei mit demselben Webinterface.
**Vollständige Spezifikation: `KONZEPT-V2.md`** (Systemarchitektur,
Daten-/Queue-Strategie, die drei Plattformen im Detail, API-/Event-Design,
Migrationsplan). Etappen V2-0 bis V2-7 stehen in `ROADMAP.md`.
**Der tragende Gedanke:** Rippy v2 ist EINE Anwendung mit DREI Verdrahtungen,
keine drei Produkte. Ein Betriebsmodus ist nur die Auswahl der Treiber hinter
vier Ports — Store, Queue, Bus, Drives. Standalone heißt SQLite + lokale
Queue + asyncio-Bus, verteilt heißt Postgres + Celery/Redis. Derselbe
Rip-Code, dieselben Tests, dasselbe UI.
Zwei Entwurfs-Entscheidungen, die von der naheliegenden Option abweichen:
- **Keine Queue-Bibliothek** (nicht Taskiq, nicht ARQ). Stattdessen der
Grundsatz *„die Datenbank ist die Wahrheit, der Broker ist nur der Wecker"*
plus zwei sehr kleine Treiber. Nebenwirkung: `zombies.py` (206 Z + 263 Z
Tests) wird durch eine Lease ersetzt, statt portiert zu werden.
- **Mounten wandert auf den Host.** Die CIFS-Ausfälle waren kein Bug, sondern
die Folge davon, dass der Container mountet (Befund 26.07.2026). Damit
fallen `SYS_ADMIN`, `DAC_READ_SEARCH`, `apparmor:unconfined`, `rshared` und
die Mount-Wache weg. Die PRÜF-Logik aus `mounts.py` bleibt vollständig.
### 2. Drei Entscheide des Commanders (28.08.2026)
Alle drei stehen jetzt in `KONZEPT.md` § 10 — **wer nur eine Datei liest,
findet sie dort, nicht nur in KONZEPT-V2.md.**
1. **Reihenfolge:** Echtzeit (SSE statt Polling) VOR der Windows-App.
2. **Disc-Schlüssel: automatischer Abruf MIT Rückfallebene.** Das verschiebt
die Grenze vom 25.07.2026 (*„Rippy verteilt KEINE Disc-Schlüssel"*)
**bewusst**. Umgesetzt als Kette in drei Stufen: eigener Bestand →
automatischer Abruf → Import von Hand. Fünf Regeln gehören dazu, allen
voran: **die Bezugsadresse steht in der Konfiguration und ist LEER
vorbelegt** (eine vorbelegte tote Adresse wäre genau die Falle aus
`.env.example`), ein Fehlschlag ist LAUT, ein funktionierender Bestand
wird nie still überschrieben, und Rippy bringt selbst nichts mit.
3. **Speicherziele:** Der Host mountet, Rippy erzeugt die kopierbare Zeile
(fstab / `.mount`-Unit / `compose.yml`). Die Eingabemaske im UI bleibt,
nur der Knopf „Verbinden" wird zu „Zeile kopieren".
### 3. Etappe V2-0 gebaut: ein Paket statt drei Zwillingen
`detection.py`, `makemkv_daten.py` und `notify.py` lagen je ZWEIMAL im Repo,
byte-identisch, weil es kein geteiltes Paket gab. Jetzt gibt es `src/rippy/`
(`core` / `drives` / `rip`); beide Container importieren dieselbe Datei.
- `conftest.py` in der Wurzel legt `src/` auf den `sys.path` (die Ampel ruft
pytest dort auf).
- Beide Dockerfiles kopieren `src/rippy` nach `/app/rippy``/app` ist
Arbeitsverzeichnis und uvicorn-App-Dir, also ohne `PYTHONPATH` findbar.
Die Anordnung wurde nachgestellt und der Import geprüft, nicht vermutet.
- **`worker_setup_paket` packt das Paket ausdrücklich mit ins Zip.** Die
Schleife dort sah nur die oberste Ebene — ein Unterordner wäre nie
mitgekommen, und der Windows-Worker beim Start gestorben.
- Die zwei Test-Dateien für `makemkv_daten` sind zu einer verschmolzen.
Damit entfällt auch der `importlib`-Umweg im Worker-Test (er war nötig,
weil bei `pytest -q` aus der Wurzel `docker/api` zuerst eingesammelt wird
und jeder weitere Import nur noch den `sys.modules`-Cache trifft — die
Worker-Kopie wurde also nie angefasst). Netto 13 doppelte Tests.
- `test_zwillinge_sind_byteweise_identisch` ist weg. Der Wächter war nötig,
weil die Konstruktion falsch war; jetzt ist sie es nicht mehr.
### 4. DER FEHLER, DEN DIESER UMBAU FAST AUSGELIEFERT HÄTTE
In `tasks.py` steht der `detection`-Import in einem `try/except ImportError`
**absichtlich**, denn der native Windows-Worker hat kein `fcntl` und soll
trotzdem starten (er komprimiert nur).
Das `except` verschluckt aber **jeden** ImportError, auch einen falschen
Modulpfad. Auf Linux hätte der Worker ab sofort still `detect_disc_type=None`
gesetzt und **jeden Rip verweigert, ohne dass irgendwo ein Fehler gestanden
hätte** — die Klasse „still scheiternder Hintergrund-Prozess" aus `AGENTS.md`,
diesmal selbst gebaut.
**Warum er fast durchkam:** Die erste Suche nach Import-Stellen prüfte nur
Zeilenanfänge (`^from detection import`). Dieser Import ist eingerückt.
Gefunden hat ihn erst eine zweite Suche ohne Zeilenanker.
**Lehre fürs nächste Mal:** Bei einem Modul-Umzug reicht `grep '^import x'`
nicht — Importe stehen auch in `try`-Blöcken, in Funktionen und in
`if TYPE_CHECKING`. Und ein `except ImportError` um einen Umzug herum ist die
gefährlichste Stelle im ganzen Vorgang, weil sie den Fehler frisst.
**Wächter dagegen:** `src/rippy/test_paket.py` prüft mit
`importlib.util.find_spec`, dass es die drei Modulpfade wirklich gibt.
`find_spec` **führt nichts aus** — der Test läuft deshalb auch auf Windows
ohne `fcntl` und ist nicht nur in der Ampel wirksam. Gegengeprüft: Modul
weggenommen → Test rot, zurückgelegt → grün. Ein Wächter, den man nicht hat
scheitern sehen, ist Deko.
### WAS ALS NÄCHSTES ANSTEHT
- [ ] **Deploy auf die VM steht aus — bewusst.** Ein Deploy startet die
Container neu, und der `api`-Container HÄLT die NAS-Verbindung: mitten
in einem Rip wäre das ein Datenverlust. Erst prüfen, ob etwas läuft
(`docker compose -p rippy ps`, Job-Liste im UI), dann
`git pull --ff-only && docker compose up -d --build`.
**Beim ersten Deploy nach V2-0 gezielt nachsehen:** Liegt `/app/rippy`
in BEIDEN Containern? (`docker exec <c> ls /app/rippy`) Und enthält das
Worker-Zip den Ordner? (`GET /worker-setup/paket` herunterladen,
hineinschauen — der Windows-Worker startet sonst nicht.)
- [ ] **Etappe V2-1: Ports einziehen**`Store`/`Queue`/`Bus`/`Drives` als
Protocol, v1-Verhalten läuft weiter über Postgres/Celery/Redis/Linux.
Wieder ohne Verhaltensänderung. Danach ist jeder weitere Betriebsmodus
eine Treiber-Datei statt eines Umbaus.
- [ ] Die zweite echte Doppelung anfassen: `api/db.py` und `worker/db.py`
(überlappend, nicht identisch) sowie `devices.eject` gegen
`ripping.wirf_disc_aus` — beides gehört in V2-1 (Store bzw. DAL).
### HINWEIS FÜR DIE NÄCHSTE SITZUNG (lokale Umgebung)
Die pydantic-Falle hat erneut zugeschlagen: global lag `pydantic 2.13.4` mit
`pydantic-core 2.48.0` (unverträglich, 2.13.4 verlangt 2.46.4), und das
Einsammeln ALLER API-Tests brach mit `SystemError`. Repariert mit
`python -m pip install "pydantic-core==2.46.4"`. Die Ampel merkt das nie, weil
sie in ein frisches venv aus `requirements.txt` installiert. Wenn lokal
plötzlich alle API-Tests wegbrechen: erst die Versionspaarung prüfen.
Und der Ignore-Pfad hat sich geändert — Linux-only-Module beim lokalen
pytest-Lauf:
`--ignore=src/rippy/drives/test_detection.py --ignore=docker/api/test_prescan_helpers.py`
(vorher `docker/worker/test_detection.py`).
## Letzter Stand davor: v3.21 — Rippy Windows Worker (27.07.2026)
> **Etappe 25: Rippy Windows Worker komplett modernisiert.**
> Der Installer und der Worker selbst wurden auf eine Flet-App (mit pystray) migriert,
> sodass das Deployment als Standalone `.exe` ohne Python-Abhängigkeit auf dem
> Host-System sauber funktioniert.
### Was wurde gebaut?
1. **Installer (`installer.py`) in Flet**:
- Die alte GUI (CustomTkinter/win32com) flog raus.
- Neues Design mit "Freigabe holen" & "Durchsuchen".
- Setup als Standalone `.exe` via `flet pack`.
- Bugfix für stale Dependencies bei Upgrades: `venv --clear`.
- Desktop Shortcuts nativ via PowerShell (kein `win32com` mehr nötig).
2. **Worker GUI (`gui.py`) in Flet**:
- Feature-Parität mit dem alten Worker (Log, Status, Restart).
- Neues UI (an das Web-Dashboard angelehnt): Thumbnails für Jobs, farbiges und geparstes Live-Log (`INFO`/`ERROR`/`WARNING` mit `regex`).
- Button-Logik überarbeitet: Bei Status "Bereit" ist der Button nicht mehr irreführend anklickbar, sondern zeigt "Worker läuft".
- **System Tray (pystray):** Ein Klick auf das X minimiert den Worker nun in das System Tray, von wo er den Hintergrundprozess am Leben hält.
3. **Deploy-Weg (Arcane)**:
- Worker `.exe` wird vom API-Container gebaut, liegt in `worker_dist` und kann von Usern heruntergeladen werden.
- Pinned `flet==0.23.2` um API-Probleme in der alten Version zu verhindern. (Ein späteres Upgrade auf Flet 0.86+ ist vermerkt).
## Letzter Stand davor: v3.20 — Rippy bremste sich selbst aus (26.07.2026, Nacht)
> **Zwei Commander-Meldungen, eine gemeinsame Wurzel: Rippy behinderte sich
> selbst und schwieg darüber.** Dazu zwei Fehler im Deploy-Weg, die jeden
> `worker`-Build zum Absturz brachten — gefunden, weil ich selbst darüber fiel.
### ZUSTAND, gemessen
| | |
|---|---|
| Repo + VM | `96c400f`, Ampel **grün**, deployt |
| Tests | **301** grün (Sitzungsbeginn heute: 130) |
| Voller Durchlauf | **komplett durch**: 40,9 GB Rip → Auswurf → Kompression auf dem PC → 12,29 GB abgelegt |
| Rate-Limit-Eimer | PC bei 590, VM gleichzeitig bei 599 — **getrennt** (vorher einer für alle) |
| Anfragen des Dashboards | 75/min → **30/min** |
| Phasen-Marke | live gesetzt: `rip_fertig: false` beim Rip-Start |
### 1. „Wird oft neu geladen" — es lud gar nichts neu
Erst gemessen, dann geglaubt. Über zwanzig Sekunden:
```
/jobs byteweise IDENTISCH über 5 Abfragen
/capabilities byteweise IDENTISCH über 5 Abfragen
alle Endpunkte ≤ 30 ms
```
Es wurde also nichts neu geladen — es wurde **geleert**. Im nginx-Log standen 97
Antworten mit **HTTP 429**. Drei Fehler griffen ineinander:
**(a) Die Bremse lag unter der eigenen Last.** `MAX_REQUESTS_PER_MINUTE = 100`,
während ein einziger offener Tab verursacht:
| Taktgeber | Rechnung | pro Minute |
|---|---|---|
| Dashboard | 5 Endpunkte alle 4 s | 75 |
| Log-Kasten | 2 Endpunkte alle 5 s | 24 |
| Laufwerks-Suche | 1 Endpunkt alle 5 s | 12 |
| Windows-Tray | `/jobs` alle 5 s | 12 je Worker |
| | **Summe** | **123** |
**(b) Alle Clients teilten einen Eimer.** Hinter dem nginx ist
`request.client.host` immer der Proxy: 812 von 876 Anfragen kamen scheinbar von
`172.19.0.6`. Der nginx gab die echte Adresse nicht weiter.
**Das ist die Erklärung für die Kopplung an den Worker**, die der Commander
gesehen hat: [`tray.py`](docker/worker/tray.py) fragt `http://<host>/api/jobs`
über Port 80, also durch denselben Proxy. Der Tray zahlte aus dem Geldbeutel des
Browsers. Seine vermutete Ursache („Celery-Ping?") lag daneben, seine Beobachtung
war exakt richtig.
**(c) Ein abgewiesener Abruf leerte das UI.** Fünfmal stand im Dashboard
`catch(() => [])`. Das heißt „es gibt keine Jobs" — gemeint war „ich weiß gerade
nichts Neues". Für einen Takt stand „Keine Jobs in diesem Tab", die Zähler
sprangen auf (0), vier Sekunden später war alles zurück.
Behoben: `X-Real-IP` im nginx, Grenze auf 600/min **mit vorgerechneter
Herleitung im Quelltext**, ein Test hält die Grenze gegen die eigene Last fest,
jedes Greifen steht im Log (gedrosselt auf eine Meldung pro Client und Minute),
`null` statt `[]` bei Fehlschlag, 20-s-Zeitgrenze für axios, und das Dashboard
trennt schnelle Daten (Jobs/Laufwerke, 4 s) von langsamen (Hardware/Worker/
Ablagen, 12 s).
**Bewiesen:** fünf Anfragen vom PC → `X-RateLimit-Remaining` 594→590; die VM
durch denselben nginx gleichzeitig bei 599/598. Zwei Clients, zwei Eimer.
### 2. „Der Button Neu' — WAS wird da gemacht?"
Immer die Kompression. Auch bei einem Job, dessen **Rip** abgebrochen war: Am
Nachmittag lagen 4,8 GB von rund 40 GB da, und „Neu" hätte daraus brav einen Film
komprimiert, der bei 12 % aufhört.
Das Problem war nicht der Knopf, sondern fehlendes Wissen: Sobald `status =
"failed"` in der Zeile steht, ist die Phase fort — die Spalte hat nur einen Wert.
`zombies.war_im_rip()` sieht sie im Moment des Aufräumens, die API später nie.
Also wird sie vermerkt ([`phasen.py`](docker/api/phasen.py)):
| Zeitpunkt | Marke |
|---|---|
| Rip-Start | `rip_fertig = false` |
| Rip fertig, Kompression eingereiht | `rip_fertig = true` |
| Start eines Transcodes | `rip_fertig = true` (heilt Bestandsjobs) |
Daraus folgen **drei** Antworten, nicht zwei:
- `transcode`**„Neu komprimieren"** · Disc bleibt draußen, Stunde gespart
- `rip`**„Neu rippen"** · `POST /jobs/{id}/retry-rip`, neuer Job mit neuer ID
- `unklar`**Dialog**, der beide Wege erklärt und die Rohdaten-Größe als
Entscheidungshilfe nennt („eine Blu-ray bringt roh 2545 GB mit")
Der dritte Fall ist der Grund, warum nicht geraten wird: Bestandsjobs tragen die
Marke nicht, und dem Commander an einem Job mit 74 GB intakter Rohdaten das
Komprimieren wegzunehmen wäre genauso falsch wie ein Bruchstück anzubieten.
Warum ein **neuer** Job und nicht der alte wiederbelebt: Das Roh-Verzeichnis
heißt `<Arbeitsverzeichnis>/<job_id>`. Bei gleicher ID läge das alte Bruchstück
im neuen Verzeichnis, und die Kompression sammelt am Ende ALLE MKV-Dateien darin
ein — sie würde es mitverarbeiten.
### 3. Zwei Fehler im Deploy-Weg (gefunden, weil ich selbst darüber fiel)
`./deploy.sh` ohne Argumente — der dokumentierte Normalfall — endete mit
`line 5: $4: unbound variable`. **Leere Argumente überleben ssh nicht:** Die
Gegenseite bekommt die Befehlszeile als EINEN String und parst sie neu, `""`
verschwindet dabei ersatzlos. Jetzt `${4:-}`.
Danach brach jeder `worker`-Build am MakeMKV-Download ab. Ursache war nicht der
Download, sondern eine **geschluckte Warnung**: Die echte `.env` liegt auf der VM
unter `~/projects/rippy/.env`, der Default zeigte auf `~/rippy/.env`. Das `cp`
scheiterte bei jedem Deploy, die Warnung scrollte im Build-Rauschen vorbei, und
gebaut wurde mit der Kopie im Klon — zwei Tage alt, darin ein
`MAKEMKV_URL_BASE`-Notbehelf auf einen web.archive.org-Schnappschuss. Der liefert
inzwischen **525**, während `makemkv.com/download` wieder **200** gibt. Der
Notbehelf von vorgestern war die Ursache von heute.
Jetzt: `.env` übernommen → es steht da. Quelle fehlt, Klon hat eine → laute
Warnung mit Dateidatum und Kandidatenliste. Keine von beiden → Abbruch, statt mit
leerem DB-Passwort zu bauen.
**Für die nächste Sitzung:**
```
RIPPY_VM=arcane@192.168.178.162 \
RIPPY_REPO_URL=<gitea> \
RIPPY_ENV_SRC=/home/arcane/projects/rippy/.env ./deploy.sh
```
### 4. Die Uhrzeit-Meldung: nachgemessen statt gehofft
Zwei offene Fragen zur `Substantial drift … 7200 seconds`-Meldung, beide jetzt
beantwortet.
**Wirkt `set TZ=UTC` auf Windows überhaupt?** Das war ungeprüft — und die
MSVC-Dokumentation verlangt eigentlich die Form `UTC0`. Hier gemessen:
| | `time.timezone` | `time.altzone` | `isdst` |
|---|---|---|---|
| ohne `TZ` | 3600 | 7200 | 1 |
| `TZ=UTC` | **0** | 3600 | **0** |
| `TZ=UTC0` | 0 | 3600 | 0 |
Celerys `utcoffset()` rechnet `time.timezone // 3600` (bzw. `altzone`, wenn
`isdst`) — mit `TZ=UTC` also 0, genau wie in den Containern. `TZ=UTC` genügt,
`UTC0` bringt nichts zusätzlich.
**Warum kam die Meldung „immer wieder"?** Sie kam nicht alle paar Sekunden,
sondern **einmal pro Absender**:
```python
@memoize(maxsize=1000, keyfun=lambda a, _: a[0])
def _warn_drift(hostname, drift, local_received, timestamp):
# we use memoize here so the warning is only logged once per hostname
```
Der Absender ist der Linux-Container, und dessen Hostname wechselt bei **jedem
Neubau**. Die Meldungen im Log (`7e71250606aa`, `09d001df95a4`, `7c2c179a1953`,
`3a9f4808ed51`) sind genau vier Deploys — deshalb kam sie „immer wieder", ohne
zwischendurch zu nerven.
**ERLEDIGT, und zwar sauber bewiesen.** Aus dem lokalen Worker-Log auf dem
PC (`%LOCALAPPDATA%\Rippy Worker\worker.log`):
```
[2026-07-26 14:21:17,301: INFO/MainProcess] tobisnicerpc@TobisNicerPC ready.
[2026-07-26 14:23:17,318: WARNING/MainProcess] Zombie-Erkennung: {...}
[2026-07-26 14:57:33,460: INFO/MainProcess] sync with celery@d160eb2f3afd
```
Zwei Dinge stehen da:
1. Die Zeitstempel sind **UTC** (14:21, während es auf dem PC 16:21 war) —
`TZ=UTC` ist im laufenden Worker also wirklich aktiv.
2. Um 14:57:33 kam ein **neuer** Absender-Hostname dazu (der Container von
heute Abend). Genau dafür ist die Merk-Sperre blind — die Warnung hätte
feuern MÜSSEN. Sie kam nicht.
Nebenbei ist damit auch die Log-Brücke bestätigt: Die Zeilen von 14:21 und
14:23 stehen als `w:tobisnicerpc` in Rippys Log. (Ich hatte sie zuerst
übersehen, weil `/logs` neueste-zuerst liefert und ich `tail` statt `head`
gefiltert hatte — die Liste war nicht leer, ich habe an der falschen Seite
geschaut.)
### 5. Der volle Durchlauf — was er bewiesen und was er gefunden hat
Job `bfb7a946`, über den neuen `retry-rip`-Weg an der Akira-BD gestartet.
Aus dem Log, unverändert:
```
15:38:57 Copy complete. 1 titles saved.
15:39:02 Disc ausgeworfen
15:39:02 [watcher] Disc entfernt: /dev/sr0
15:39:03 Rip fertig, Kompression eingereiht
15:39:05 Dieser Worker erreicht die Ziel (fertige Datei) nicht: …
15:44:17 rippy: neu verbunden (4.2s)
15:44:36 Kompression gestartet (1 Datei(en), Disc-Typ 'bluray',
Preset 'HQ 1080p30 Surround', Ton: deu, Untertitel: deu)
```
**Vier Dinge sind damit belegt, die vorher nur behauptet waren:**
| | |
|---|---|
| Auswurf im AUTOMATISCHEN Weg | bisher war nur der Knopf im UI live geprüft, nicht der Weg nach einem echten Rip — und die Laufwerks-Wache bestätigt ihn unabhängig |
| Phasen-Marke | sprang bei der Übergabe auf `rip_fertig: true`, `retry_art` wurde `transcode` — der Knopf bot „Neu komprimieren" an, bei 40,9 GB intaktem Rip genau richtig |
| Sprachwahl beim EXTERNEN Encoder | `Ton: deu, Untertitel: deu` — die Wahl aus dem Rip-Dialog kommt auf dem Windows-PC an |
| Mount-Wache | 4,2 s, inklusive eines abgewiesenen ersten Versuchs (`mount error(16): Device or resource busy`) |
**Und ein echter Fehler, gefunden genau dort, wo noch nie jemand war:** Die
Kompression brach 0,2 s nach der Übergabe ab —
`erreicht die Ziel nicht: \\…\rippy\movies\Akira (1988)`. Die Freigabe war
erreichbar; es fehlten zwei **noch nie angelegte** Ordner. Die Prüfung ließ aber
genau EINE fehlende Ebene durch (sie sah nach `dirname`), obwohl gleich darauf
`os.makedirs` die ganze Kette anlegt. Damit war **jeder erste Film in einer neuen
Ablage** systematisch unerreichbar — der Fehler wartete seit v3.17 darauf, dass
jemand eine frische Ablage benutzt.
Zweiter Fehler in derselben Meldung: Sie behauptete
„RIPPY_PATH_MAP … deckt diesen Pfad aber nicht ab" und nannte im selben Satz den
korrekt übersetzten UNC-Pfad. Wer dem folgte, suchte in der Karte statt in der
Freigabe. Dazu der Artikelfehler „erreicht **die** Ziel".
`_erreichbarkeit_pruefen` hatte **keinen einzigen Test** — deshalb kam beides
durch. Jetzt zwölf (`test_erreichbarkeit.py`). Beim Schreiben fiel ein dritter
Fehler auf: `isdir=os.path.isdir` als Vorgabewert bindet die Funktion beim
IMPORT; ein Ersetzen geht danach ins Leere. Auflösung jetzt beim Aufruf.
**Das Ergebnis nach der Reparatur — die Kette ist zum ersten Mal ganz
durchgelaufen:**
```
16:47:57 abgeschlossen → \\192.168.178.62\rippy\movies\Akira (1988)
```
40,9 GB roh → **12,29 GB** fertig (63 Minuten auf dem Ryzen 9700X), das
Roh-Verzeichnis danach automatisch aufgeräumt. Die Sprachwahl am fertigen
Ergebnis nachgeprüft (`HandBrakeCLI --scan` auf die abgelegte Datei):
```
+ audio tracks:
+ 1..5, Deutsch (MP3, 2.0 ch, 160 kbps) (iso639-2: deu)
+ subtitle tracks:
+ 1, Deutsch (PGS)
+ 2, Deutsch (PGS)
```
Kein Japanisch, keine unbenannten Spuren — vorher meldete der Scan der Disc
`Ton: deu 4×, jpn 2×, und 4×` und `Untertitel: deu 4×, und 8×`. **Die
Sprachwahl greift also wirklich, bis in die abgelegte Datei.**
### 5b. Ein Fund aus dem Ergebnis: „Surround" liefert Stereo
Der Ton der fertigen Datei ist **fünfmal MP3 2.0 mit 160 kbps** — von einer
Blu-ray. Das gewählte Preset heißt `HQ 1080p30 Surround`; ausgelesen mit
`--preset-export` schreibt es vor:
| Regel | Encoder | Mixdown | Bitrate |
|---|---|---|---|
| AudioList[0] | `av_aac` | stereo | 160 |
| AudioList[1] | (Surround) | — | 640 |
Angewandt wurde nur Regel 0 — auf JEDE der fünf behaltenen Spuren. Regel 1 hat
nie eine Spur erzeugt. Der Surround-Ton eines Presets namens „Surround" fällt
damit still weg, und die Bitrate deckelt bei 160 kbps.
Warum der Encoder MP3 statt `av_aac` wurde, ist **nicht geklärt** — Rippy
übergibt keinen Audio-Schalter, nur `--audio-lang-list` und `--all-audio`.
Bitrate und Mixdown stimmen exakt mit Regel 0 überein, nur der Codec nicht.
Nicht geraten, sondern als offener Punkt notiert.
**Zu entscheiden ist das vom Commander**, nicht still von mir: Für ein Archiv
wäre ein Preset mit Surround-Durchreichung richtig (die `H.265 MKV`-Reihe), oder
weniger Tonspuren behalten. Ich habe nichts umgestellt.
### 6. Was noch offen ist
- **Der Ton-Befund aus 5b** — Preset-Wahl, Entscheidung des Commanders.
- ~~Job `2182d525` mit falschem Fehlertext~~ — **erledigt**: Der Commander hat
den Eintrag am 26.07. um 16:35 samt 4,8-GB-Bruchstück über den Papierkorb
entfernt (`aus der Liste entfernt (inkl. 4.8 GB Rohdaten gelöscht)`). Damit
ist nebenbei auch der Lösch-Dialog mit Rohdaten-Wahl im echten Betrieb belegt.
- **`sudo ./install.sh` auf einem FRISCHEN Host ist weiter unbelegt.** Der
Prüfpfad (`--nur-pruefen`) läuft auf der VM sauber durch, alle fünf Prüfungen
grün. Ein echter Root-Lauf hier hätte den laufenden Rip getötet.
- **Der Windows-Worker läuft auf gemischten Dateiständen** — direkt auf dem PC
nachgesehen (`C:\Program Files\Rippy Worker`): `tasks.py` 13:19, `ripping.py`
14:06, `zombies.py` 14:17. Die Sprachwahl ist vollständig drin (geprüft:
`tasks.py` liest sie, `ripping.py` gibt sie an HandBrake), `meta_merken`/
`RIP_FERTIG` von heute Abend fehlen noch. Für diesen Test unerheblich (die
Marke setzt der Linux-Worker bei der Übergabe), aber **nach dem Test einmal
den Installer laufen lassen**, damit alles auf einem Stand ist.
- **Es fehlt eine Versionsanzeige für den externen Worker.** Nichts vergleicht
seinen Code-Stand mit dem des Servers; „läuft auf gemischten Ständen" war nur
zu sehen, weil ich auf dem PC selbst nachgesehen habe. Ein Zähler in `caps.py`
(z. B. der Commit-Kurz-Hash) und eine Warnung im UI wären der saubere Weg —
bewusst nicht heute Nacht gebaut.
- **Im Wurzelverzeichnis der Freigabe liegt eine verwaiste
`Evangelion 2.22_t00.mkv`** ohne zugehörigen Job. Kann weg, wenn du sie nicht
brauchst — Rippy zeigt sie nirgends an, weil kein Eintrag darauf zeigt.
---
## Vorheriger Stand: v3.19 — Sprachwahl, Verwaltungsfenster, Schlüssel-Automatik (26.07.2026, spät)
> **Deployt und live gegengeprüft.** Sieben Commander-Meldungen, alle gemessen
> statt vermutet. Drei davon waren echte Fehler, die niemand gesehen hatte, und
> einer davon hat sich als Kette aus drei Ursachen entpuppt.
### ZUSTAND, gemessen
| | |
|---|---|
| Repo + VM | `848deb1`, Ampel **grün**, deployt |
| Tests | **269** grün (Sitzungsbeginn: 130) |
| Freigabe nach Deploy | heilt sich in **8 Sekunden** selbst (vorher 150202 s) |
| Job `95afdc89` | `can_retry = true`, 74,1 GB erreichbar |
| Sprach-Scan an der Akira-BD | Ton `deu 4×, jpn 2×, und 4×` · Untertitel `deu 4×, und 8×` |
### 1. AUSWURF: das ioctl meldete Erfolg und tat nichts
Am laufenden System an der eingelegten Disc nachgestellt:
```
wirf_disc_aus("/dev/sr0") → True
Laufwerksstatus danach → 4 (Disc drin)
```
MakeMKV **verriegelt die Laufwerkstür** während des Rips (`CDROM_LOCKDOOR 1`) und
entriegelt sie nicht wieder. Ein verriegeltes Laufwerk quittiert `CDROMEJECT`
trotzdem mit Erfolg. Gegenprobe an derselben Disc: mit `CDROM_LOCKDOOR 0` davor
→ Status 2 (Schublade offen). Genau das macht das Werkzeug `eject` immer.
Wichtiger als das Entriegeln: Das Ergebnis wird jetzt **geprüft statt geglaubt**.
Deshalb rutschte der Fehler in v3.14 durch — dort wurde richtig festgestellt,
dass die Einstellung von niemandem gelesen wurde; danach WURDE sie gelesen,
ausgeworfen wurde weiterhin nicht, und im Log stand „Disc ausgeworfen".
**Live bewiesen** (API-Weg = der Knopf im UI): 0,67 s von „Disc drin" zu
„Schublade offen". Der automatische Weg nach einem Rip ist derselbe Code und
durch Tests gedeckt, aber nicht nochmal live gemessen — die Schublade lässt sich
per Software nicht wieder schließen (`CDROMCLOSETRAY` bleibt wirkungslos,
viermal versucht; das BU40N will von Hand zugeschoben werden).
**Zweite Disc parallel** war nur daran gescheitert: `has_active_job` blockiert
ausschließlich bei `pending`/`running`, ein Job in `transcoding` gibt das Gerät
längst frei, und der Docker-Worker läuft ohne `--concurrency`, also mit 4 Slots.
### 2. SPRACHWAHL VOR DEM RIP
Die Auskunft lag längst vor und wurde weggeworfen: Derselbe Titel-Scan, der die
Titel-Tabelle füllt, liefert in derselben `makemkvcon`-Ausgabe die Streams mit.
Format an der Akira-Blu-ray gemessen:
```
SINFO:<titel>,<stream>,<attribut>,<code>,"<wert>"
1 = Typ ("Audio"/"Subtitles") 3 = Sprachcode 4 = Sprachname
6 = Codec 14 = Kanäle 30 = Beschreibung
```
⚠️ **Die Falle:** Die Sprache steht in **3/4, NICHT in 28/29**. Die tragen auf
JEDEM Stream „eng"/„English" — auch auf einer deutschen Tonspur und auf dem
Videostream; das ist MakeMKVs eigene Anzeigesprache. Wer 28 nimmt, hält jede Disc
für englisch. Ein Test hält das fest.
**Angewendet wird bei der KOMPRESSION, nicht beim Rippen** — drei Gründe: Der Rip
bleibt vollständig und verlustfrei (Muss-Feature laut KONZEPT); HandBrake hat
dafür dokumentierte Schalter (`--audio-lang-list` / `--subtitle-lang-list`, die
genau die ISO-639-2-Codes nehmen, die MakeMKV liefert — beides gegengeprüft); und
wer später andere Sprachen will, komprimiert neu statt die Disc wieder
einzulegen. Genau das sagt der Dialog auch, sonst glaubt man, es werde
unvollständig gerippt.
Nichts angeklickt heißt „alles behalten". Auch die Einstellung ist bewusst LEER
vorbelegt: Ein stilles „deu" würde bei einem japanischen Original die
Originaltonspur wegwerfen, ohne dass jemand gefragt hat. Wunschsprachen für die
Automatik stehen unter Einstellungen → Verarbeitung.
### 3. DER EXTERNE WORKER: Deinstaller, Verwaltungsfenster, Slots
**Der Deinstaller lief nicht mehr** — reproduziert mit echtem PowerShell:
```
Der Typ [System.Windows.Forms.MessageBox] wurde nicht gefunden.
```
`Add-Type -AssemblyName System.Windows.Forms` stand **eine Zeile zu spät**. Der
Deinstaller starb in seiner ersten Arbeitszeile, jedes Mal. Zwei weitere Mängel
gleich mit: ASCII-Kodierung trotz Umlauten, und `Remove-Item -Recurse -Force
$PSScriptRoot` löscht den Ordner, in dem das laufende Skript liegt (klappt auf
Windows nicht zuverlässig — venv-DLLs sind geladen). Jetzt räumt ein losgelöstes
`cmd` nach, sobald PowerShell weg ist. Der erzeugte Deinstaller ist
gegengeprüft: parst, BOM da, Umlaute intakt.
**Verwaltungsfenster** (Doppelklick aufs Tray): Status, Aufgaben, Log, plus
Knöpfe für Rippy, Log-in-Rippy und Deinstallieren — das geht damit auch aus dem
Tray-Menü. Eigener PROZESS statt Fenster im Tray, weil pystray und tkinter beide
den Haupt-Thread wollen; tkinter statt WinForms, weil es bei jeder
Windows-Python-Installation dabei ist. Headless gerendert und angesehen. Alles
Fachliche kommt von Rippy (`/capabilities`, `/jobs`), damit dort keine zweite,
abweichende Wahrheit steht.
**Slots einstellbar.** Vorher fest `--pool=solo` = genau EIN Auftrag. Der
Installer fragt die Zahl jetzt, Vorbelegung ab 12 Kernen zwei, sonst einer:
HandBrake nutzt schon alle Kerne, aber x265 skaliert nicht linear. Auf Windows
gibt es keinen prefork-Pool (kein `fork`) — deshalb `--pool=threads`, was passt,
weil die Arbeit ein Kind-Prozess ist und der Thread nur wartet. Auf dem
Commander-PC erkennt der Installer 16 Kerne und schlägt 2 vor.
**Log geht nach Rippy** (`logbruecke.py`, Quelle `w:<name>`), die Logs-Seite hat
Knöpfe je Quelle. Durchgelassen wird wenig und mit Grund: Die Job-Meldungen
stehen längst in Rippy; gefehlt hat, was DANEBEN passiert (hochgefahren oder
nicht, Verbindung, Abstürze). Dazu eine Drossel (30 Zeilen/Minute), die MELDET,
wieviel sie verschluckt hat. Die lokale Datei bleibt — sie ist genau dann die
einzige Auskunft, wenn Rippy nicht erreichbar ist.
### 4. SCHLÜSSEL-AUTOMATIK für 4K-UHD (auf Commander-Entscheid)
`makemkvcon` unter LINUX ruft Disc-Schlüssel nie ab, die WINDOWS-Version schon.
Bisher Handarbeit: Laufwerk an den PC, Disc öffnen, `_private_data.tar` suchen,
im UI hochladen. Läuft jetzt von selbst — **zweischichtig, mit Absicht**:
1. **Der Wächter (verlässlich):** sieht `_private_data.tar` nach und lädt sie zu
Rippy hoch, sobald sie sich geändert hat. Keine Laufwerkserkennung, nichts
geraten. Deckt auch ab, dass man die Disc einfach in MakeMKV öffnet.
2. **Das Anstoßen (nach bestem Wissen):** liegt eine Disc im Laufwerk, wird
`makemkvcon info` darauf losgelassen — dabei holt MakeMKV den Schlüssel.
Warum getrennt: Das Format der BELEGTEN `DRV:`-Zeile ließ sich nicht messen (der
Commander-PC hat kein optisches Laufwerk — alle 16 Plätze melden
`DRV:i,256,999,0,"","",""`, das ist gemessen). Geraten wird also nur in Schicht 2,
und wenn die Vermutung falsch ist, passiert dort einfach nichts. **Die teure
Annahme steckt nie im verlässlichen Teil.**
Gemessene Fundstellen: Datenverzeichnis ist `%USERPROFILE%\.MakeMKV` (NICHT
`%APPDATA%\MakeMKV`) — dort lag die echte Datei mit 6.420.480 Bytes. Programm:
`C:\Program Files (x86)\MakeMKV\makemkvcon64.exe`, v1.18.4. Die Automatik
schaltet sich ab, wenn MakeMKV fehlt: Auf einem reinen Encoding-PC gibt es nichts
zu holen.
### 5. DIE 150 SEKUNDEN — drei Ursachen in einer Kette
Der Commander: *„150 Sekunden für das Neu-Anbinden eines Mounts? Das ist verrückt
langsam."* Er hatte recht, und die erste Vermutung war falsch. Erst die
Zeitstempel im Log zeigten, wo die Zeit sitzt:
```
12:53:56 API gestartet
12:54:03 „antwortet nicht — wird neu verbunden" ← Erkennung: 7 s, gut
12:57:18 „eingehängt" ← Reparatur: 3 min 15 s
```
Die Reparatur hing in der **ersten Zeile** von `mounten()`:
```python
os.makedirs(ziel, exist_ok=True)
```
`exist_ok` prüft mit `os.path.isdir`, und ein `stat` auf einen toten CIFS-Mount
blockiert im Kernel bis zum SMB-Timeout. Ausgerechnet der Aufruf, der „lege den
Ordner an, falls er fehlt" bedeutet, hing drei Minuten — **bevor** irgendeine der
sorgfältig begrenzten Prüfungen dran war. Dritter Fund derselben Sorte an einem
Tag: os-Aufruf auf einen Netzpfad ohne Zeitgrenze.
Jetzt klärt `pfad_lage()` das mit einem abbrechbaren Kind-Prozess (`timeout 4
ls -d`, drei Antworten: da / weg / unklar). Dieselbe Falle steckte in
`reparieren()` (`os.path.ismount` als Vorbedingung) und `aushaengen()`
(`ismount` + `rmdir`). Dazu: Wache prüft erstmals nach 3 s statt 60 s, benutzt
zum Erkennen die schnelle Einzelprobe, und die **Dauer steht jetzt im Log**.
**Gemessen: 202 s → 8 s.**
### 6. DER WIDERSPRUCH AUF DEM DASHBOARD
Oben stand „Akira im Laufwerk erkannt", die Server-Status-Karte gleichzeitig
„Bereit — keine Disc in Arbeit / Disc einlegen". Zwei Aussagen, ein Blick. Die
Karte fragt jetzt `/devices` und sagt „Disc erkannt — wartet auf Rippen
starten'" samt Titel.
### 7. WEITERGABE GEPRÜFT — drei Lücken geschlossen
Nicht durch Lesen, sondern indem ich mich wie ein fremder Rechner verhalten habe:
frischer `git clone` von Gitea in ein leeres Verzeichnis, dann
`./install.sh --nur-pruefen`. **2,6 MB, alles grün**, Laufwerk samt richtigem
sg-Knoten über die SCSI-Adresse erkannt. Der Weg trägt.
Drei Lücken fielen auf:
1. **In Beispielbefehlen stand meine IP.** `install.ps1` und
`remote-transcode-worker.yml` nannten 192.168.178.162 — genau die Zeile, die
ein Fremder kopiert. Jetzt Platzhalter, beim Windows-Installer mit dem Hinweis,
wo die richtige IP steht (und dass es NICHT die des eigenen PCs ist).
2. **Der Assistent fragte den MakeMKV-Beta-Key nicht.** Er stand in der README,
in der `.env` und in den Einstellungen — nur nicht dort, wo man beim
Einrichten hinsieht. Ein Fremder installiert, legt eine Blu-ray ein und
bekommt später einen Fehlschlag, ohne Hinweis. **Wahrscheinlichste
Stolperstelle einer frischen Installation.** Jetzt fragt der Assistent ihn ab,
mit dem Unterschied im Klartext: DVDs gehen ohne, Blu-ray braucht ihn — und er
ist NICHT der Disc-Schlüssel einer 4K-Disc.
3. Eine Docstring nannte die NAS-IP als Beispiel → neutral.
Tests und Log-Beispiele behalten die echten Namen: Sie dokumentieren Messungen.
### NOCH OFFEN
**1. Der gemeinsame Weitergabe-TEST steht aus** — geprüft ist die Vorbereitung,
nicht der Durchlauf auf einem fremden Rechner. Ungetestet bleibt dabei weiterhin
`install.sh` **als root** (Verzeichnisse anlegen, `mount --make-rshared`,
systemd-Unit) — auf dieser VM sind alle root-Schritte No-Ops.
**2. Der api-Container HÄLT die NAS-Verbindung** (Namespace-Befund aus v3.18).
Startet er mitten in einem Rip neu, verliert auch der Worker sein Ziel. Bis auf
Weiteres: nicht deployen, während ein Rip läuft. Sauber wäre ein Mount auf dem
HOST — eigener Umbau, widerspräche „Speicherziele über das UI".
**3. Die Schublade der VM steht offen** und braucht einen Handgriff.
**4. Der externe Worker läuft weiter mit altem Code.** Er braucht einen Lauf des
neuen Installers für: Pfad-Mapping, Preset-Meldung, Log-Brücke, Tray-Anzeige,
Slots, Verwaltungsfenster, Schlüssel-Automatik. Danach sind auch die
Live-Gegenproben möglich, die jetzt fehlen — mehrere Encodes gleichzeitig
(bisher nur die richtige Celery-Option, kein gemessener Durchsatz), die
Schlüssel-Automatik an einer echten UHD-Disc, und die belegte `DRV:`-Zeile.
**5. Sprachauswahl live an einem echten Encode** — der Scan ist gegengeprüft
(Sprachen kommen korrekt an), die Anwendung in HandBrake noch nicht.
**6. Unverändert offen:** Live-Gegenprobe der Metadaten-Kette (MyAnimeList war
die ganze Sitzung weg, HTTP 504); ISO-Sicherung und Mehr-Laufwerk-Betrieb;
Klartext-Geheimnisse in der settings-Tabelle; VM-CPU-Typ auf `qemu64`; kein
TypeScript-Typcheck.
### FALLEN DIESER RUNDE
- **Dreimal derselbe Fehlertyp an einem Tag:** ein `os`-Aufruf auf einen Netzpfad
ohne Zeitgrenze (`isdir` in der Rohdaten-Suche, `makedirs` im Mounten,
`ismount`/`rmdir` im Reparieren/Aushängen). Wer im Container einen Pfad unter
`/app/media` anfasst, nimmt einen abbrechbaren Kind-Prozess.
- **Meine erste Erklärung für die 150 s war falsch** (Wettlauf mit `umount -l`).
Die Änderung machte es sogar langsamer (202 s). Erst Zeitstempel im Log haben
die Stelle gezeigt. Lehre: bei „langsam" nicht die plausibelste Ursache
beheben, sondern die Dauer je Schritt messbar machen.
- **Ein Stub muss zur neuen Aufrufzahl passen.** Zwei Tests brachen, als eine
Prüfung zu zwei wurde bzw. ein `timeout ls` dazukam. Besser die HÖHERE Funktion
stubben als Aufrufe zählen.
- **`Add-Type` gehört VOR die erste Benutzung des Typs.** Klingt banal; hat den
Deinstaller monatelang komplett lahmgelegt, ohne dass es auffiel.
---
## Vorheriger Stand: v3.18 — Auswurf, externer Worker, und DIE Mount-Ursache (26.07.2026, Abend)
> **Deployt und live gegengeprüft.** Zwei Commander-Meldungen: Das Laufwerk geht
> nach dem Rip nicht auf, und der externe Worker „ist ein bisschen dünn". Beide
> abgearbeitet — und unterwegs ist die Ursache gefallen, die v3.17 noch als
> „liegt beim NAS, nicht gefunden" führte. Sie lag bei uns.
### ZUSTAND, gemessen
| | |
|---|---|
| Repo + VM | `4cf7acb`, Ampel **grün**, deployt |
| Tests | **228** grün (Sitzungsbeginn: 130) |
| Freigabe nach dem Deploy | **heilt sich selbst in ~150 s**, ohne Handgriff |
| Job `95afdc89` | `can_retry = true`, 74,1 GB erreichbar |
| ⚠️ Laufwerks-Schublade | **steht OFFEN** — vom Auswurf-Test, per Software nicht schließbar (siehe unten) |
### 1. DER AUSWURF: das ioctl meldete Erfolg und tat nichts
Commander: *„Den Button gibt es in den Settings, aber es passiert nicht, das
Laufwerk geht nicht auf."* An der Disc, die gerade drin lag, nachgestellt:
```
wirf_disc_aus("/dev/sr0") → True
CDROM_DRIVE_STATUS danach → 4 (Disc drin)
```
Das ioctl wird **angenommen und tut nichts**. Ursache: MakeMKV verriegelt während
des Rips die Laufwerkstür (`CDROM_LOCKDOOR 1`) und entriegelt sie nicht wieder.
Ein verriegeltes Laufwerk quittiert den Auswurf trotzdem mit Erfolg. Gegenprobe
an derselben Disc:
```
CDROM_LOCKDOOR 0 + CDROMEJECT → Status 2 (SCHUBLADE OFFEN)
```
Genau das macht das Werkzeug `eject` immer: erst entriegeln, dann auswerfen.
**Die wichtigere Hälfte des Fixes:** Das Ergebnis wird jetzt GEPRÜFT statt
geglaubt (bis zu 5 s, die Schublade braucht ein bis zwei; „kein Datenträger"
zählt mit, weil ein Slot-Laufwerk keine Schublade hat). Deshalb ist der Fehler
in v3.14 durchgerutscht: Dort wurde richtig festgestellt, dass die Einstellung
von niemandem gelesen wurde — danach WURDE sie gelesen, ausgeworfen wurde
weiterhin nicht, und im Log stand „Disc ausgeworfen". Dieselbe Lücke steckte im
Auswurf-Knopf der API.
**Live bewiesen** (API-Weg, also der Knopf im UI): aus „Disc drin" wurde in
**0,67 s** „Schublade offen".
**Zum eigentlichen Ziel — zweite Disc parallel:** Das war nur am Laufwerk
gescheitert. `has_active_job` blockiert ausschließlich bei `pending`/`running`,
ein Job in `transcoding` gibt das Gerät also längst frei, und der Worker läuft
ohne `--concurrency`, also mit 4 Slots. Der Auswurf sitzt auch an der richtigen
Stelle: nach dem Rip, VOR dem Einreihen der Kompression.
⚠️ **Die Schublade der VM steht jetzt offen.** Der Test hat sie geöffnet, und
`CDROMCLOSETRAY` bleibt wirkungslos (viermal versucht, mit bis zu 15 s Wartezeit
je Versuch) — das BU40N will von Hand zugeschoben werden. Deshalb ist der
AUTOMATISCHE Weg (`wirf_disc_aus` nach einem Rip) nicht noch einmal live
gemessen: identischer Code, durch Tests gedeckt, aber der Live-Beweis steht nur
für den API-Weg.
### 2. DER EXTERNE WORKER: Log nach Rippy, Anzeige, Slots
Commander: *„der externe Encoder Worker ist ein bisschen dünn — der könnte noch
viel mehr"*, dazu ausdrücklich *„bessere Log-Ansichten (kein txt file → direkt
von Rippy Logs)"*.
**Log geht nach Rippy** (`worker/logbruecke.py`). Vorher schrieb das Tray nach
`%LOCALAPPDATA%` und öffnete die Datei im Editor — wer wissen wollte, warum der
Worker nichts tut, musste sich an den PC setzen. Jetzt landen die wichtigen
Zeilen in Rippys Log-Tabelle (Quelle `w:<name>`), und die Logs-Seite hat Knöpfe
je Quelle: „was macht mein PC" ist ein Klick.
Durchgelassen wird **wenig**, und das mit Grund: Die Job-Meldungen stehen längst
in Rippy (`tasks.py` schreibt sie selbst). Es fehlte, was DANEBEN passiert und
den Worker unbrauchbar macht, ohne dass ein Job existiert — hochgefahren oder
nicht, Verbindung zu Redis/Postgres, Abstürze. Alles andere fliegt weg: Celery
ist bei `--loglevel=info` gesprächig, die `logs`-Tabelle hat **keine**
automatische Aufräumung. Dazu eine Drossel (30 Zeilen/Minute), die MELDET,
wieviel sie verschluckt hat. Das Zeilenformat ist wörtlich aus dem laufenden
Container abgenommen (Celery 5.4.0). Die lokale Datei bleibt — sie ist genau dann
die einzige Auskunft, wenn Rippy nicht erreichbar ist.
**Das Tray zeigt, was läuft.** Vorher stand dort „läuft" oder „gestoppt" — auf
einer Maschine, die stundenlang an einem Film rechnet, ist das keine Auskunft.
Jetzt Titel, Prozent und Restzeit, geholt von Rippys `/jobs`. Bewusst dieselbe
Quelle wie das Dashboard, damit im Tray nicht eine zweite, abweichende Schätzung
steht. Dazu: **Windows schläft nicht mehr mitten im Encode ein**
(`SetThreadExecutionState`, ohne `ES_DISPLAY_REQUIRED` — der Bildschirm darf
ausgehen). Die Sperre wird zurückgenommen, sobald nichts läuft, und auch bei
einem harten Ende des Trays — sonst schläft der PC nie wieder ein und niemand
weiß warum.
**Mehrere Encodes gleichzeitig.** Der Worker lief fest mit `--pool=solo` und nahm
genau EINEN Auftrag an. Der Installer fragt die Zahl jetzt (Feld neben dem Namen,
erkannte Kernzahl daneben), Vorbelegung ab 12 Kernen zwei, sonst einer: HandBrake
nutzt schon alle Kerne, aber x265 skaliert nicht linear. Auf Windows gibt es
keinen prefork-Pool (kein `fork`) — deshalb `--pool=threads`, was hier passt,
weil die Arbeit ein Kind-Prozess ist und der Thread nur wartet.
**Zur Frage des Commanders** *„würden wir mit auch rippen können' nicht den Sinn
von Rippy aushebeln?"* — teilweise ja, und die Antwort steht als Empfehlung:
Ein Windows-Ripper bräuchte eine **zweite komplette Laufwerks-Erkennung**
(`detection.py` steckt voller Linux-ioctls), also einen zweiten Unterbau mit
eigenen Fehlern. Der einzige harte Vorteil ist, dass MakeMKV unter Windows die
4K-Disc-Schlüssel selbst holt. Das ist viel billiger zu haben: ein kleiner
Helfer, der bei eingelegter Disc MakeMKV öffnen lässt und `_private_data.tar`
automatisch zu Rippy hochlädt. **Entscheidung steht beim Commander, nichts
gebaut.**
### 3. DIE MOUNT-URSACHE — sie lag nicht am NAS, sondern bei uns
v3.17 führte das als „Ursache liegt beim NAS, nicht gefunden". Gemessen in
`/proc/fs/cifs/DebugData`:
```
Mount-Verbindung → Net namespace: 4026532653
api-Container → net:[4026532653] ← DIESELBE
worker-Container → net:[4026532540] ← andere
```
**Die CIFS-Verbindung lebt in der Netz-Namespace des api-Containers** — dort wird
sie eingehängt, weil nur dieser Container `CAP_SYS_ADMIN` hat. Wird der Container
neu gebaut, stirbt sein Netz-Namespace und mit ihm der Socket. Der Mount steht
danach weiter in `/proc/mounts` (per rshared auf den Host propagiert) und sieht
vollkommen gesund aus — aber jeder Zugriff läuft in den CIFS-Timeout.
Damit erklärt sich alles, was vorher widersprüchlich aussah: warum es nach JEDEM
Deploy passiert, warum `mount` Erfolg meldet, warum genau eine korrekte Schicht
in `/proc/mounts` steht, und warum nur ein echtes Neu-Verbinden hilft. **Das NAS
ist unschuldig** — eine Sitzung, alles Status 1, 630 Credits, Ping 0,47 ms.
**Zweiter Fund, der den Rest erklärt:** Direkt nach einem frischen Mount
antwortete die Freigabe — und Sekunden später nicht mehr. Das ist ein Wettlauf
mit `umount -l`: lazy heißt, der Abbau passiert später, und fällt er samt
Propagation hinter den neuen Mount, zeigt der Pfad wieder auf die Leiche. Eine
einzige Probe kann das nicht sehen — deshalb prüft `wirklich_erreichbar()`
**zweimal mit drei Sekunden Abstand**.
**Gebaut:** eine Mount-Wache (erste Prüfung nach 10 s, dann minütlich), die
stumme Freigaben neu verbindet. Zwei Dinge daran sind wichtiger als die Heilung:
Sie rührt **nie** etwas an, solange irgendein Job nicht durch ist (neu verbinden
heißt `umount -l`; mitten in einem Rip wäre das Datenverlust — `db.hat_arbeit()`,
`pending` zählt mit), und sie meldet nur den **Übergang**, nicht jede Minute
(ein ausgeschaltetes NAS wäre sonst ein Log-Wasserfall).
**Live bewiesen, ohne einen Handgriff:** Nach `docker compose up -d --build` war
die Freigabe stumm; nach rund 150 s stand im Log „antwortet nicht — wird neu
verbunden" → „neu verbunden", und `can_retry` war wieder `true`. Vorher heilte
das **nie** von selbst.
### NOCH OFFEN
**1. Der api-Container HÄLT die NAS-Verbindung.** Startet er mitten in einem Rip
neu, verliert auch der Worker sein Ziel — das ist die strukturelle Folge des
Namespace-Befunds. Das saubere Gegenmittel wäre ein Mount auf dem HOST
(systemd/fstab) statt im Container; das ist ein eigener Umbau und widerspräche
„Speicherziele über das UI einhängen" (Commander-Anforderung 23.07.). **Zu
entscheiden.** Bis dahin gilt: nicht deployen, während ein Rip läuft.
**2. Die Schublade der VM steht offen** und braucht einen Handgriff (siehe oben).
**3. Der Auswurf nach einem echten Rip ist nicht live gemessen** — identischer
Code wie der bewiesene API-Weg, durch Tests gedeckt, aber der Beweis fehlt, weil
die Schublade sich nicht per Software schließen lässt.
**4. Der externe Worker läuft weiter mit altem Code.** Er braucht einen Lauf des
neuen Installers, um Pfad-Mapping, Preset-Meldung, Log-Brücke, Tray-Anzeige und
Slots zu bekommen. Danach ist auch die Live-Gegenprobe von „mehrere Encodes
gleichzeitig" möglich — bisher ist das nur die richtige Celery-Option, kein
gemessener Durchsatz.
**5. „Auch rippen können"** — Entscheidung steht beim Commander (siehe oben).
**6. Unverändert offen aus v3.17:** Live-Gegenprobe der Metadaten-Kette
(MyAnimeList war die ganze Sitzung weg, HTTP 504); ISO-Sicherung und
Mehr-Laufwerk-Betrieb; Klartext-Geheimnisse in der settings-Tabelle; VM-CPU-Typ
auf `qemu64`; `install.sh` nie als root gelaufen; kein TypeScript-Typcheck.
### FALLEN DIESER RUNDE
- **Ein ioctl-Rückgabewert beweist nichts.** Zweimal in einer Sitzung dasselbe
Muster: `CDROMEJECT` quittiert Erfolg auf einem verriegelten Laufwerk, `mount`
quittiert Erfolg auf einer Verbindung, die gleich stirbt. Wo eine Wirkung
prüfbar ist, prüfe die Wirkung.
- **`/proc/fs/cifs/DebugData` ist die Antwort auf „warum hängt der Mount".**
Sitzungen, Shares, Credits, TCP-Status — und die Netz-Namespace, die hier den
Fall gelöst hat.
- **Tests, die `main` importieren, gehören in `test_api_smoke.py`** — nur dieses
Modul überspringt sich unter Windows selbst. In `test_mounts_helpers.py`
brachen sie den lokalen Lauf.
- **Ein Stub muss zur neuen Aufrufzahl passen.** `iter([False, True])` lief in
StopIteration, als eine Prüfung zu zwei wurde. Besser die HÖHERE Funktion
stubben als die Anzahl der Aufrufe nachzählen.
---
## Vorheriger Stand: v3.17 — der Blocker ist zu, neun Punkte abgearbeitet (26.07.2026)
> **Deployt und live gegengeprüft.** Auftrag war „lies den Savepoint und lass uns
> das Projekt endlich beenden". Die neun Commander-Punkte aus v3.16 sind
> abgearbeitet; unterwegs kamen vier Bestandsfehler dazu, die vorher niemand
> gesehen hat. Was offen bleibt, steht ganz unten — und es ist wenig.
### ZUSTAND, gemessen
| | |
|---|---|
| Repo + VM | `87537bf`, Ampel **grün**, deployt |
| Container | alle 5 `Up`, api/postgres/redis `healthy` |
| Tests | **211** grün (vorher 130) |
| Job `95afdc89` | `failed`, aber **`can_retry = true`** — die 74,1 GB sind erstmals über das UI erreichbar |
| Rohschnitt | `/app/media/rippy/95afdc89-…/title_t00.mkv`, 79.604.951.639 Bytes, intakt |
| Platte VM | 148 GB, 107 GB frei |
| NAS-Freigabe | eingehängt und antwortend — **aber siehe „NOCH OFFEN" Punkt 1** |
### DER BLOCKER IST ZU — und er brauchte keine Eingabe vom Nutzer
`RIPPY_PATH_MAP` wurde von niemandem gesetzt; deshalb konnte externes Encoden
**nie** funktionieren. Die Lösung musste nichts erfinden: **Rippy hat die
Freigabe selbst eingehängt und kennt ihre Quelle.** Mountpunkt plus Quelle IST
das Mapping.
```
GET /worker-setup/pfad-map
→ /app/media/rippy=\\192.168.178.62\rippy
```
Live auf der VM abgefragt, Antwortzeit 0,010 s. Beide Windows-Installer holen
diesen Wert selbst, prüfen mit `Test-Path`, ob **dieser PC** die Freigabe
erreicht, und schreiben ihn in `start-tray.bat` und `start-worker.bat`. Der
Commander muss nichts über Container-Pfade wissen — das Feld bleibt leer.
Ist keine Freigabe eingehängt, sagt der Endpunkt das im Klartext samt Abhilfe,
statt ein leeres Mapping zu liefern.
**Was der Commander jetzt tun kann:** `RippyWorkerSetup.exe` neu holen
(Einstellungen → Worker) und laufen lassen. Danach steht bei „Neu komprimieren"
sein Ryzen mit avx512f und der RX 9070 XT zur Wahl, und die 74 GB gehen durch,
ohne die Disc noch einmal einzulegen.
### DIE NEUN PUNKTE — Stand jetzt
| Sein Punkt | Stand |
|---|---|
| 1. „Bei den Presets soll IMMER das Beste ausgewählt werden" | **erledigt** — Knopf „Bestes wählen", Begründung unter jeder Liste |
| 2. „Hier steht noch meine Settings drin" | erledigt (v3.16) |
| 3. „Vektorbefehle müssen ausgelesen werden" | erledigt (v3.16) |
| 3b. „…wenn der Worker AV1 oder besseres kann, immer diesem empfehlen" | **erledigt** — Hardware-Presets nach Familie, AV1 vor H.265 |
| 3c. „…Warnung verschwindet / orange zu grün" | erledigt (v3.16) |
| 4. „JIKAN ist drin — wird das genutzt?" | **beantwortet: ja** — und ab jetzt an der richtigen Stelle in der Kette |
| 5. „Was wenn der Unterbau nicht Debian ist?" | **erledigt** — Paketwerkzeug wird erkannt |
| 6. „Mein PC hat ne dicke Grafikkarte" | **erledigt** — Erkennung (v3.16) + Presets + Pfad-Mapping, die Kette ist vollständig |
| 7. „Warum wird der Datei-Browser noch angezeigt" | **erledigt** — eingeklappt |
| 8. „Server Status muss dringend überarbeitet werden" | **erledigt** — zwei Placebos entfernt |
| 9. „ETA im externen Worker und im Dashboard" | **erledigt** — Restzeit aus gemessenem Fortschritt |
### GEBAUT — was das Raten beendet
**Presets kommen vom Worker, nicht aus dem Quelltext.** `caps.py` meldet
`HandBrakeCLI --preset-list` (auf der VM: **90 Namen**), `api/presets.py`
staffelt daraus die Empfehlung. Kein Punktesystem: für jede Lage eine feste
Reihenfolge echter Namen, genommen wird der erste, den der Worker kennt.
> ⚠️ **Richtigstellung zu v3.16:** Dort stand, die Namen der Hardware-Presets
> seien auf der Rippy-VM „nicht ermittelbar", weil deren HandBrake keinen
> Hardware-Encoder hat. **Gemessen ist das falsch** — die Kategorie `Hardware/`
> steht vollständig in der Liste (`H.265 VCN 2160p 4K`, `AV1 QSV 2160p 4K`,
> NVENC, MF). HandBrake trennt zwei Fragen: `--preset-list` nennt alle Presets,
> `--help` nur die nutzbaren Encoder. Zwei Fragen, zwei Quellen. Der vermeintliche
> Blocker existierte nicht.
Feinheiten, die zählen: Der Encoder heißt `vce`, das Preset heißt **VCN** — ohne
diese Zuordnung liefe die AMD-Karte unter einem Intel-Namen. `vaapi` bekommt
bewusst **kein** Hardware-Preset (HandBrake 1.6.1 liefert keines mit), `MF` wird
nie empfohlen (Nutzbarkeit nicht ablesbar), und für DVD-Auflösungen gibt es
keine Hardware-Presets. Kennt ein Worker keinen der bekannten Namen, sagt das UI
„selbst wählen" statt etwas Falsches einzustellen. Und HandBrakes
`Invalid preset <Name>` wird jetzt übersetzt — vorher stand da „Code 3".
**Dashboard: zwei Placebos raus.** Die Karte hieß „Echte Live-Daten" und log an
zwei von drei Stellen. „Auslastung: 0 % (Aktiv)" war nicht die CPU-Last, sondern
der Job-Fortschritt (bzw. eine feste 15 bzw. 5). Die Verlaufskurve daneben war
`Math.random()`. Und „N Worker Online" zählte registrierte Einträge, auch Leichen
alter Rebuilds. Jetzt: laufende Phase mit **Restzeit**, jeder erreichbare Worker
mit Kernen/Vektorbefehlen/extern, freier Platz mit Warnung, wenn er nicht mehr
für eine Disc reicht.
**Restzeit ehrlich.** Messreihe je Phase (Rip und Kompression haben nichts
miteinander zu tun), Stillstand verlängert die Schätzung, unter zwei Messwerten
oder 60 Sekunden Spanne gibt es **keine** Aussage — dann steht „Restzeit wird
gemessen". Mit den echten 25.07.-Werten gegengerechnet: 1,44 % in 29 min ergibt
„noch ca. 1 Tag 23 h".
**Warnung VOR dem Start.** Wählt man einen externen Encoder, der Quelle oder Ziel
nicht erreicht, steht das im Rip-Dialog — nicht erst nach einer Stunde. Bei
„Automatisch" wird genannt, welcher Worker die Aufgabe kaputtmachen könnte.
**Metadaten: exakt schlägt unscharf.** Das Gate 0,55 war nachrechenbar, weil
`titel_aehnlichkeit` rein ist:
```
"Alien" vs. "Alien 9" → 0,83 TRIFFT
"Inception" vs. "Deception" → 0,78 TRIFFT
"Hero" vs. "Heroman" → 0,73 TRIFFT
"The Dark Knight" vs. "Dark Knight Rises" → 0,69 TRIFFT
```
Ein Kinofilm, den TMDB verpasst, bekam damit Anime-Metadaten, und OMDb wurde nie
gefragt. **Und die Vermutung „das Gate ist zu großzügig" ist trotzdem falsch:**
Für den Fall, für den Jikan eingebaut wurde, ist es zu **streng**
„Evangelion 2.22" gegen den MAL-Titel ergibt 0,51 und fällt durch. Eine einzelne
Zahl kann beides nicht leisten. Jetzt zwei Schwellen: nur ein praktisch exakter
Titel (≥ 0,9) beendet die Kette, ein unscharfer Treffer wird gemerkt, dann kommt
OMDb, und erst wenn OMDb nichts hat, gilt er als **Vorschlag** (0,6).
### VIER BESTANDSFEHLER, die beim Gegenprüfen auffielen
Alle vier durch **Messen an der laufenden Instanz** gefunden, nicht durch Lesen.
**1. „Neu komprimieren" ging überhaupt nicht.** Der Savepoint v3.16 schrieb:
„Rohschnitt 79,6 GB intakt → Neu komprimieren' genügt". Gemessen war
`can_retry = false`**den Knopf gab es nicht.** Gesucht wurde nur in
`/app/temp/raw` und unter dem *aktuellen* `workDir`; der Rip lag aber auf der NAS,
weil beim Start eine Wahl **nur für diesen Rip** getroffen worden war, und die
Einstellung stand auf leer. Dasselbe in `retry_transcode`: es hätte am falschen
Ort gesucht. Neues Modul `rohdaten.py` sieht jetzt an allen Orten nach, die
überhaupt in Frage kommen. **Wieder derselbe Fehler:** aus einem Zustandswert
(der heutigen Einstellung) auf einen Mechanismus (wohin damals gerippt wurde)
geschlossen.
**2. `os.path.isdir` hing im KERNEL und tötete eine Hintergrund-Schleife.**
Zwei Threads des API-Prozesses standen im Zustand **D** (uninterruptible sleep).
Die Rohdaten-Schleife startete ihren ersten Durchlauf, während Rippy die
CIFS-Freigabe neu einhängte — `asyncio.to_thread` kam nie zurück, die Schleife
erreichte ihr `sleep` nie und war **für immer tot**. Ein Timeout um den Aufruf
hätte nichts geholfen: ein im Kernel hängender Thread lässt sich aus Python nicht
abbrechen. Ein Kind-**Prozess** lässt sich abbrechen — geprüft wird jetzt mit
`timeout 4 ls -d`, dasselbe Werkzeug, das `mounts.ist_erreichbar` seit dem
24.07. benutzt.
**3. „Konnte nicht nachsehen" wurde als „ist weg" gewertet.** Nach einem
Container-Neustart stallt der erste Zugriff auf die Freigabe; in diesem Fenster
verschwand der Knopf „Neu komprimieren", obwohl 74 GB dalagen. Jetzt drei
Antworten statt zwei (`da` / `weg` / `unklar` — 124 ist der Rückgabewert von
`timeout`), und bei „unklar" wird die letzte bekannte Antwort gehalten.
**4. Der NAS-Mount kam nach einem Rebuild nicht zurück.** `mounten()` glaubte
`os.path.ismount` — und der Mountpunkt existiert im neuen Container weiter
(Bind-Mount vom Host), also brach das Wiederherstellen genau dort ab. Dazu öffnet
`schreibtest()` eine Datei **ohne Zeitgrenze**: auf einem toten CIFS blockiert das
im Kernel. Jetzt entscheidet `ist_erreichbar()` zuerst, und nach dem Mount wird
geprüft statt geglaubt (zwei Versuche, dann ehrlicher Fehler).
### DIE FALLE, DIE DIE MEISTE ZEIT KOSTETE
**Ein `except Exception: pass` in einer Hintergrund-Schleife.** Der Vorrat blieb
leer, die Funktion lief direkt aufgerufen einwandfrei, und **warum** sie im
Server nichts tat, war nirgends zu sehen. Erst ein Blick auf die Thread-Zustände
in `/proc/<pid>/task/*/stat` brachte das D. Konsequenzen, beide gebaut: Die
Schleife **meldet** ihren Fehler jetzt, und es gibt `GET /health/vorraete` — sagt
für Ping- und Rohdaten-Vorrat, wie alt der letzte Durchlauf ist. Bleibt so ein
Vorrat leer, ist am Endpunkt selbst nämlich **nichts** zu sehen; er antwortet nur
dauerhaft „nichts gefunden".
Merksatz für die nächste Sitzung: Ein Hintergrund-Prozess, der still scheitert,
ist schlimmer als einer, der laut scheitert.
### NOCH OFFEN
**1. Die NAS-Freigabe antwortet nach einem Container-Neustart oft erst beim
zweiten Anlauf — Ursache liegt beim NAS, nicht bei Rippy.** Gemessen: `mount`
meldet Rückgabewert 0, `/proc/mounts` zeigt genau eine korrekt aussehende Schicht
mit den richtigen Optionen, `ist_erreichbar` bekommt direkt danach eine Antwort —
und Sekunden später läuft jeder Zugriff in die Zeitgrenze.
`POST /storage-mounts/rippy/repair` stellt sie dann in ~30 s her,
reproduzierbar. Das NAS ist per Ping bei 0,47 ms erreichbar. Rippy geht damit
jetzt sauber um (harte Zeitgrenzen, zwei Versuche, ehrliche Meldung,
Selbstheilung binnen 30 s), aber **die eigentliche Ursache ist nicht gefunden**
Verdacht: das NAS mag sieben Mount-Zyklen in zwanzig Minuten nicht. Zu prüfen,
wenn es im Alltag wieder auftritt: SMB-Protokollversion und Sitzungsgrenzen am
NAS, und ob ein `repair`-Aufruf beim API-Start als Automatik sinnvoll ist.
**2. Der externe Worker des Commanders läuft noch mit altem Code.** Er meldet
`simd=unbekannt`, `presets: 0`, `pfad_map: None` und stand bei der Prüfung auf
`online=false`. Alles, was diese Sitzung gebaut hat, wirkt für ihn erst nach
einem Lauf des neuen Installers.
**3. Live-Gegenprobe der Metadaten-Kette steht aus.** MyAnimeList war während
der ganzen Sitzung weg (Jikan antwortete durchweg HTTP 504). Die Arithmetik des
Gates ist davon unberührt und in `test_jikan_helpers.py` festgehalten; ein echter
Anime-Rip muss es noch bestätigen.
**4. Aus dem ARM-Vergleich, unverändert nicht gebaut:** ISO-Sicherung für
Datenträger, die weder Film noch Audio-CD sind, und **mehrere Laufwerke
gleichzeitig** (Rippy sieht sie, ob parallel gerippt wird, ist unbewiesen).
**5. Ältere Punkte, unverändert gültig:**
- Discord-Webhook und MakeMKV-Key liegen im **Klartext** in der settings-Tabelle.
Für Heimnetz erwartbar, aber der Webhook ist eine echte Zugangsberechtigung.
- **VM-CPU-Typ steht auf `qemu64`** → `host` würde AVX2 freischalten, 24×
schneller. Anleitung in der README („Rippy schneller machen"). Ein
VM-Neustart, nicht gemacht — das ist eine Entscheidung des Commanders.
- **`install.sh` ist nie als root durchgelaufen.** Verzeichnisse anlegen,
`mount --make-rshared` und die systemd-Unit bleiben ungetestet; sie laufen erst
bei einer echten Neuinstallation.
- Es gibt **kein TypeScript-Typcheck** im Projekt (`typescript` ist nicht
installiert, `npm run build` transpiliert nur mit esbuild). Typfehler im UI
fallen nirgends auf. Bewusst nicht geändert: `tsc` in die Ampel zu nehmen färbt
sie womöglich wegen Bestandscode rot, und das ist eine Entscheidung.
### FALLEN DIESER SITZUNG (für die nächste)
- **Ein Prozess-Zustand `D` in `/proc/<pid>/task/*/stat` heißt: im Kernel
blockiert.** Das ist der schnellste Weg, einen hängenden Netz-Zugriff zu
finden — schneller als jedes Log.
- **`os.path.join` für Container-Pfade ist unter Windows falsch**
(`/app/media\rippy`). Dritter Fund derselben Falle; diesmal hat ein Test sie
gefunden, nicht die Produktion. Immer `posixpath` für Pfade, die in Container
gehen.
- **Ein Umlaut-Werkzeug hat `install.ps1` von CRLF auf LF gestellt** — 470
Zeilen Diff-Rauschen für eine Änderung von 56. Nach automatisierten
Textänderungen an `.ps1` immer `file` gegenprüfen (BOM **und** CRLF).
- **Eigene Testerwartungen sind auch Behauptungen.** Zweimal lag der Test falsch
und der Code richtig (Confidence 0,3 statt 0,0; „1 Tag 23 h" statt „mehr als 2
Tage"). Beide Male hat die Ampel bzw. der lokale Lauf es gefangen.
- **Ein Test, der Quelltext-Positionen vergleicht, findet seine eigene Erwähnung
im Kommentar.** Verhalten prüfen, nicht Zeichenketten.
---
## Vorheriger Stand: v3.16 — ÜBERGABE: externes Encoden konnte nie funktionieren (26.07.2026)
> **Zuerst lesen.** Diese Sitzung endete wegen vollem Kontext. Der Auftrag war
> Commander-Feedback aus der ersten Selbst-Einrichtung plus die Diagnose eines
> fehlgeschlagenen Test-Rips. Die Diagnose ist abgeschlossen, zwei Commits sind
> gebaut — **aber NICHT deployt**, und der Kern-Blocker steht noch offen.
### ZUSTAND, gemessen
| | |
|---|---|
| Repo | `fd1feaa`, Ampel **grün** |
| **VM** | **`61c38a0` — die letzten 2 Commits sind NICHT aktiv** |
| Windows-Worker des Commanders | läuft mit **altem** Code (vor der Windows-Erkennung) |
| Job `95afdc89` | `failed` beim Encoden |
| **Rohschnitt** | **79,6 GB intakt** auf `/app/media/rippy/95afdc89-…/title_t00.mkv` (NAS) → „Neu komprimieren" genügt, **kein Neu-Rip** |
| Commander-PC | AMD Ryzen 7 9700X · **avx512f** · 16 Kerne · RX 9070 XT |
| Rippy-VM-CPU | QEMU-Generikum · nur `sse4_2` · 4 Kerne |
### DER KERNBEFUND: drei Ursachen, die ineinandergriffen
Der Commander vermutete „das System sieht den externen Encoder nicht". **Falsch —
das Routing funktionierte einwandfrei.** Gemessen:
```
meta.transcode_node = tobisnicerpc@TobisNicerPC
VM-Worker-Log = nur rip_disc, KEIN transcode_files
22:19:41.271 Kompression eingereiht
22:19:41.453 "Keine Roh-MKVs gefunden" ← 182 ms später
```
Sein PC nahm die Aufgabe an und lehnte sie sofort ab. Warum:
1. **`RIPPY_PATH_MAP` wird von NIEMANDEM gesetzt** — nicht vom Installer, nicht
vom UI, nicht von Compose. `pfad_lokal()` in `tasks.py` hat sogar Tests, aber
keinen Anschluss. `/app/media/...` sind Container-Pfade; ein externer Worker
sieht sie nur übersetzt. **Ein Windows-Worker konnte also nie transcodieren.**
⚠️ **DAS IST DER OFFENE BLOCKER.**
2. **Die Ablage war im UI überhaupt nicht einstellbar.** `outputDir` wurde von
Vollautomatik und Schnellwahl gelesen, hatte aber **kein Eingabefeld**. Das
Arbeitsverzeichnis hatte eine Auswahl eingehängter Ziele, das Ziel nicht.
Folge: Rohdaten auf der NAS (erreichbar), Ziel auf der VM-Platte (nicht
erreichbar — dort läuft **kein Samba**). Die richtige Einstellung war mit den
vorhandenen Bedienelementen **nicht erreichbar.** → behoben, siehe unten.
3. **Die Fehlermeldung log.** „Keine Roh-MKVs gefunden" klang nach kaputtem Rip,
obwohl der Rip vollständig war. → behoben.
### GEBAUT in dieser Sitzung (2 Commits, gepusht, Ampel grün)
**`fe12401`** — Hardware-Encoder + CPU-Merkmale auf Windows, ehrliche Fehler:
- **Hardware-Erkennung war MEIN Bug:** `leite_backends_ab()` verlangte
`/dev/dri` bzw. `nvidia-smi` — beides gibt es unter Windows nicht. Der PC des
Commanders meldete deshalb nur CPU, obwohl HandBrakes Windows-Build `vce_*`
kennt. Die Prüfung war zudem überflüssig: **HandBrake probiert Hardware selbst
an und listet nur Nutzbares** (auf der VM belegt: „qsv: not available", und
`qsv_*` fehlt dann in `--help`). Jetzt ist HandBrakes Liste die einzige
Auskunft, unterschieden nach Familie (`nvenc`/`qsv`/`vce`/`vaapi`) plus eigener
Kennung für Hardware-AV1.
- **Vektorbefehle unter Windows** über `IsProcessorFeaturePresent` (kernel32,
winnt.h) + CPU-Name aus der Registry. Auf dem Commander-PC gegengeprüft:
vorher „AMD64 Family 26 Model 68 / unbekannt", jetzt „AMD Ryzen 7 9700X /
avx512f". Damit verschwindet die AVX2-Warnung für diesen Worker von selbst.
- **`_erreichbarkeit_pruefen()`** unterscheidet jetzt „Pfad nicht erreichbar +
kein Mapping" (nennt `RIPPY_PATH_MAP` mit Beispiel), „Mapping greift nicht"
und „Ordner leer" — und prüft **beide** Pfade, nicht nur die Quelle. Jede
Meldung sagt ausdrücklich, dass die Rohdaten nicht verloren sind.
- Vier Platzhalter mit der Umgebung des Commanders (`192.168.178.20`,
`TOBIS-PC`, seine Jellyfin- und Rippy-IP) verallgemeinert.
**`fd1feaa`** — Ablage einstellbar + Kohärenz-Prüfung:
- Auswahlliste **„Ablage"** in Einstellungen → Ripping, gleiche Liste wie beim
Arbeitsverzeichnis. Wirkt automatisch in Vollautomatik und Schnellwahl, weil
beide `outputDir` lesen — genau wie vom Commander gefordert.
- **Warnung, sobald ein EXTERNER Worker gemeldet ist** und Ablage +
Arbeitsverzeichnis nicht beide auf einer erreichbaren Freigabe liegen. Drei
Fälle mit Konsequenz im Klartext (kann nicht lesen / kann nicht schreiben /
läuft, kostet aber eine Vollkopie).
- „Extern" ist keine Heuristik: `caps.py` meldet `extern` selbst (`/app` gibt es
im Rippy-Image immer, außerhalb nie) und zusätzlich `pfad_map`.
### COMMANDER-FEEDBACK aus der ersten Selbst-Einrichtung — wo steht was
Seine neun Punkte, damit nichts durchfällt. „→ Punkt N" verweist auf die
Prioritätenliste darunter.
| Sein Punkt | Stand |
|---|---|
| 1. „Bei den Presets soll IMMER das Beste ausgewählt werden" | **offen → Punkt 2** (hängt an der Preset-Liste vom Worker) |
| 2. „Hier steht noch meine Settings drin, muss verallgemeinert werden" | **erledigt** (`fe12401`, vier Platzhalter) |
| 3. „Vektorbefehle müssen ausgelesen werden" | **erledigt** (`fe12401`, avx512f auf seinem PC gemessen) |
| 3b. „…wenn der Worker AV1 oder besseres kann, immer diesem empfehlen" | **offen → Punkt 2** |
| 3c. „…Warnung verschwindet / orange zu grün" | **erledigt**`schwacheEncoderCpu()` schaltet ab, sobald SIMD schnell ODER Hardware da ist. Wirkt für seinen PC ab dem Deploy |
| 4. „JIKAN ist drin — wird das genutzt?" | **beantwortet: ja.** Kette TMDB → Jikan → OMDb, Gate 0,55. Nebenbefund → Punkt 7 |
| 5. „Was wenn der Unterbau nicht Debian ist?" | **offen → Punkt 5** |
| 6. „Mein PC hat ne dicke Grafikkarte (RX 9070 XT), NVIDIA auch möglich" | **Erkennung erledigt** (`fe12401``vce`/`nvenc`/`qsv` getrennt, Hardware-AV1 eigene Kennung). **Benutzen** hängt an **Punkt 1** (Pfad-Mapping) **und Punkt 2** (Preset-Namen) — beides offen |
| 7. „Warum wird der Datei-Browser noch angezeigt" | **offen → Punkt 4** |
| 8. „Server Status muss dringend überarbeitet werden" | **offen → Punkt 3** |
| 9. „ETA im externen Worker und im Dashboard" | **offen → Punkt 3** |
Drei erledigt, eine Frage beantwortet, fünf offen — und die fünf sind unten
priorisiert.
### OFFENE PUNKTE — nach Priorität, mit Kontext
**1. `RIPPY_PATH_MAP` setzen (DER BLOCKER).** Der Installer muss fragen „wie
erreicht dieser PC die Freigabe?" und den Wert in `start-tray.bat` /
`start-worker.bat` schreiben. Format laut `pfad_lokal()`:
`/app/media=\\NAS\rippy;/app/temp=…` (Paare per `;`, Präfix-Ersetzung, wandelt
`/` zu `\`, wenn das Ziel Backslashes enthält). Commander-Vorgabe dazu: *„Wenn
ein externes Ziel für den Speicher eingehängt ist, soll Rippy das ausgewählte als
Arbeitsziel verwenden … das muss auch für den Automatik-Modus so sein."* Punkt 2
oben erfüllt die Einstellungs-Seite davon; es fehlt die Worker-Seite.
Danach: Commander kann seinen 80-GB-Rohschnitt per „Neu komprimieren" auf dem
Ryzen laufen lassen.
**2. Preset-Liste vom Worker holen** — Voraussetzung für „immer das Beste".
Commander: *„Bei den Presets soll IMMER das Beste ausgewählt werden"* und *„wenn
der Worker AV1 oder noch besseres kann, immer diesem empfehlen, die Warnung
verschwindet oder wird von orange zu grün"*. Blocker: Die Namen der
HandBrake-**Hardware**-Presets sind auf der Rippy-VM **nicht ermittelbar** (deren
HandBrake hat keinen Hardware-Encoder), und Erfinden verstößt gegen AGENTS
Regel D — das Projekt hat das zweimal teuer bezahlt. Richtige Lösung: `caps.py`
meldet `HandBrakeCLI --preset-list` des jeweiligen Workers, das UI bietet genau
diese an. Beendet das Raten dauerhaft.
**3. Dashboard + ETA.** Commander: *„Der Server Status muss dringend überarbeitet
werden, das Dashboard soll ja quasi alles auf einen Blick zeigen"* — „Auslastung:
0 % (Aktiv)" ist nichtssagend. Dazu eine **ETA** im Dashboard und beim externen
Worker. Restzeit ist aus dem Fortschrittsverlauf schätzbar; ehrlich bleiben,
solange die Datenlage dünn ist. Verlässlichster Messpunkt (in dieser Sitzung
bewährt): Leseposition im Quellstrom über `/proc/<pid>/fdinfo/`.
**4. Datei-Browser aus dem Rip-Dialog.** Commander: *„Warum wird hier der Datei
Browser noch angezeigt — das ist doch quatsch"*. Prüfen, ob wirklich redundant
(darüber gibt es Schnellwahl + Arbeitsverzeichnis-Auswahl), dann raus oder
einklappen. Datei: `docker/ui/src/components/RipTargetModal.tsx`.
**5. Nicht-Debian-Hosts.** `install.sh` nennt bei fehlendem Compose nur
`sudo apt install docker-compose-plugin`; auf Arch/Fedora/openSUSE falsch.
Paketmanager erkennen. Klarstellen: der Worker-**Container** ist unabhängig vom
Host-System.
**6. Warnung im Rip-Dialog** (nicht nur in den Einstellungen), wenn der gewählte
externe Encoder die Pfade nicht erreicht — vor dem Start, nicht nach einer Stunde.
Bausteine liegen: `caps.py` meldet `extern` und `pfad_map`.
**7. Jikan-Reihenfolge prüfen** (Nebenbefund). Kette ist TMDB → **Jikan** → OMDb
mit Gate `MINDEST_AEHNLICHKEIT = 0.55`. Jikan wird also wirklich benutzt (Antwort
auf die Commander-Frage), läuft aber **vor** OMDb: bei einem Nicht-Anime, den
TMDB verpasst, antwortet zuerst eine Anime-Datenbank. 0,55 ist großzügig.
**8. Aus dem ARM-Vergleich, nicht gebaut:** ISO-Sicherung für Datenträger, die
weder Film noch Audio-CD sind (daran scheitert Rippy heute), und **mehrere
Laufwerke gleichzeitig** — Rippy sieht sie (`/dev/sr[0-9]*`), ob parallel gerippt
wird, ist **unbewiesen**.
**9. Ältere offene Punkte, unverändert gültig:**
- „Job aus der Liste entfernen" sagt nicht, wie viel daneben liegen bleibt — der
Rohschnitt verwaist unsichtbar (v3.14).
- Discord-Webhook und MakeMKV-Key liegen im Klartext in der settings-Tabelle.
- **VM-CPU-Typ steht auf `qemu64`** → `host` würde AVX2 freischalten, 24×
schneller. Anleitung steht in der README („Rippy schneller machen").
- **`install.sh` ist nie als root durchgelaufen** — Verzeichnisse anlegen,
`mount --make-rshared` und die systemd-Unit sind ungetestet.
- Zwei API-Tests laufen nur in der Ampel (`test_api_smoke.py` überspringt sich
unter Windows selbst).
### FALLEN, die diese Sitzung gekostet haben
- **Deutsche Anführungszeichen in DOPPELT gequoteten Python-Strings** beenden den
String vorzeitig. Dreimal hineingetappt. Der Bestand nutzt dafür **einfach**
gequotete f-Strings (`f'…„{x}"…'`). In dreifach gequoteten Docstrings ist es
unproblematisch.
- **In f-Strings steht in `{...}` CODE, kein Text.** Ein Umlaut-Konverter machte
aus `f"{groesse}"` ein `f"{größe}"`, während die Variable `groesse` hieß —
Ruff fand es als F821.
- **`docker exec` ohne `sh -c` expandiert kein `*`** — ein `du -sh /pfad/*`
liefert dann still nichts und sieht wie „leer" aus.
- **Eine `/proc`-Suche nach „HandBrake" trifft die eigene Shell mit**, weil deren
Kommandozeile das Wort enthält. Zwei Fehlalarme.
- **`pydantic-core` springt lokal wiederholt auf 2.47.0** und bricht damit das
Einsammeln ALLER API-Tests (`SystemError`). Fix:
`python -m pip install "pydantic-core==2.46.4"`. Zweimal nötig gewesen.
---
## Vorheriger Stand: v3.15 — Aufräum-Runde: Tempo, tote Routen, 4K-Entscheidung (25.07.2026, 20:00)
> **Deployt und live gegengeprüft.** Auftrag war „bau alles so um, dass es
> sauber ist" mit fünf Vorgaben: saubere Codebase, selbsterklärendes UI,
> externe Worker, kein Laggen, idiotensicher. Was noch offen ist, steht unten.
### Das „Laggen" hatte genau eine Ursache — gemessen und behoben
Über alle 15 Endpunkte gemessen, die das UI beim Laden braucht:
| Endpunkt | vorher | nachher |
|---|---|---|
| `/capabilities` | **1,010 s** | **0,003 s** |
| `/system/updates` | 0,491 s | unverändert (hängt am Knopf) |
| `/metadata/status` | 0,412 s | unverändert (hängt am Knopf) |
| die anderen 12 | < 0,025 s | < 0,010 s |
Nur `/capabilities` schlug beim **Seitenaufbau** zu — und fünf Stellen holen
ihn (Dashboard, Einstellungen, Worker-Tab, Wizard, Rip-Dialog). Jede Seite
zahlte eine Sekunde. Ursache ist kein Fehler, sondern das Wesen des
Celery-Pings: er sammelt Antworten bis zum Timeout und kann nicht früher
aufhören. Den Timeout zu kürzen würde Antworten langsamer Remote-Worker
verschlucken — also genau die Maschinen, um die es beim externen Encoding geht.
Jetzt pingt eine Hintergrund-Schleife im 5-s-Takt, der Endpunkt liest ab. Ist
der Vorrat älter als 30 s, wird einmal synchron gepingt: lieber langsam als
falsch („alles offline", obwohl alles läuft). Kompletter Dashboard-Aufbau:
**1,03 s → 0,028 s.**
### 4K ist entschieden — Kompression je Disc-Typ abwählbar
Die offene Frage aus v3.14 ist gebaut. Bisher gab es nur `transcodeEnabled`:
alles oder nichts. Jetzt kann das Preset eines Disc-Typs auf den Reservewert
`keine` stehen → die verlustfreie Datei bleibt stehen. Damit ist die sinnvolle
Einstellung für diese Maschine erstmals möglich: **4K verlustfrei behalten,
DVD und Blu-ray weiter schrumpfen.**
`komprimieren_fuer()` ist die neue reine Funktion; `preset_fuer()` überspringt
den Reservewert bewusst und gibt ihn NIE als Preset-Namen zurück — sonst bekäme
HandBrake `--preset keine`. Gegengeprüft: keiner der 90 echten Presets aus
`--preset-list` heißt so, und die fünf im UI angebotenen Namen existieren alle.
### Der Wizard läuft nicht mehr in die 4K-Falle
Er hatte die Zahlen längst vorliegen (`/capabilities` meldet `cpu_simd` und
`cpu_kerne`) — benutzt hat er sie nicht und H.265 als Standard vorgeschlagen.
Auf einer CPU ohne AVX2 sind das ein bis zwei Tage pro 4K-Film.
Jetzt entscheidet die **gemessene** Leistung: schwache CPU → 4K nicht
komprimieren, Blu-ray/DVD auf H.264. Stark oder Hardware-Encoder → H.265
durchgehend. Und er schreibt **alle vier** Preset-Felder statt nur des
allgemeinen; vorher fiel 4K auf ein 1080p-Preset zurück.
Dazu gehärtet: Kasten „Was Rippy gerade sieht" (Laufwerk, Worker mit
Kernen/SIMD, freier Platz) — jede Zeile mit **Handlungsanweisung** statt nur
einem Kreuz. Lädt parallel und wiederholt im 5-s-Takt, weil der Worker beim
ersten Start noch hochläuft. API-Keys sind sichtbar statt Punkte (kopierte
Keys, keine Passwörter — Tippfehler sieht man in Punkten nicht) und werden
direkt nach dem Speichern geprüft, mit der Wahl „Key korrigieren" oder
„Trotzdem fertigstellen".
**Gewarnt wird nur, wenn es belegt ist:** `schwacheEncoderCpu()` verlangt
mindestens einen Worker mit BEKANNTER SIMD-Stufe und keinen mit
Hardware-Encoder. Ein Windows-Worker meldet `unbekannt` (kein `/proc/cpuinfo`)
→ dann wird geschwiegen statt falsch gewarnt.
### Vier tote Routen raus
`POST /prescan`, `POST /jellyfin/format` (+ `nfo_generator.py` und
`image_downloader.py`, die sonst nichts nutzte), `GET /stream/jobs`,
`GET /worker-setup/windows-gui`. Jede ein Überrest eines ersetzten Entwurfs,
keine mit Aufrufer. `main.py`: 1726 → 1682 Zeilen, dazu 279 Zeilen in zwei
gelöschten Modulen. Tests halten beide Seiten fest: die vier müssen WEG
bleiben, die drei für die Worker-Installation (`/worker-setup/paket`,
`/windows`, `/windows-exe`) müssen DA sein.
### Externe Worker — vorbereitet, vom Commander zu testen
Auf **Windows gegengeprüft** (dieser PC): `cpu_kerne` 16 und CPU-Modell kommen
korrekt durch, `encoders` ist ohne installiertes HandBrake korrekt **leer**,
`cpu_simd` ehrlich `unbekannt` → keine falsche Warnung. Ein GPU-Worker braucht
ein HandBrake-Build mit `nvenc_*`/`qsv_*`; die neue Anzeige nennt die
ungefilterte HandBrake-Auskunft, damit das nachprüfbar ist.
### ARM-Vergleich (Vorbild-Projekt)
Fast alles, was ARM automatisch macht, macht Rippy schon — und meist
gründlicher: Metadaten aus drei Quellen statt nur OMDb, Episoden-Erkennung per
Laufzeitabgleich, Kompression auf eine eigene Queue routbar. Bezeichnend:
ARMs Auto-Auswurf war bei Rippy nur **behauptet** und ist erst in v3.14 echt
geworden. Zwei Lücken bleiben, beide **nicht gebaut, als Vorschlag**:
- **ISO-Sicherung** für Datenträger, die weder Film noch Audio-CD sind — daran
scheitert Rippy heute.
- **Mehrere Laufwerke gleichzeitig.** Rippy sieht sie (`/dev/sr[0-9]*`), ob
parallel gerippt wird, ist **unbewiesen** — mit einem Laufwerk nicht testbar.
### Installation: ein Befehl statt Checkliste (`install.sh`)
Commander-Rückmeldung: „Das Docker Deployment ist mir zu kompliziert." Zu
Recht — es war eine Sechs-Schritte-Checkliste, von der zwei Punkte Fachwissen
verlangten. Jetzt:
```bash
git clone <repo-url> rippy && cd rippy
sudo ./install.sh
```
Der schlimmste Punkt war das **Laufwerk**: MakeMKV braucht ZWEI Geräteknoten,
und die sg-Nummer ist je Host anders. Das Skript gleicht sie über die
SCSI-Adresse in `/sys` ab statt zu raten — auf der VM gegengeprüft
(`sr0 → 3:0:0:0`, `sg1 → 3:0:0:0` = dasselbe Gerät, korrekt erkannt). Dazu:
Verzeichnisse, Mount-Propagation **inklusive neustart-fester systemd-Unit**
(vorher stand in der README nur „reboot-fest persistieren", ohne zu sagen wie —
nach einem Reboot scheiterte das NAS-Einhängen aus dem UI stillschweigend),
`.env` schreiben ohne Bestehendes zu überschreiben, bauen, starten.
`./install.sh --nur-pruefen` sieht nur nach. Wiederholbar, damit auch der
Update-Weg: `git pull && sudo ./install.sh`.
**Zwei Fallgruben fielen beim Testen auf, beide meine eigenen:**
- `--nur-pruefen` verlangte root und brach ab — ein Prüf-Modus, der nichts
ändert, darf daran nicht scheitern.
- `.env.example` hatte `OPTICAL_SG=/dev/sg1` **unkommentiert** vorbelegt. Der
Installer hätte den erkannten Wert deshalb nicht eingetragen („steht schon
drin") und auf jedem fremden Host still eine kaputte Konfiguration
hinterlassen — genau das, was er verhindern soll. Beide Gerätezeilen sind
jetzt auskommentiert (Compose hat ohnehin Vorgaben), plus eine Gegenprobe:
zeigt ein wirksamer Wert auf ein Gerät, das es hier nicht gibt („`.env` von
einem anderen Rechner"), wird das benannt statt kryptisch von Docker gemeldet.
Die `.env`-Logik ist in drei Fällen auf der VM geprüft: frische Datei bekommt
den erkannten Wert, ein selbst gesetzter Wert bleibt unangetastet, zweimal
ausführen erzeugt genau eine Zeile.
### README neu aufgebaut
Vorher 293 Zeilen, in denen der Schnellstart zwischen `lsscsi`, sg-Knoten,
USB-Passthrough und `mount --make-rshared` begraben war. Jetzt: Installation in
zwei Zeilen oben, dann die Tabelle „Wenn etwas nicht geht" mit den vier Fällen,
die praktisch alles abdecken. Alles Technische darunter in aufklappbaren
Abschnitten, inklusive der Handarbeits-Variante.
**Neu und ausdrücklich gewünscht: „Rippy schneller machen"** — der VM-CPU-Typ.
Warum Virtualisierer eine generische CPU ohne AVX2 geben, was das kostet
(gemessene 2855 h je 4K-Film), die genauen Proxmox-Schritte (herunterfahren →
Hardware → Processors → Type auf `host` → starten, bzw. `qm set <vmid> --cpu
host`), **warum ein Neustart von innen nicht genügt**, und wie man nachprüft:
Rippy zeigt die Vektorbefehle selbst an. Dazu der Nachteil (keine
Live-Migration auf andere CPUs) und die Alternative `x86-64-v3` für Cluster.
### Der MakeMKV-„Fallback" auf der VM war seit Monaten tot
Beim Aufräumen der VM-`.env` aufgefallen und nachgemessen (25.07.2026):
| Quelle | Antwort |
|---|---|
| `https://www.makemkv.com/download` (Repo-Standard) | **HTTP 200** |
| `https://www.makemkv.com/download/old` | HTTP 525 (Cloudflare) |
| web.archive.org-Schnappschuss **aus der VM-`.env`** | **HTTP 404** |
Die VM zeigte also auf eine **kaputte** Adresse. Aufgefallen ist es nie, weil
dort die `vendor/`-Tarballs liegen und der Download-Zweig gar nicht erreicht
wird. Nimm die Tarballs weg, und jeder Bau scheitert mit 404 — während die
Konfiguration gesund aussieht. Genau diese Sorte Fehler ist bei einer Übergabe
teuer.
**Bereinigt** (Sicherung liegt als `.env.sicherung-vor-aufraeumen-20260725`):
`MAKEMKV_URL_BASE` raus → es gilt der Standard, der liefert. `JWT_SECRET_KEY`
raus → Überrest der in v3.4 ausgebauten Anmeldung, wirkungslos.
**Gebaut — Rückfall dreistufig und ehrlich:** `vendor/`-Tarballs →
`MAKEMKV_URL_BASE``MAKEMKV_URL_FALLBACK` (neu, wird automatisch versucht).
Der Fallback ist **absichtlich leer vorbelegt**: Es gibt derzeit keine belegbare
zweite Quelle, und eine einzutragen, die nicht liefert, wäre schlimmer als
keine — siehe oben. Scheitert alles, nennt die Fehlermeldung jetzt die beiden
Wege, die funktionieren (Tarballs nach `vendor/`, oder eigene Quelle als
`MAKEMKV_URL_FALLBACK`), statt nur einen curl-Rückgabewert zu hinterlassen.
### NOCH OFFEN
1. **Externe Worker im Praxistest** (Commander).
2. **ISO-Sicherung und Mehr-Laufwerk-Betrieb** — siehe ARM-Vergleich.
3. **„Job aus der Liste entfernen" sagt nicht, wie viel daneben liegen bleibt**
(v3.14). Der Rohschnitt verwaist dabei unsichtbar.
4. **Discord-Webhook und MakeMKV-Key liegen im Klartext** in der
settings-Tabelle. Für Heimnetz erwartbar, aber der Webhook ist eine echte
Zugangsberechtigung.
5. **Der VM-CPU-Typ steht auf `qemu64`.** `host` würde AVX2 freischalten und
jeden Software-Encode 24× beschleunigen. Ein VM-Neustart, nicht gemacht —
**die Anleitung steht jetzt in der README** („Rippy schneller machen").
6. **`install.sh` ist nie als root durchgelaufen.** Auf dieser VM sind alle
root-Schritte No-Ops (Verzeichnisse da, Propagation schon `shared`), und
`sudo` verlangt hier ein Passwort. Bewiesen sind Laufwerkserkennung,
`.env`-Logik (drei Fälle) und Syntax; **ungetestet bleiben das Anlegen der
Verzeichnisse, `mount --make-rshared` und die systemd-Unit** — die laufen
erst bei einer echten Neuinstallation.
---
## Vorheriger Stand: v3.14 — Durchsicht: vier Placebos und ein unwirksamer Schutz (25.07.2026, 18:40)
> **Stand 19:30: alles committet, Ampel grün (`a1aabd5`), DEPLOYT und live
> gegengeprüft.** Der Akira-Encode wurde um 18:31 abgebrochen (Nachtrag im
> Block darunter), danach der Job aus der Liste gelöscht und die Rohdaten
> entsorgt. Es läuft nichts, die Platte hat **111 GB frei**.
> Was noch zu entscheiden ist, steht unten unter „NOCH OFFEN".
### DER WICHTIGSTE FUND: der Platten-Schutz aus `c065967` greift nicht
`_original_aufheben()` entschied per `os.stat().st_dev`, ob umgehängt oder
kopiert werden muss. Auf der VM gemessen — **beides gleichzeitig wahr**:
```
st_dev /app/temp = 2050
st_dev /app/media = 2050 → identisch
os.rename(...) → EXDEV, "Invalid cross-device link"
```
Der Kernel vergleicht bei `rename()` den **Mount**, nicht das Gerät.
`/app/temp` (Docker-Volume) und `/app/media` (Bind-Mount) sind zwei Mounts
DERSELBEN ext4-Partition. Die Prüfung sah deshalb „gleiches Dateisystem",
**übersprang die Platzprüfung**, und `shutil.move` kopierte doch — 75 GB bei
37 GB frei. Der Schutz hätte genau den Schaden zugelassen, gegen den er
gebaut wurde.
**Behoben:** `os.rename` wird jetzt VERSUCHT statt vorhergesagt. Klappt es,
ist es umgehängt. Kommt EXDEV, steht die Kopie fest — und erst dann wird der
Platz geprüft. Vier Tests dazu (`test_original_aufheben.py`), inklusive des
Falls, der die Platte füllte.
### GEMESSEN, Stand 18:30 (alles per Befehl auf der VM geprüft)
- **Container:** alle 5 `Up`, api/postgres/redis `healthy`. api/ui/worker seit
18:00:39 (Deploy von `8bb075c`), postgres/redis älter.
- **Platte:** 148 G, 105 G belegt, 37 G frei = 75 G Rohschnitt + 4,9 G Medien
(Evangelion) + ~25 G System. **Keine Kopier-Reste**, kein `original`-Ordner.
- **Akira-Job:** `failed`, „Abgebrochen durch Nutzer" (Abbruch 18:30:17
angefordert, Worker bestätigt 18:33:38 → **3,4 Minuten Verzug**, siehe unten).
Es läuft **kein** HandBrake mehr (per `/proc` geprüft, Stand 19:06).
- **Der Encode war bei 1,44 %**, gemessen an der Leseposition im Quellstrom
(`/proc/<pid>/fdinfo/3`: 1.145.940.149 von 79.604.951.639 Bytes) — exakter
als jede Fortschrittsanzeige. Zwei Tempo-Fenster ergaben 395 und 784 KB/s
**2855 h** für den Film, bei 3,84 von 4 gesättigten Kernen.
- **Achtung bei Prozess-Suchen per `/proc`:** Ein `case "$c" in *HandBrake*)`
trifft die eigene Shell mit, weil deren Kommandozeile das Wort enthält. Zwei
Fehlalarme in dieser Sitzung. Immer die PID gegenprüfen.
- **Die Ursache dafür ist neu und behebbar:** Die VM läuft auf dem generischen
QEMU-CPU-Modell (`QEMU Virtual CPU version 2.5+`), `grep -c avx2
/proc/cpuinfo` = **0**, nur bis `sse4_2`. x265 lebt von AVX2. Abhilfe:
CPU-Typ von VM 106 in Proxmox auf `host` stellen (braucht VM-Neustart).
- **HandBrake im Worker-Image kann:** `svt_av1`, `x264`, `x265` (je 10/12-bit),
`mpeg4/2`, `VP8/9`, `theora` — und **keinen einzigen Hardware-Encoder**.
- **Ampel grün** für `b526a0a` und `8bb075c` (Gitea-API abgefragt).
### GEBAUT — vier Placebos entfernt bzw. echt gemacht
1. **Fortschritt log statt Wahrheit.** `get_progress_from_line` matchte jede
Zahl vor einem `%` — also auch HandBrakes **Scan-Durchlauf**, der VOR dem
Encodieren bis 100 % hochläuft. Dazu warf `if progress > 0` alle echten
Werte unter 1,00 % weg. Ergebnis: Anzeige klebte stundenlang auf 99 %.
Jetzt wird nur die `Encoding:`-Zeile gelesen, `task N of M` mitgerechnet,
und **-1 heißt „keine Angabe"** (Muster von `get_progress_from_prgv`).
Formatstrings aus dem Binary gelesen, nicht geraten.
**⚠️ Richtigstellung zu v3.13:** Dort steht, `progress=99` sei ein „Altwert
aus dem Absturz". Falsch — beide Startpfade setzen auf 0, der Wert war
frisch vom Scan-Durchlauf geschrieben. Es war ein Bug, kein Überrest.
2. **„Automatischer Auswurf" tat nichts.** Die Einstellung (Standard: ein)
wurde von niemandem gelesen: DVD/Blu-ray warfen **nie** aus, Audio-CDs
**immer**, weil abcde `-x` fest verdrahtet bekam. Jetzt entscheidet die
Einstellung beides (`wirf_disc_aus()` per CDROMEJECT-ioctl, Linux-guarded).
3. **„Alle Tracks rippen" konnte nichts bewirken** — abcde bekommt keine
Track-Auswahl und es gibt keine Oberfläche dafür. Schalter entfernt, an
seiner Stelle steht jetzt die Wahrheit („wird immer vollständig gerippt").
4. **Encoder-Auslese behauptete statt zu messen.** `cpu-x264`/`cpu-x265`
standen fest verdrahtet drin („immer dabei") — ein Rip-Worker **ohne**
HandBrake behauptete damit, komprimieren zu können. Und `vaapi` wurde
allein wegen `/dev/dri` gemeldet, ohne zu prüfen, ob HandBrake das kann
(dieses Image kann es nicht). Jetzt aus `HandBrakeCLI --help` geparst, plus
**CPU-Modell, Kernzahl und Vektorbefehlsstufe** je Worker — mit sichtbarer
Warnung im UI, wenn AVX2 fehlt. Genau die Angabe, deren Fehlen den
55-Stunden-Encode unsichtbar machte.
### GEBAUT — Zombie-Erkennung (v3.13 Punkt 2 abgearbeitet)
`zombies.py`: Beim Worker-Start werden Jobs, die auf `ripping`/`transcoding`/
`canceling` stehen, gegen Celerys `active`/`reserved`/`scheduled` gehalten und
ehrlich auf `failed` gesetzt, wenn niemand daran arbeitet. Drei Sicherungen,
weil ein falsch getöteter Job teurer ist als eine stehende Leiche:
- **nur beim Start** (da ist „es lief nichts" eindeutig),
- **120 s Gnadenfrist** (Celery stellt unbestätigte Aufgaben erneut zu),
- **Vollzähligkeit**: antworten weniger Knoten als laut Herzschlag online
sind, wird NICHTS gewertet — sonst wäre der laufende Job eines
beschäftigten Remote-Workers eine falsche Leiche.
13 Tests, unter anderem: „laufender Job wird nicht angetastet" und
„schweigender Worker verhindert jedes Urteil".
### GEBAUT — Pfad-Prüfung gehärtet
Elf Stellen prüften mit nacktem `startswith(MEDIA_ROOT)`. `/app/media-boese/x`
beginnt mit `/app/media`, liegt aber außerhalb — betroffen waren auch `/browse`
und `/browse/mkdir`, wo der Pfad vom Nutzer kommt. Neuer Zwillings-Helfer
`unter_wurzel()` in `api/main.py` und `worker/tasks.py`, alle elf Stellen
umgestellt, Tests in beiden.
Nebenbefund dabei: `_zielbasis()` benutzte `os.path.normpath` — unter Windows
werden daraus Backslashes und die Prüfung greift nicht mehr; das gewählte Ziel
wäre still auf den Standard zurückgefallen. Genau die Falle, die
`_arbeitsverzeichnis()` drei Zeilen weiter dokumentiert und mit `posixpath`
vermeidet. Live war es nie (nur aus `rip_disc`, das auf Windows verriegelt
ist), jetzt konsistent.
### GEFUNDEN, NICHT ANGETASTET — vier tote Endpunkte
Nirgends im UI referenziert (mechanisch gegengeprüft: alle `api.*`-Aufrufe
gegen alle Routen):
| Endpunkt | Lage |
|---|---|
| `POST /jellyfin/format` | ersetzt durch `medien.py` im Worker; zieht `nfo_generator.py` + `image_downloader.py` in der API mit, die sonst niemand nutzt |
| `POST /prescan` | ohne Aufrufer (die `PreScan`-Klasse selbst wird woanders sehr wohl gebraucht) |
| `GET /stream/jobs` | niemand konsumiert ihn; der Kommentar behauptet einen „Fix 23.07.", der ihn funktionsfähig machte — gebraucht wird er trotzdem nicht |
| `GET /worker-setup/windows-gui` | seit der `.exe` (v3.9) unreferenziert |
Bewusst nicht entfernt: `test_api_smoke.py` prüft `/prescan` als verdrahtete
Route, und Entfernen ist eine Entscheidung, keine Reparatur. **Der Commander
entscheidet.**
### NOCH OFFEN / EHRLICH UNGEKLÄRT
- **Was die Platte am 25.07. mittags füllte, ist nicht belegt.** Es gibt keine
Kopier-Reste, kein `original`-Verzeichnis und **keinen einzigen Log-Eintrag
von `_original_aufheben`** — das loggt in beiden Zweigen. Zwischen 10:21:50
und 17:49 steht überhaupt nichts im Log. Der EXDEV-Mechanismus ist jetzt
belegt (siehe oben), dass er DAMALS zuschlug, ist es nicht.
- **`tracks:/dev/sr0` meldet `{"status":"done","tracks":[]}`** — null Titel für
eine Disc, die MakeMKV mit `TCOUNT:5` öffnet. Nicht weiter verfolgt.
- **Der Discord-Webhook und der MakeMKV-Key liegen im Klartext** in der
settings-Tabelle. Für Heimnetz erwartbar, aber der Webhook ist eine echte
Zugangsberechtigung.
- **Zwei neue API-Tests laufen nur in der Ampel** — `test_api_smoke.py`
überspringt sich unter Windows selbst (`main.py` braucht `fcntl`).
### GEBAUT — „Abbrechen" wirkt jetzt sofort
Der Fund aus dem Nachtrag (Commander, am laufenden Job beobachtet) ist behoben.
Ursache war genau wie dort beschrieben: Der Abbruch wurde nur in
`datei_fortschritt` geprüft, und diese Closure stieg oben sofort wieder aus,
wenn sich die Prozentzahl nicht geändert hatte. Bei einem Prozent je halber
Stunde sah „Abbrechen" entsprechend lange wirkungslos aus.
Jetzt gibt es in `run_handbrake` einen **eigenen Abbruch-Kanal** neben dem
Fortschritts-Callback — dasselbe Muster, das `run_makemkv` schon für `log_cb`
benutzt, und aus demselben Grund. Er wird bei JEDER Ausgabezeile aufgerufen
und ist im Worker auf 5 Sekunden gedrosselt (`ABBRUCH_INTERVALL_SEKUNDEN`).
Nebeneffekt: Er greift auch während des Scan-Durchlaufs, der überhaupt keine
Encode-Prozente liefert — dort war ein Abbruch vorher grundsätzlich unmöglich.
Die Leseschleife ist als `_handbrake_schleife()` herausgezogen, damit die
Reihenfolge (Abbruch VOR Fortschritt) ohne echtes HandBrake testbar ist.
### ERLEDIGT UM 19:30 — deployt und live gegengeprüft
`a1aabd5` läuft auf der VM. Belege, nicht Behauptungen:
- **Container:** alle 5 up, api/postgres/redis `healthy`.
- **Die neue Auslese antwortet ehrlich** (`GET /capabilities`):
`cpu-x264, cpu-x265, cpu-av1`**kein Phantom-`vaapi`** mehr, obwohl die
alte Fassung es bei vorhandenem `/dev/dri` gemeldet hätte. Dazu
`cpu_modell: QEMU Virtual CPU version 2.5+`, `cpu_kerne: 4`,
`cpu_simd: sse4_2` → die AVX2-Warnung im UI greift.
- **Zombie-Erkennung:** 19:26:09, exakt 120 s nach Worker-Start,
`{'geprueft': 0, 'aufgeraeumt': []}` — nachgesehen, nichts in Arbeit
gefunden, korrekt nichts angetastet.
- **604 Disc-Schlüssel** haben den Rebuild überlebt.
- **Aufgeräumt:** das unbrauchbare 4K-Fragment (67.633.152 B) gelöscht; der
Akira-Job wurde um 19:07 aus der Liste entfernt, wodurch der 75-GB-Rohschnitt
verwaiste (kein Job-Datensatz zeigte mehr darauf, im UI unsichtbar,
„Neu komprimieren" unmöglich) — auf Entscheid des Commanders gelöscht, samt
vier leerer Alt-Ordner. **Platte: 37 GB → 111 GB frei.**
⚠️ **Merken für die Job-Verwaltung:** „Job aus der Liste entfernen" löscht
bewusst keine Dateien — das ist richtig, macht die Rohdaten aber unerreichbar,
weil beides nur über die Job-ID verbunden ist. Ein Rohschnitt ohne Job-Zeile
taucht nirgends mehr auf. Das UI sollte beim Löschen sagen, wie viel daneben
liegen bleibt. Nicht gebaut.
### NOCH OFFEN
**1. Die UHD-Strategie** (aus dem Nachtrag, unverändert gültig).
Preset-je-Disc-Typ war richtig, reicht aber nicht: Software-HEVC in 4K ist auf
dieser CPU keine Option. Drei Wege, keiner davon gebaut:
- **UHD gar nicht komprimieren** — Roh-MKV behalten. Ehrlichste Variante,
kostet Platz (75100 GB je Film, gehört dann auf die NAS).
- **Hardware-Encoder** — Remote-Worker mit GPU (`nvenc`/`vaapi`). Rippy kann
das schon routen (bewiesen v3.7); es fehlt die Maschine. Achtung: Das
Worker-Image kann selbst keinen Hardware-Encoder, ein GPU-Worker braucht ein
HandBrake-Build mit `nvenc_*`/`qsv_*` — die neue Anzeige sagt das jetzt.
- **CPU-Typ der VM auf `host`** — schaltet AVX2 frei, bringt bei x265 typisch
Faktor 24. Aus 2855 h werden damit aber immer noch Stunden bis Tage; das
allein löst 4K nicht, hilft aber jedem 1080p-Encode.
**2. Vier tote Endpunkte** — entfernen oder behalten (Tabelle oben). Alle vier
sind Überreste eines ersetzten Entwurfs: `/jellyfin/format` (die Arbeit macht
seit v3.2 `medien.py` im Worker), `/prescan` (Metadaten-Seite ist seit v3.4
weg; die `PreScan`-Klasse selbst wird sehr wohl gebraucht), `/stream/jobs`
(kein `EventSource` im UI — das Dashboard pollt `setInterval(…, 4000)`) und
`/worker-setup/windows-gui` (seit der `.exe` in v3.9 ohne Aufrufer; die Datei
steckt weiter in der `.exe`). **`/stream/jobs` ist der unangenehmste:** keine
harmlose Leiche, sondern eine Endlosschleife je Verbindung, die jeder erreichen
kann. Empfehlung: alle vier raus, plus `nfo_generator.py` und
`image_downloader.py` in der API (nutzt sonst nichts) — und `test_api_smoke.py`
prüft `/prescan` als verdrahtete Route, der Test muss also mit.
**3. Akira liegt jetzt gar nicht mehr vor.** Fragment und Rohschnitt sind
gelöscht (siehe oben). Wer den Film will, legt die Disc neu ein — der
Schlüsselspeicher steht (604 Schlüssel), der Rip dauert rund eine Stunde. Vor
dem nächsten UHD-Versuch aber Punkt 1 entscheiden, sonst läuft dasselbe
50-Stunden-Rennen wieder an.
---
## Vorheriger Stand: v3.13 — ÜBERGABE (25.07.2026, 18:05)
> Dieser Block ist eine Übergabe an die nächste Sitzung. Er trennt bewusst
> **gemessen** von **vermutet** — in dieser Sitzung wurden zwei Behauptungen
> aufgestellt, die sich als falsch erwiesen (siehe „Vertrauensnotiz" unten).
> Im Zweifel: selbst nachmessen, nicht diesem Dokument glauben.
### GEMESSEN, Stand 18:05 (alles per Befehl auf der VM geprüft)
- **Container:** api/postgres/redis/ui/worker alle `Up`, api + postgres +
redis `healthy`.
- **Platte:** `/dev/sda2` 148 G, 105 G belegt, **37 G frei** (75 %).
- **Läuft gerade:** `HandBrakeCLI --input
/app/temp/raw/73b89777-…/title_t00.mkv --output
"/app/media/movies/Akira (1988)/title_t00.mkv" --preset
"H.265 MKV 2160p60 4K" --all-audio --all-subtitles`
- **Job `73b89777-a808-426f-8159-d406d27d0ec8`:** `status='transcoding'`,
`progress=99` (⚠️ **Altwert aus dem Absturz, NICHT der echte Fortschritt** —
der Lauf hat um 18:02 begonnen), `disc_type='uhd'`,
`output_path='/app/media/movies/Akira (1988)'`.
- **Dateien:** `…/Akira (1988)/title_t00.mkv` ist **0 Bytes** (HandBrake hat
die vorherige 1080p-Fassung beim Start gekürzt und schreibt gerade neu).
Roh-Rip `/app/temp/raw/73b89777-…/` = **75 GB, unversehrt.**
- **Git:** `8bb075c` ist HEAD und deployt. Davor `c065967`, `e84afc1`,
`f449c4e`, `0935766`. Alle Ampeln waren grün.
- **Schlüsselspeicher:** 604 Disc-Schlüssel, `/system/keystore` antwortet.
- **NAS:** `//192.168.178.62/rippy` auf `/srv/rippy/media/rippy` gemountet,
erreichbar, **2311 GB frei**. Wird als Ablageziel gelistet.
### NACHTRAG 18:35 — der 4K-Lauf wurde abgebrochen, zwei neue Befunde
- **4K-HEVC ist auf dieser CPU nicht machbar.** Gemessen: von 18:02 bis 18:31
kam der Lauf von 0 auf **1 %** → hochgerechnet **~50 Stunden** für den Film.
Der Commander hat um 18:31 abgebrochen. **Konsequenz, die noch zu
entscheiden ist:** entweder UHD gar nicht komprimieren (Roh-MKV behalten,
`transcodeEnabled` für UHD aus), oder ein Hardware-Encoder (Remote-Worker
mit GPU, `nvenc`/`vaapi`), oder bewusst bei 1080p bleiben. Das eben
gebaute Preset-je-Disc-Typ ist damit richtig, aber allein nicht genug.
- **Abbruch wirkt verzögert (FEHLER, nicht gebaut).** Job steht seit 18:31 auf
`canceling`, **HandBrake lief um 18:35 immer noch**. Ursache: Der Abbruch
wird nur in `datei_fortschritt` geprüft, und diese Closure läuft nur, wenn
sich die PROZENTZAHL ändert (`tasks.py`, `if gesamt == letzter[0]: return`).
Bei 1 % alle 30 Minuten heißt das: bis zu eine halbe Stunde, in der
„Abbrechen" für den Nutzer wirkungslos aussieht. Der Abbruch muss unabhängig
vom Fortschritt geprüft werden (z. B. zeitgesteuert beim Lesen der
HandBrake-Ausgabe).
- **Die 1080p-Fassung ist weg.** HandBrake hat sie beim Start auf 0 Bytes
gekürzt; aktuell liegen dort ~64 MB 4K-Fragment. Der **75-GB-Rohschnitt ist
unversehrt** — es ist also nichts unwiederbringlich verloren, aber im
Akira-Ordner liegt gerade eine unbrauchbare Datei.
### WAS ALS NÄCHSTES ANSTEHT
1. **Ausgang des 4K-Laufs prüfen.** Erwartung, die noch NICHT bewiesen ist:
Der Job endet **erfolgreich**, und „Original behalten" (Setting steht auf
`true`) meldet nur eine **Warnung**, weil 75 GB nicht neben 37 GB freien
Platz passen. Genau dieser Pfad ist neu (`_original_aufheben` in
`tasks.py`) und in der Praxis noch nie gelaufen. Wenn er hält, ist der
Ausfall von heute Mittag strukturell behoben.
2. **Zombie-Erkennung fehlt (gefunden, NICHT gebaut).** Nach dem Absturz stand
der Job auf `transcoding 96 %`, obwohl weder ein Prozess lief noch etwas in
den Celery-Queues stand (beide `llen` = 0). Folge: keine Anzeige, kein
Download, und der „Neu komprimieren"-Knopf fehlt, weil
`_kann_neu_komprimieren` (main.py) `status == "failed"` verlangt. Der
Worker sollte beim Start solche Leichen erkennen und ehrlich auf `failed`
setzen.
3. **`keepOriginal` steht auf `true`** — von dieser Sitzung gesetzt, um den
4K-Rohschnitt zu retten. Bewusst entscheiden, ob das so bleibt: mit
Arbeitsverzeichnis auf der NAS-Freigabe wäre es ein reines Umhängen statt
einer Vollkopie.
4. **75 GB Rohschnitt** liegen weiter in `/app/temp/raw/73b89777-…`. Nach
erfolgreichem 4K-Lauf entscheiden, ob er weg kann.
### VERTRAUENSNOTIZ — zwei Fehlschlüsse dieser Sitzung
- **„MakeMKVs Schlüssel-Kanal ist abgeschaltet"** — falsch. Stützte sich auf
zwei tote Hostnamen aus alten Forumsbeiträgen, die MakeMKV längst nicht mehr
benutzt. Richtig: `makemkvcon` unter **Linux** fragt nie, die
**Windows**-Version schon. Korrigiert in v3.11.
- **„Celery hat die Aufgabe nach dem Deploy erneut zugestellt"** — falsch.
Aus dem Status `transcoding` geraten, ohne Prozesse oder Queues zu prüfen.
Es war ein Zombie-Eintrag (Punkt 2 oben).
Beide Male war das Muster dasselbe: **aus einem Zustandswert auf einen
Mechanismus geschlossen, statt den Mechanismus zu messen.**
---
## Vorheriger Stand: v3.12 — Preset je Disc-Typ (25.07.2026)
- **Der Fund:** Beim ersten echten UHD-Rip aufgefallen — die Kompression
fragte den Disc-Typ **gar nicht**: `preset = einstellungen.get(
"transcodePreset")`, ein globales Preset für alles. Live eingestellt war
`HQ 1080p30 Surround`. Der laufende Akira-Rip wäre also verlustfrei in 4K
gerippt und danach **auf 1080p heruntergerechnet** worden — und mit
`keepOriginal: False` wäre der 4K-Rohschnitt anschließend gelöscht worden.
Aufgefallen ist es nur, weil der Commander gefragt hat, ob Rippy das
UHD-Preset automatisch nimmt.
- **Sofortmaßnahme am laufenden Job:** `keepOriginal` auf `True` gesetzt
(nur dieses eine Feld, gegengeprüft: kein anderer Schlüssel verändert).
Damit überlebt der 4K-Rohschnitt die Kompression auf jeden Fall.
- **Gebaut:** `preset_fuer(disc_type, einstellungen)` in `ripping.py` (pure,
getestet) plus drei Einstellungen `transcodePresetDvd` / `…Bluray` /
`…Uhd`. Reihenfolge: Preset des Disc-Typs → allgemeines
`transcodePreset` → `DEFAULT_HB_PRESET`. **Bestandsinstallationen ändern
ihr Verhalten nicht**, solange die neuen Felder nicht gespeichert sind.
`transcode_files` holt den Disc-Typ aus dem Job-Datensatz und schreibt ihn
mit ins Log.
- **UI (Einstellungen → Verarbeitung):** drei Auswahlfelder statt einem, mit
Klartext dazu, warum 4K auf ein 2160p-Preset gehört. Alle Preset-Namen
stammen aus `HandBrakeCLI --preset-list` im Worker-Image (1.6.1) — nicht
geraten (AGENTS Regel D).
- **⚠️ Deploy bewusst zurückgehalten:** `docker compose up -d --build`
würde den Worker-Container neu erstellen und den **laufenden Akira-Rip
abbrechen**. Erst deployen, wenn der Job durch ist. Der 4K-Rohschnitt ist
durch `keepOriginal` geschützt; danach reicht „Neu komprimieren" im UI,
um mit dem richtigen Preset in 4K zu komprimieren.
- **Nebenbefund:** `ps` gibt es im Worker-Image nicht (python-slim). Frühere
Prüfungen auf laufende Rips per `ps | grep` lieferten deshalb still
„nichts aktiv" — richtig geht es über `/proc`.
---
## Vorheriger Stand: v3.11 — 4K-UHD GELÖST: Akira geht auf (25.07.2026)
- **🎉 Der Durchbruch:** Nach Übernahme des Schlüsselspeichers öffnet
`makemkvcon` auf der VM die Akira-UHD: „Operation successfully
completed", **`TCOUNT:5`**, fünf Titel — identisch zum Windows-Ergebnis.
**Erster belegter UHD-Disc-Zugriff auf der Rippy-Maschine.**
- **Die echte Ursache — und sie ist eine andere als in v3.10:**
`makemkvcon` unter **Linux** ruft Disc-Schlüssel **nie** ab. Die
**Windows**-Version tut es. Gegenprobe mit demselben Laufwerk und
derselben Disc:
| | Linux (Worker) | Windows |
|---|---|---|
| Verbindungen beim Disc-Öffnen | **keine einzige** | 185.84.108.20:443 |
| Meldung 3338 „Downloading latest HK" | nie | ja |
| `_private_data.tar` | 2048 B, **0** Schlüssel | 6,4 MB, **604** Schlüssel |
| Disc | „volume key is unknown" | **geht auf** |
Gegengeprüft mit leerem UND gefülltem Speicher, mit und ohne `--noscan`,
mit `dev:/dev/sr0` und `disc:0`, mit gelöschter `update.conf`. Immer:
keine Verbindung. Die Meldungsvorlage „Downloading latest %1 to %2 ..."
steckt sehr wohl im Linux-Binary — sie löst nur nie aus. Gleiches
Symptom im MakeMKV-Forum, seit Jahren offen (t=25782, t=34022).
- **⚠️ Richtigstellung zu v3.10 (direkt darunter):** Dort steht, MakeMKVs
Schlüssel-Kanal sei abgeschaltet. **Das war falsch.** Die Herleitung
stützte sich auf zwei Hostnamen aus alten Forumsbeiträgen
(`hkdata.fairuse.org`, `hkdata.crabdance.com`), die tatsächlich nicht
mehr auflösen — MakeMKV benutzt sie aber längst nicht mehr. **Der Dienst
lebt, der Worker erreicht ihn sogar** (Verbindungstest auf
185.84.108.20:443 erfolgreich); er wird unter Linux nur nie gefragt.
Aufgedeckt hat das der Commander mit dem Einwand, unter Windows ginge es
sofort. Alles in v3.10 Gebaute bleibt richtig und nötig — nur die
`KEYDB.cfg` ist nicht der Haupt-, sondern der Ersatzweg.
- **Gebaut — Schlüsselspeicher übernehmbar:** `GET`/`POST
/system/keystore` plus die Helfer in `makemkv_daten.py` (beide
Zwillinge). Der Rohkörper der Anfrage IST die Datei — binär, deshalb
kein JSON und kein Base64. Die Prüfung lehnt einen Speicher **ohne**
`hkd_*.bin` ab, sonst lädt jemand den leeren Vorrat einer frischen
Installation hoch und wundert sich, dass nichts passiert.
- **Gebaut — UI:** neuer Block „Disc-Schlüssel für 4K-UHD" **über** dem
KEYDB-Block, mit Schlüssel-Anzahl, Upload und der Schritt-für-Schritt-
Anleitung für den Windows-Umweg. KEYDB.cfg ist jetzt als **Notnagel**
beschriftet. Die Worker-Plakette zeigt die Anzahl bekannter Schlüssel;
`0` heißt sichtbar „4K-UHD scheitert".
- **Gebaut — ehrliche Texte:** Der UHD-Fehlertext nennt jetzt den
Windows-Weg und die Anzahl der Schlüssel dieses Workers. Alle
Falschaussagen korrigiert: UI (3 Stellen), Anleitung (2), README (3),
KONZEPT §8 + §10, Modulkopf von `makemkv_daten.py`, Worker-Dockerfile
und `makemkv_key.py` (dort stand: „Den AACS-Schlüssel zieht MakeMKV via
LibreDrive ohnehin selbst aus dem Laufwerk" — gilt für Blu-ray, NICHT
für UHD).
- **So hältst du den Vorrat aktuell:** Laufwerk an den Windows-PC, Disc in
MakeMKV öffnen, dann `_private_data.tar` aus dem MakeMKV-Datenverzeichnis
(*Preferences → General*) unter Einstellungen → System hochladen. Der
Speicher der Windows-Installation liegt bereits auf der VM unter
`/srv/rippy/makemkv/`.
- **Offen:** Der volle UHD-Rip inklusive Transcode-E2E ist noch nicht
durch — belegt ist der Disc-Zugriff, nicht die komplette Kette bis zur
fertigen Datei. Und ob sich der Abruf unter Linux doch anstoßen lässt,
ist ungeklärt; der Code dafür ist im Binary vorhanden.
---
## Vorheriger Stand: v3.10 — 4K-UHD: KEYDB.cfg statt Warten auf MakeMKV (25.07.2026)
- **Der Befund (am 25.07. live auf der VM im Worker-Container
nachgemessen — kein Verdacht, alles belegt):** Eine 4K-UHD (Akira,
MKB v76, Pressung Dezember 2020) scheitert mit „The volume key is
unknown for this disc". **Laufwerk und MakeMKV sind dabei in Ordnung:**
makemkvcon meldet „Using LibreDrive mode (v06.3)" und „Using direct
disc access mode", liest die Disc und legt den AACS-Dump ab
(Meldung 3332). Der Fehler liegt also NICHT an der Hardware.
- **Die echte Ursache — MakeMKV fragt gar nicht erst:** Das Debug-Log
geht ohne einen einzigen Netz-Versuch von „Loaded content hash table"
direkt auf „The volume key is unknown". **Beweise:**
`/root/.MakeMKV/_private_data.tar` enthielt nur die Index-Datei und
KEINE einzige `hkd_*.bin` — es wurde also nie ein Schlüssel geladen.
Auch mit erzwungener frischer Prüfung (`update.conf` gelöscht,
Meldung 5074 belegt den Web-Kontakt) und mit `app_UpdateEnable = "1"`
kam keiner. Und die im MakeMKV-Forum dokumentierten Schlüssel-Server
`hkdata.fairuse.org` und `hkdata.crabdance.com` lösen weltweit nicht
mehr auf (NXDOMAIN gegen Fritz!Box, 8.8.8.8 und 1.1.1.1).
- **Richtigstellung zu v3.3 (weiter unten korrigiert):** Dort stand, die
Ursache sei „Disc neuer als MakeMKVs Schlüssel-DB" und ein
MakeMKV-Update werde das lösen. Das ist widerlegt — es gibt keine
nachladbare Schlüssel-Datenbank mehr. **Warten auf ein MakeMKV-Update
hilft bei diesem Fehler nicht.**
- **Der einzige Weg, der heute funktioniert: `KEYDB.cfg`** — GROSS
geschrieben (unter Linux case-sensitiv) im MakeMKV-Datenverzeichnis.
Rippy macht diesen Weg jetzt begehbar, statt auf MakeMKV zu warten.
- **Gebaut — persistentes Datenverzeichnis:** `docker-compose.yml` mountet
`${MAKEMKV_DATA_HOST:-/srv/rippy/makemkv}` vom Host — im Worker auf
`/root/.MakeMKV`, in der API auf `/app/makemkv-data`; beide Container
bekommen `MAKEMKV_DATA_DIR`. Damit überleben `KEYDB.cfg` und die
AACS-Dumps jeden Rebuild. `entrypoint.sh` schreibt `settings.conf`
jetzt ergänzend statt zerstörend (sonst hätte der Beta-Key den Rest
überbügelt) und setzt `app_UpdateEnable = "1"`.
- **Gebaut — eine einzige Wahrheit über das Verzeichnis:** neues
Zwillings-Modul `makemkv_daten.py` (identisch in `docker/api/` und
`docker/worker/`) mit reinen Helfern — Status lesen, Inhalt prüfen,
atomar schreiben, löschen, AACS-Dumps auflisten. Nur reine Funktionen,
damit die Ampel sie ohne Postgres/Redis testen kann.
- **Gebaut — Bedienung im UI:** Einstellungen → System zeigt den
`KEYDB.cfg`-Status (Pfad, Größe, Anzahl Disc-Einträge, Datum), nimmt die
Datei über einen ganz normalen Datei-Dialog entgegen (der Browser liest
sie und schickt den Text als JSON — serverseitig bewusst KEIN
Multipart-Upload, es gibt kein `python-multipart`, das würde die API
beim Import töten), lehnt
unplausiblen Inhalt mit deutschem Klartext ab, kann die Datei wieder
entfernen und die AACS-Dumps zum Download anbieten.
- **Gebaut — man sieht endlich, was MakeMKV sagt:** `parse_msg()` in
`ripping.py` plus Log-Callback in `tasks.py` schreiben MakeMKV-Meldungen
ins Rippy-Log (gedrosselt: Code 1003 raus, keine Wiederholungen, max. 40
je Rip). Der UHD-Fehlertext ist ehrlich neu geschrieben. `caps.py`
meldet zusätzlich `keydb: ja | nein | unbekannt` je Worker.
- **Abgrenzung, die überall durchscheint:** **Rippy liefert KEINE
Schlüssel mit, lädt keine herunter und verteilt keine.** Rippy stellt
nur den Platz für eine Datei bereit, die der Nutzer selbst mitbringt,
und zeigt ehrlich an, was dort liegt. Genau die Grenze zieht schon
`docker/api/makemkv_key.py` (Zeile 14) für den Beta-Key: das ist die
Software-LIZENZ, nicht das Entschlüsseln oder Verteilen von
Disc-Schlüsseln.
- **⚠️ NOCH NICHT end-to-end bewiesen — ehrlich gesagt:** Zum Zeitpunkt
dieser Änderung lag KEINE `KEYDB.cfg` vor, die den Akira-Schlüssel
enthält. Belegt sind der Befund oben und die neue Mechanik (Mount,
Modul, Endpunkte, UI) — **nicht** ein erfolgreicher UHD-Rip. Der
Nachweis steht aus und braucht eine echte Schlüssel-Datei.
---
## Vorheriger Stand: v3.9 — Windows-Installer als echte .exe (Icon, kein Konsolenfenster) (24.07.2026)
- **RippyWorkerSetup.exe** ersetzt den .vbs/.bat-Weg (Commander-Einwand:
.vbs ist abgekündigt + wird als gefährlich geflaggt). install-gui.ps1 via
ps2exe kompiliert: eingebettetes Rippy-Disc-Icon, KEIN Konsolenfenster
(-noConsole), WinForms (-STA). Vorgebaut auf Windows
(deploy/worker-windows/build-exe.ps1) + committet — Windows-.exe geht
nicht von Linux (Rippy-Host). API: GET /worker-setup/windows-exe.
UI (Worker → Windows): „Installer herunterladen (.exe)" als Haupt-Weg.
**E2E bewiesen**: von der VM geladen → Fenster öffnet, conhost-Zähler
unverändert (kein Konsolenfenster), Icon eingebettet.
- **Wichtig — friert nichts ein**: Die .exe holt HandBrake zur Laufzeit
(GitHub latest) und den Worker-Code live von Rippy. Nur bei Änderung der
GUI (install-gui.ps1) neu bauen (build-exe.ps1), NICHT bei Tool-Updates.
- **Update-Story dokumentiert** (Commander-Frage): MakeMKV → .env-Bump +
Rebuild (Update-Check zeigt es); HandBrake Docker = Debian-stabil,
native Worker = latest beim Installieren.
---
## Vorheriger Stand: v3.8 — Grafischer Windows-Installer + Versionslage klar (24.07.2026)
- **Grafischer Windows-Installer** (statt CLI): install-gui.ps1 (WinForms,
ASCII-only) — Fenster mit Feldern (Rippy-IP, Worker-Name, Autostart) +
Install-Knopf mit Live-Log + „Worker starten". Gleiche Schritte wie der
CLI-Installer (Worker-Code, HandBrake latest, venv, Tray/Start/Uninstall).
UI (Worker → Windows): Knopf „Grafischen Installer herunterladen (.bat)" —
die .bat wird CLIENT-seitig mit der eingetragenen LAN-IP erzeugt,
Doppelklick lädt+startet die GUI von Rippy (GET /worker-setup/windows-gui).
CLI-Befehl bleibt als Profi-Alternative. Bewiesen: GUI-Fenster öffnet
sauber auf dem Commander-PC (Titel „Rippy Encoding-Worker - Installation").
- **Werkzeug-Versionslage exakt dargestellt** (Commander-Wunsch): System-Tab
erklärt jetzt klar — MakeMKV aktualisierbar (wichtig wegen Schlüssel-DB),
HandBrake im Docker-Worker bewusst Debian-stabil (1.6.1, KEIN Alarm mehr),
native Worker holen die neueste. Update-Check zeigt MakeMKV mit „✓ aktuell"
bzw. Update-Befehl, HandBrake neutral als Info.
---
## Vorheriger Stand: v3.7 — Drei Praxis-Bugs (Mounts, HandBrake, Encoder-Wahl) (24.07.2026)
Alle drei live auf der VM verifiziert:
- **Speicher-Mounts robust**: Ein toter CIFS-Mount (NAS weg/Rebuild) war
mounted:false, verschwand aus „Verfügbare Ziele" und ließ sich nicht neu
anlegen („Name existiert"). Jetzt: `reachable`-Status (bounded `timeout 3
ls` — Endpoint lädt in 0,014 s statt 10 s), `umount -l`-Fallback,
`reparieren()` (lazy abhängen + frisch mounten), Re-Add repariert statt
409, POST /storage-mounts/{name}/repair, eigene „Netzwerk-Mounts"-Liste im
UI mit Status + Reparieren/Entfernen. Bewiesen: Reparatur des `rippy`-
Mounts → mounted:true, writable:true.
- **HandBrake-Versionen konsistent**: Windows-Installer zieht dynamisch die
neueste (GitHub latest) — bewiesen: HandBrake 1.11.2 installiert (statt
fest 1.9.2). Update-UI erklärt: Docker = Debian-stabil (bewusst älter),
Windows = neueste. Nebenbei ein latenter Installer-Bug gefixt: PowerShell
5.1 liest .ps1 als ANSI — ein Gedankenstrich zerschoss das Skript →
install.ps1 ist jetzt ASCII-only.
- **Encoder-/Worker-Wahl beim Rip**: Celery `worker_direct=True`, gezieltes
Routing (transcode_queue) mit sicherem Fallback auf die geteilte Queue.
Bewiesen mit ping_worker: Task landet exakt beim gewählten Node. Dropdown
im Rip-Dialog ab 2 Online-Workern. Node-Zuordnung disambiguiert bei
mehreren Workern pro Host. Nebenfund gefixt: DeviceDiscovery leitete die
Titel-Auswahl (titles) gar nicht weiter.
⚠️ Gezieltes Routing braucht AKTUELLEN Worker-Code (worker_direct) — ein
vor dieser Version installierter Remote-Worker pingt zwar, konsumiert aber
seine Direkt-Queue nicht → neu installieren.
- **Voller Transcode-E2E weiterhin durch die UHD-Key-Lage blockiert** (keine
entschlüsselbare Disc) — die Routing-Mechanik ist per ping_worker bewiesen.
---
## Vorheriger Stand: v3.6 — Design 2.0 gelandet + Ein-Branch-Umstellung (24.07.2026)
- **Design 2.0 ist in `main`** (von Gemini umgesetzt): UI von 428
`theme === 'dark'`-Ternaries auf Tailwind-`dark:` + UI-Primitives
(`components/ui/`, `lib/design.ts`) umgebaut, „Cinematic Cinema OS"-Look.
Ampel grün (49 Tests, Ruff, Vite-Build), Live auf der VM.
- **Nur noch EIN Branch: `main`** (Commander-Entscheid). Der
`stable`-Zwischenbranch samt Grün-Gate ist abgeschafft — er war
vestigial, weil die VM ohnehin aus `main` deployt. Konsequenzen:
- ci.yml: Beförderungs-Schritt raus, die Ampel PRÜFT nur noch.
- deploy.sh: nutzt `main` statt `stable`.
- AGENTS §C / README / DESIGN-Briefing entsprechend umgeschrieben.
- Gelöschte Branches: `stable`, `design-2.0` (gemergt), `kernumbau-2026-07-23` (alt).
- **Deploy-Weg ab jetzt:** push → Ampel grün prüfen → auf der VM
`git pull` (in ~/projects/rippy) bzw. `./deploy.sh`.
---
## Vorheriger Stand: v3.5 — Track-Tabelle, nativer Windows-Worker, Anleitungs-Tab (24.07.2026, Claude)
- **Volle Titel-Auswahl vor dem Rip**: „Disc scannen" im Rip-Dialog →
Worker-Task scan_tracks (makemkvcon info; Ergebnis via settings-Tabelle
'tracks:<device>', UI pollt) → Tabelle mit Checkbox/Dauer/Größe/Kapiteln,
Vorauswahl ab 5 min. Gewählte Titel gehen als meta.titles in den Job;
der Worker rippt sie einzeln (makemkvcon kann pro Aufruf nur einen Titel
oder all), Fortschritt anteilig. Parser mit Tests (apdefs-Attr 8/9/11).
- **Nativer Windows-Worker (ohne Docker!)**: Einstellungen → Worker bietet
jetzt ZWEI Varianten — Linux (Docker, wie gehabt) und Windows (nativ,
braucht nur Python). Die Rippy-Instanz versorgt sich selbst:
GET /worker-setup/windows liefert install.ps1, /worker-setup/paket den
Worker-Code als Zip (beides liegt via api-Dockerfile im Image).
install.ps1: venv + Abhängigkeiten, HandBrakeCLI 1.9.2 vom offiziellen
GitHub-Release (URL verifiziert), start-worker.bat (celery -Q transcode
--pool=solo), optional -Autostart (schtasks onlogon). tasks.py lädt auf
Windows ohne fcntl (Import-Guard); rip_disc ist dort hart verriegelt.
Für echte Transcodes übersetzt RIPPY_PATH_MAP die Container-Pfade aufs
Netzlaufwerk (pfad_lokal, mit Tests) — Voraussetzung bleibt eine
Freigabe der Rippy-Ablage (derselbe offene Infra-Punkt wie beim
Linux-Remote-Worker/AI-Box).
**E2E-BEWEIS (24.07., Commander-PC):** Installer in einem Rutsch
durchgelaufen (venv, Abhängigkeiten, HandBrakeCLI 1.9.2), Worker
gestartet und in Rippy erschienen:
`test-windows-nativ | online: True | HandBrake: 1.9.2 | IP: 192.168.178.98`
— echte LAN-IP, Celery-Ping grün. Testeintrag danach wieder entfernt.
- **Tray-Symbol + rückstandsfreie Deinstallation (v3.5.1, bewiesen):**
tray.py (pystray/Pillow, nur Windows-Worker) — Disc-Symbol neben der
Uhr mit Status, Start/Stopp, „Rippy öffnen", Log, Beenden; Celery läuft
als Kind ohne Konsolenfenster. install.ps1 erzeugt start-tray.bat
(empfohlen), start-worker.bat (Debug) und uninstall.ps1. **E2E bewiesen
auf dem Commander-PC:** Frisch installiert → Tray + celery-Kind liefen →
`tray-test` online in Rippy → uninstall.ps1 -Force stoppte alle
Prozesse, meldete den Worker per DELETE /workers/{name} ab und löschte
den Ordner restlos (danach: 0 Prozesse, Ordner weg, in Rippy nur noch
rippy-hauptworker).
- **Anleitungs-Tab** im UI: kompletter Selbsterklärer (Disc-Weg, Serien,
NAS, Media-Server, Benachrichtigungen, Key, Worker, Downloads, FAQ).
- Design 2.0 kommt bewusst in eine FRISCHE Session (Commander-Entscheid) —
Infrastruktur steht (darkMode 'class'), reine Konvertierungsarbeit.
---
## Vorheriger Stand: v3.4 — Restefeger: der Ideen-Katalog ist abgearbeitet (24.07.2026, Claude)
**Commander-Entscheid: AUTH IST KOMPLETT RAUS** (Heimnetz-only; das UI hatte
nie einen Login, die Endpoints waren Placebo, passlib/bcrypt brach die Ampel).
/token + /api-keys + auth.py + Abhängigkeiten entfernt, JWT_SECRET_KEY ist
keine Pflicht mehr — der Schnellstart läuft ohne .env-Zwang. KONZEPT §10.
**Neu in v3.4 (Details in ROADMAP Etappe 15):**
- **Serien-Flow**: Rip-Dialog fragt Serienname + Staffel → Ablage
`<Serie>/Season NN`, tvshow.nfo/poster im Serien-Ordner, und die
**Episoden werden per Laufzeitabgleich (TMDB) erkannt und benannt**
(„Serie S01E02.mkv") — nur bei eindeutiger Zuordnung, sonst ehrliches Log.
Komplette Staffel auf einer Disc klappt auch bei uniformen Anime-Laufzeiten.
- **Jellyfin/Emby-Refresh** nach jedem fertigen Rip (URL/API-Key +
Test-Knopf in Einstellungen → Ripping) — Disc rein, Film erscheint im
Server, null Klicks dazwischen.
- **Duplikat-Warnung** per Disc-Fingerabdruck (Karte zeigt „bereits
gerippt", Vollautomatik überspringt).
- **„Nur Hauptfilm" funktioniert jetzt wirklich** (Info-Lauf → längster
Titel; pro Rip im Dialog wählbar). Vorher wirkungsloses Setting.
- **OMDb-Treffer werden eingedeutscht** (TMDB /find über die IMDb-ID).
- Dashboard: **Speicherplatz-Anzeige** (amber < 60 GB) + **CSV-Export**.
- **Metadaten-Seite entfernt** (Korrektur-Popup ist der einzige Weg) samt
Placebo-Endpoints; Doppel-Jahr „(2009) (2009)" gefixt.
- **Remote-Worker-Blocker behoben**: redis/postgres waren NIE veröffentlicht
— Ports 6379/5432 jetzt offen, API_URL für Worker gesetzt. Damit sind
AI-Box/Windows-Worker überhaupt erst anschließbar.
**Bewusst offen** (ROADMAP „Ideen-Katalog (Rest)"): volle Track-Tabelle,
nativer Windows-Worker (braucht Testlauf auf Ziel-Hardware), Design-2.0-
Konvertierung (Infrastruktur steht), AI-Box-NFS-Export, Kodi-Refresh.
---
## Vorheriger Stand: v3.3 — Praxis-Feedback-Runde (24.07.2026, Claude)
**Zwei echte Bugs mit Beweis gefixt:**
1. **SMB-Mount „Unable to apply new capability set"**: mount.cifs hebt
CAP_DAC_READ_SEARCH an — die fehlt in Dockers Default-Caps. Auf der VM
reproduziert (Bounding-Set a82425fb, Bit 2 fehlt) und mit
`cap_add: DAC_READ_SEARCH` bewiesen behoben (docker-compose.yml).
2. **TMDB fiel still aus**: Der Client konnte nur v4-Bearer-Tokens — der
eingetragene übliche v3-Key (32 Hex) bekam still 401, Suche lieferte nur
OMDb. Jetzt beide Key-Arten (ist_v4_token, mit Tests); Einstellungen →
APIs hat „Verbindung prüfen" mit Live-Status je Quelle (am Cache vorbei).
**Neu in v3.3:**
- **4K UHD als eigener Disc-Typ** (classify ≥ 55 GiB, beide detection.py,
Tests): eigene Badge-Farbe überall, Prescan-Label „4K UHD".
- **UHD-Kette diagnostiziert (Nachtrag, gleicher Tag): Der BU40N-Flash IST
erledigt** — makemkvcon meldet „Using LibreDrive mode (v06.3)". Der
Summer-Wars-Fehlschlag lag NICHT am Laufwerk, sondern an „The volume key
is unknown" — auf der VM mit 1.17.7 UND der aktuellsten 1.18.4
reproduziert. Konsequenzen:
MakeMKV auf **1.18.4** gehoben (Scan-Hänger durch überall genutztes
--noscan umgangen, auf der BU40N sauber gelaufen; URL-Base → /download),
run_makemkv reicht kritische Meldungen (volume key/Key abgelaufen) in
den Fehlertext durch, UHD-Klartext unterscheidet jetzt „Laufwerk kann
kein UHD" von „Disc-Schlüssel fehlt" inkl. Forum-Dump-Hinweis
(MakeMKV speichert den AACS-Dump automatisch unter /root/.MakeMKV/).
**⚠️ Korrigiert am 25.07.2026 (siehe v3.10 ganz oben):** Die hier
ursprünglich genannte Erklärung „die Disc ist neuer als MakeMKVs
Schlüssel-DB, ein MakeMKV-Update löst das" ist FALSCH. Nachgemessen:
MakeMKV hat gar keine nachladbare Schlüssel-DB mehr und versucht auch
keinen Online-Abruf; die alten Schlüssel-Server sind tot. Der Weg zum
Ziel heißt `KEYDB.cfg`, nicht „warten auf die nächste Version".
- **Vollautomatik** (Einstellungen → Ripping): Disc erkannt → Rip startet
ohne Popup in den passenden Schnellwahl-Ordner (Serie/Film/Musik).
- **Job-Verwaltung**: „Neu komprimieren" nur noch, wenn Rohdaten wirklich
daliegen (can_retry); Jobs einzeln löschbar (Papierkorb) + „Erledigte
aufräumen" mit Bestätigungs-Dialog — Dateien bleiben immer liegen.
- **„Alle herunterladen"** im Job-Detail (gestaffelte Einzel-Downloads —
bewusst kein Server-seitiges 40-GB-Zip).
- **Worker zuordenbar**: WORKER_NAME-Env als stabiler Anzeigename (compose:
rippy-hauptworker; Remote-Worker: frei wählbar) — fixt zugleich die
Offline-Leichen nach Rebuilds; IP + Container-ID werden mit angezeigt,
verwaiste Einträge sind löschbar (DELETE /workers/{name}). Online-Abgleich
läuft jetzt über info.hostname.
- **Disc-Karte**: „Quelle: TMDB/OMDb/MyAnimeList · xx % sicher" statt des
nackten „Übereinstimmung xx %"; TMDB-Metadaten sind Deutsch (language=
de-DE war schon überall dran — sie kamen nur nie an, siehe Key-Bug).
- **Ripping-Tab nach Medium** gegliedert (Video / Audio-CD / Allgemein) +
Klartext: alle Tonspuren & Untertitel bleiben erhalten (--all-audio/
--all-subtitles) — wichtig für Anime.
- **Ordner-Verwaltung** (Ex-„Dateibrowser") ist jetzt beschriftet, erklärt
und standardmäßig eingeklappt.
- **Docker-Pflicht beim Worker**: ehrlich im UI beantwortet; nativer
Windows-Dienst steht als Ausbaustufe in der ROADMAP (Ideen-Katalog).
---
## Vorheriger Stand: v3.2 — Universal-Komfort-Runde + Ampel entrostet (24.07.2026, Claude)
**Wichtigster Befund zuerst: die Ampel war seit dem 23.07. ROT und `stable`
hing 10 Commits hinter `main`** — deshalb kam nichts Neues mehr auf die VM.
Zwei Ursachen, beide behoben:
1. bcrypt ≥ 4.1 bricht passlib 1.7.4 (`__about__` entfernt → Selbsttest wirft
„password cannot be longer than 72 bytes") → **bcrypt==4.0.1 gepinnt**.
2. Der cache_keys-Test kannte den Disc-Fingerprint im Prescan-Key (23.07.)
nicht → Test an das echte Format angepasst + Fingerprint-Testfall dazu.
**Neu in v3.2 (Commander-Sammelauftrag, alles mit Tests / Vite-Build grün):**
- **Media-Server-Integration**: Setting `mediaServer` (Wizard + Einstellungen →
Ripping): Zielordner „Titel (Jahr)" statt Job-UUID; für Jellyfin/Emby/Kodi
zusätzlich movie.nfo/tvshow.nfo + poster.jpg (Worker: medien.py). Plex = nur
Benennung. Job speichert Disc-Metadaten jetzt mit (jobs.meta, Migration).
- **Benachrichtigungen ECHT**: notify.py in API+Worker (Discord/Slack/ntfy/
generisch, Erkennung an der URL) — das Feld war vorher ein Placebo. Meldung
bei fertig/fehlgeschlagen/abgebrochen; Anleitung + „Test senden" im UI.
- **SMB-Scan-Fix**: „NT_STATUS_ACCESS_DENIED" heißt jetzt im Klartext „Gast-
Abfrage verweigert → Benutzer/Passwort eintragen"; Credential-Felder stehen
im UI VOR dem Auflisten-Knopf. NAS-Ziel-Anlage quittiert per Toast.
- **4K-UHD-Vorsorge**: Arbeitsverzeichnis `workDir` konfigurierbar (auf
NAS-Freigabe legbar), Platz-Check per Disc-Größe (ioctl) VOR dem Rip mit
Klartext-Abbruch statt voller Platte bei 40 GB.
- **Einstellungen → System**: Werkzeug-Versionen je Worker (MakeMKV/HandBrake,
ENV MAKEMKV_VERSION im Image), Key-Quelle (ui/env/keiner), freier Platz;
**MakeMKV-Beta-Key im UI pflegbar** — gilt ab dem nächsten Rip, ohne Rebuild.
- **Job-Detail-Popup** (Klick auf Titel in „Neueste Jobs"): Poster, Jahr,
Beschreibung, Genres, Ablagepfad, Fehler (GET /jobs/{id}/detail).
- **Download fertiger Rips im Browser**: „Download"-Knopf bei fertigen Jobs
(Aktion-Spalte) → Dateiliste mit Größen im Detail-Popup, Stream via
GET /jobs/{id}/files/{name} (Pfad-Validierung strikt unter /app/media,
realpath-Check gegen Symlink-Ausbrüche) — vorher nur per scp erreichbar.
- **UI-Feedback**: Toast-System (ToastContext, ploppt oben rechts), drehende
Refresh-Knöpfe, Logs-Pills um Warnung/Fehler ergänzt, Datei-Browser zeigt
jetzt auch DATEIEN (grau, mit Größe) — der „leere" Bluray-Ordner war voll.
- **Favicon** (Disc, Indigo-Verlauf) + **Footer** „Created with ❤️ by LucyAI,
Claude and KrBrZ".
- **Doku**: README (Media-Server, Benachrichtigungen, UHD-Arbeitsverzeichnis,
„Rippy woanders bereitstellen"), ROADMAP Etappe 13 + Ideen-Katalog,
KONZEPT-Fortschreibungen (Abschnitt 10), AGENTS-Stand.
**Nächste Schritte:** unverändert die Fäden aus v3.1 (unten) + Ideen-Katalog
in der ROADMAP (Serien-Flow, Duplikat-Warnung, Jellyfin-Refresh, Auth).
---
## Vorheriger Stand: v3.1 — E2E BEWIESEN + Universal-Runde (24.07.2026, Claude)
**Der Beweis steht: Evangelion 2.22 (BD-50) komplett durch die Kette** —
erkannt → verlustfrei gerippt (43 GB, LibreDrive) → H.264-komprimiert →
**4,8 GB Endergebnis** (Hauptfilm 3,3 GB + 4 Extras), Rohdaten automatisch
gelöscht. Retry-transcode dabei live bewiesen (x265 war auf 4 vCPUs zu lahm
→ Abbruch + Neustart mit H.264 OHNE Neu-Rip).
Seit v3.0 dazugekommen (alles deployt, Ampel grün):
- **Korrektur-Flow**: „Nicht korrekt?!"-Popup an der Disc-Karte; Suche über
alle Quellen (GET /metadata/search), Wahl wird 30 Tage per
Disc-Fingerabdruck gemerkt (POST /metadata/override)
- **Metadaten-Kette**: BD-Klartext-Titel via ro-Mount (bdmt_*.xml),
Jikan/MAL (keyless, Ähnlichkeits-Score), OMDb-Qualitäts-Gate,
Wort-Strip-Degradation, ehrliches Cache-TTL (unknown nie cachen)
- **Worker-Verwaltung** (Einstellungen → Worker): Live-Erreichbarkeit
(Celery-Ping + Minuten-Herzschlag), Copy-Paste-Anbindung neuer Maschinen
- **Speicherziele**: SMB-Freigaben-Auflistung, Schreibtest beim Mount,
lokaler Ordner-Browser + mkdir; Mounts via UI (CAP_SYS_ADMIN + rshared)
- **First-Run-Wizard**, Job-Abbruch (kooperativ), Transcode als eigener
Task auf Queue `transcode` (Basis Remote-GPU-Worker), ruff.toml gepinnt
**Nächste Schritte:** AI-Box als VAAPI-Transcode-Worker (VCN4; NFS-Export
nötig, deploy/remote-transcode-worker.yml) · TMDB-Key eintragen ·
Arcane-GitSync-Binding auf `stable` (aktuell Notfall-Deploy-Weg) ·
Metadaten-Seite entfernen, wenn Popup abgenommen · Jellyfin-Postprocessing
an Worker · Auth vor schreibende Endpoints · MAKEMKV_APP_KEY erneuern
(~Ende Juli, Forum t=1053).
---
## Vorheriger Stand: v3.0 — Kernumbau (23.07.2026, Claude)
**Der Zweck existiert jetzt wirklich.** Komplett-Audit ergab: „Disc rein →
gerippt raus" hatte nie einen Code-Pfad (Details in ROADMAP Etappe 11).
Kernpunkte des Umbaus:
- **Rip-Kette komplett neu**: POST /jobs → Celery → Worker (MakeMKV 1.18.4
verlustfrei, Etappe 10) → Postgres-Job-Status → UI. CD weiter via abcde.
- **Disc-Erkennung** über Kernel-ioctls (CDROM_DISC_STATUS + Größe), Watcher
pollt statt udev (im Container gibt es kein udev).
- **UI**: echter Vite-Build hinter nginx mit /api-Proxy — vorher Dev-Server
und hartkodiertes localhost:8000 (deshalb waren nie Laufwerke zu sehen).
- **Compose**: Laufwerk als devices: (Bind-Mounts gaben EPERM), Ausgabe auf
media-Volume (vorher /output → nicht gemountet + read-only-Falle).
- **Placebos entfernt**: Mock-Logs, toter SSE-Stream, udevadm-Discovery,
14 Troubleshooting-Altdateien (ARCAN-*.md, Setup-Skripte, udev-Rules).
**Hardware-Kette (23.07.):** Laufwerk (Verbatim 4K BD RW, BU40N 1.05) hängt am
Proxmox-Host; LPM-Quirk `usbcore.quirks=18a5:0428:k` gefixt die Serien-Disconnects
(reboot-fest in GRUB). VM 106 bekommt es per USB-Passthrough `host=18a5:0428`.
⚠️ Firmware 1.05 kann KEIN UHD-Ripping (verschlüsselt) — Crossflash auf 1.03-MK
ist der dokumentierte Weg, separater Faden. DVD/BD normal geht.
**Offen:** E2E mit echter Disc · Jellyfin-Post-Processing an Worker anbinden ·
Auth vor schreibende Endpoints · MAKEMKV_APP_KEY in Arcane-Env pflegen (monatlich).
---
## Vorheriger Stand
**v1.10 — Complete Dark Mode Implementation (22.07.2026)**
Rippy läuft auf Arcane VM (192.168.178.162):
- ✅ Arcane WebUI: http://192.168.178.162:3552
- ✅ Rippy UI: http://192.168.178.162:80
- ✅ Rippy API: http://192.168.178.162:8000
- ✅ Theme Context ausgelagert (App.tsx → ThemeContext.tsx + useDarkMode.ts)
- ✅ Config Validation mit TMDB-API-Key Pflicht
- ✅ Cache Key Centralization (cache/keys.py)
- ✅ Dark Mode mit Theme-Toggle und localStorage persistence (UI-Only)
- ✅ **Alle UI-Komponenten komplett auf Dark Mode aktualisiert:**
- `App.tsx`, `Dashboard.tsx`, `Settings.tsx`, `MetadataPreview.tsx`
- ✅ Sidebar Navigation mit Dark Mode Support
- ✅ Einstellungen-Page mit Tab-Struktur
- ✅ Doppelte Arcane-Einträge behoben (`rippy` statt `Rippy`)
- ✅ Import-Fixes (relative → absolute Imports)
- ✅ Git Sync in Arcane konfiguriert
- ✅ Commit: `d2fb281` — Complete Dark Mode Implementation
### Docker-Container
| Container | Port | Status |
|-----------|------|--------|
| rippy-api | 8000 | Healthy |
| rippy-worker | - | Running |
| rippy-ui | 80 | Running |
| rippy-postgres | 5432 | Healthy |
| rippy-redis | 6379 | Healthy |
### Features
- ✅ **Etappe 3**: SQLite-Cache, TMDB/MusicBrainz/TheTVDB Clients, Pre-Scan, Metadaten-Preview
- ✅ **Etappe 4**: Jellyfin-Formatierung (NFO-Generator, Image-Downloader)
- ✅ **Etappe 5**: JWT-Auth (15min/7T), Rate-Limiting (100/min), API-Key-Management
- ✅ **Etappe 6**: Docker read_only, tmpfs, healthchecks, minimale Images
- ✅ **Etappe 7**: API UI Modernisiert, Import-Fixes, Doppelte Einträge behoben
- ✅ **Etappe 8**: Dark Mode mit Theme-Toggle, Separation of Concerns
- ✅ **Etappe 9**: Complete Dark Mode Implementation (alle UI-Komponenten)
- ✅ **Dokumentation**: README, KONZEPT.md, ROADMAP.md, SAVEPOINT.md
### Nächste Schritte
- Job-Verlauf UI (aktuell nur leere Listen)
- TMDB API Schlüssel in Arcane Environment konfigurieren
- Proxmox LXC Template
- Ansible Playbooks
### SoC Refactoring — Abgeschlossen (Etappe 9)
- ✅ **Theme-Context**: Theme-Logik aus App.tsx ausgelagert
- ✅ **Dark Mode Utility**: useDarkMode Hook implementiert
- ✅ **Component-Struktur**: Dark Mode props von inneren Komponenten versteckt
- ✅ **Config Validation**: TMDB-API-Key ist Pflicht
- ✅ **Cache Key Centralization**: cache/keys.py erstellt
- ✅ **CD-Ripping**: abcde-Integration implementiert
### Git-Log
```
d2fb281 - fix: complete dark mode implementation with all UI components
43dfce5 - feat(ui): implement dark mode with theme toggle
rippy-ui-modern - UI Modernisiert & Import-Fixes
3d87c8c - Dokumentation aktualisiert
bdb9f8d - SAVEPOINT v1.5: Sicherheit abgeschlossen
efa642e - Etappe 6: Sicherheit
```
---
## v2.0 — Fix-Runde nach externem Code-Review (22.07.2026, Claude)
**Alle Befunde des Reviews behoben, echte Tests eingeführt, Ampel-CI aktiv.**
Gefixt:
- **Metadaten-Preview WIEDERHERGESTELLT**: Beim SoC-Refactoring hatte ein Stub-Paket
die echte prescan-Implementierung überschattet — die Preview lieferte immer
„Unknown Disc". Echte Logik liegt jetzt im Paket, tote Altmodule (cache.py,
prescan.py flach) gelöscht.
- Fortschrittsmeldung: Celery `update_state` statt Aufrufe eines nie existierenden
Tasks; `self.send_task` (erfundene API) entfernt.
- abcde-Kommando korrigiert (`-o` war doppelt → CD-Ripping war nie funktionsfähig);
Zielverzeichnis jetzt via OUTPUTDIR-Config; Kommando-Bau als testbare Funktion.
- JWT: fester Schlüssel PFLICHT (kein Zufalls-Fallback pro Prozess mehr);
Logout blacklistet wirklich; Cleanup löscht nur Abgelaufene (vorher: alles).
- main.py: crashende Endpoints repariert (fehlende Imports Path/secrets,
nicht existentes api_keys-Dict → ratelimit-Store), Audio-`year`-Bug,
Admin-Zugang aus .env statt hartkodiert.
- Ruff komplett grün (29 Funde), package-lock.json committet.
- Tests: test_auth, test_cache_keys, test_ripping_helpers (der giftige
test_health-Placebo ist raus).
## ✅ ENTSCHIEDEN (22.07. abends): Das KONZEPT gilt — MakeMKV kommt
**MakeMKV lossless (Muss-Feature) wird umgesetzt — als eigene Etappe 10 in der
ROADMAP** (Worker-Container + makemkvcon + Beta-Key, in Zed zu bauen, E2E mit
echter Disc). Bis dahin bleibt HandBrake als SICHTBARER Übergang aktiv — jede*r
weiß: aktuell wird transkodiert, nicht verlustfrei gesichert.
## Deploy-Weg seit 22.07. abends: GRÜN-GATE (vollautomatisch)
Push auf `main` → Ampel prüft → grün → CI befördert auf Branch `stable` →
**Arcane-GitSync zieht `stable` und deployt**. Rot deployt NIE. `./deploy.sh`
ist nur noch der Notfall-Hebel; SSH zur VM bleibt als Fallback erlaubt.