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