Files
rippy/SAVEPOINT.md
T
HitonabiandClaude Fable 5 d21332e3c7
Ampel / ampel (push) Successful in 1m44s
docs: SAVEPOINT — Einstellungen-Karten und Version 5.1.0
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 22:10:10 +02:00

199 KiB
Raw Blame History

SAVEPOINT — Rippy

Aktueller Stand: 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: ".

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äuftnode: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 transcodingund 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/GAuswerfen 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 ImportErrorabsichtlich, 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 einziehenStore/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 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):

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
  • unklarDialog, 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:

@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 Fehlertexterledigt: 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():

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 = falseden 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 qemu64host 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" erledigtschwacheEncoderCpu() 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 (fe12401vce/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 qemu64host 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:

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_BASEMAKEMKV_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 Ampeltest_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-av1kein 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 transcodePresetDEFAULT_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:', 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 stableArcane-GitSync zieht stable und deployt. Rot deployt NIE. ./deploy.sh ist nur noch der Notfall-Hebel; SSH zur VM bleibt als Fallback erlaubt.