Files
rippy/KONZEPT-WINDOWS.md
T
HitonabiandClaude Opus 5 e514e4070b docs(konzept): Entscheid 8 — der Remote-Worker bleibt bei Docker
Commander: „Das soll NICHT Teil des neuen Standalone-Produktes sein, da
dieser NUR fuer die Docker-Variante verwendet wird."

Damit ist bestaetigt, was in Paragraph 11.1 zuvor nur als Auslegung stand.
Der Remote-Encode-Worker wird WEDER abgeraeumt (er ist Docker, und Docker
bleibt unangetastet — Entscheid 2) NOCH nach v5 uebernommen (v5 ist
Einzelplatz, Entscheid 3 — es gibt dort keinen zweiten Knoten, dem man
etwas schicken koennte).

Dazu die eigentliche Reparatur an diesem Abschnitt: Er war in
Entwickler-Begriffen geschrieben (Modulnamen, PyInstaller, os.name-Zweige)
und deshalb fuer den Adressaten nicht lesbar — zweimal nachgefragt, zweimal
zu Recht. Paragraph 11.1 fuehrt die drei Teile jetzt erst in Klartext ein:

  A ist Rippy. B ist ein Handlanger fuer die VM. C sind Notizzettel,
  die wegen A eingeklebt wurden.

Und macht den Unterschied A/C an dem fest, worauf es beim Aufraeumen
ankommt — nicht am Inhalt, sondern am Ort: A sind ganze Ordner, die Docker
nie aufschlaegt (wegwerfen). C sind einzelne Seiten in Ordnern, die Docker
taeglich benutzt (einzeln durchgehen). Erst danach kommt die Tabelle mit
den Dateinamen.

Das ist kein Beiwerk: Genau diese Verwechslung wuerde beim Aufraeumen die
laufende VM treffen. Und manche Windows-Zeile braucht sogar B weiter — etwa
die Regel, dass eine Laufwerkswurzel ihren abschliessenden Trenner behaelt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-30 15:59:11 +02:00

988 lines
51 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# KONZEPT — Rippy für Windows
> **Rippy v5.** Ein eigenständiges Windows-Programm, gebaut mit Electron und
> TypeScript. Grüne Wiese: kein Docker-Code, kein Python, keine Container-Reste.
>
> Stand: 30.08.2026 · Branch `worktree-windows-electron`
---
## 0. Leseanleitung
| § | Frage |
|---|-------|
| 1 | Warum wird neu gebaut statt repariert? |
| 2 | Was hat der Commander entschieden? |
| 3 | Was wurde gemessen, bevor entschieden wurde? |
| 45 | Wie ist das Programm aufgebaut, und was passiert, wenn eine Disc reingeht? |
| 69 | Features, Oberfläche, Setup, Werkzeuge |
| 10 | Was kommt aus dem alten Rippy mit? |
| 11 | Was aus `main` entfernt wird |
| 1214 | Etappen, Risiken, was NICHT gebaut wird |
**Was dieses Dokument NICHT tut:** Es erfindet keine Messungen. Alles, was hier
als Zahl oder Verhalten steht, ist entweder in § 3 an dieser Maschine gemessen,
im alten Rippy belegt (`SAVEPOINT.md`, `AGENTS.md`) oder ausdrücklich als
**Annahme** markiert. Die Trennung ist wichtig, weil dieses Projekt schon
mehrfach teuer dafür bezahlt hat, dass jemand eine plausible Erklärung für eine
gemessene gehalten hat.
---
## 1. Warum neu gebaut wird
Die heutige Windows-Fassung ist **kein Windows-Programm**. Sie ist der
Docker-Code in einer EXE:
```
RippySetup.exe (PyInstaller)
├─ docker/api/ → FastAPI, uvicorn auf 127.0.0.1:7788
├─ docker/worker/ → der Celery-Worker, nur ohne Celery
├─ docker/ui/dist/ → dieselbe React-Oberfläche wie im Browser
└─ pywebview → ein Fenster davor
```
Das Ergebnis steht im `SAVEPOINT.md`: **elf Release-Kandidaten in drei Tagen.**
Und die Funde sind fast alle vom selben Typ — Linux-Annahmen, die unter Windows
etwas anderes bedeuten:
| Fund | Was passierte |
|------|---------------|
| `timeout 4 ls -d <pfad>` als Verzeichnisprüfung | Unter Windows gibt es `timeout.exe`, aber sie kennt weder `ls` noch `-d`. Antwort war **„weg" für jedes Verzeichnis** — Rohdaten waren grundsätzlich unsichtbar. Dazu blitzten Konsolenfenster auf. |
| `shutil.which("makemkvcon")` | Findet unter Windows nie etwas — Programme liegen in `Program Files`, nicht im PATH. Die 4K-Schlüssel-Automatik lief deshalb **nie** an. |
| `os.path.isdir("/app")` | Rippy hielt sich für einen fremden Docker-Worker. Zweimal gefunden, an zwei Stellen. |
| Routen `/dev/{name}` | Das UI ruft mit `G` auf, gebaut war `/dev/G`. **Auswerfen und Disc-Scan antworteten immer mit 404.** |
| `F:\` wurde zu `F:` | Unter Windows ist `F:` ohne Trenner der *aktuelle Ordner* auf F, nicht dessen Wurzel. 16,5 GB Rohschnitt unsichtbar; der Dialog bot nur „Neu rippen" an. |
| `makemkvcon` überlebte den Elternprozess | Windows räumt Kindprozesse nicht auf. Zwei Waisen hielten das Laufwerk fest. |
| Zwei Speicher für dieselben Ordner | Die Oberfläche schrieb `outputDir` in die Datenbank, der Betrieb las `storage.medien` aus einer Konfigurationsdatei, die niemand schrieb. Eingestellt `E:\Rippy`, angezeigt `C:\Users\...\Videos\Rippy`. |
Diese Liste ist nicht das Problem — sie ist das **Symptom**. Jeder einzelne
Fund war eine Stelle, an der Code, der für einen Linux-Container geschrieben
wurde, unter Windows etwas anderes bedeutet. Solange die Grundlage ein
umgebogener Container ist, ist der nächste Fund dieser Sorte nur eine Frage der
Zeit.
**Die Entscheidung ist deshalb nicht „das ist schlecht gebaut", sondern: das
ist am falschen Ort gebaut.** Ein Windows-Programm fängt bei Windows an, nicht
bei einem Container, dem man Windows beibringt.
---
## 2. Die Entscheidungen (Commander, 30.08.2026)
Acht Entscheide, alle am selben Tag. Die ersten vier legen die Architektur
fest, die naechsten drei beantworten die Punkte, die das Konzept offengelassen
hatte, und der achte steht bei der Sache, zu der er gehoert (Paragraph 11.1). Sie stehen hier, damit später nachvollziehbar ist, was Entscheidung war
und was Vorschlag.
### Entscheid 1 — Alles TypeScript/Node
Es gibt **keinen Python-Anteil** in Rippy für Windows. Kein Sidecar, keine
mitgelieferte Laufzeit, keine IPC-Brücke zwischen zwei Sprachen. Eine Sprache,
eine Werkzeugkette, ein Setup.
*Was das kostet:* Die rund 18.000 Zeilen Python werden nicht übernommen. Was
übernommen wird, ist ihr **Wissen** — die Parser-Regeln, die gemessenen
Fallen, die Kommandozeilen (§ 10). Das ist der teure Teil an ihnen, und der
wandert vollständig mit.
*Was das bringt:* Der ganze Fehlertyp aus § 1 kann nicht mehr entstehen. Es
gibt keinen Container-Pfad, den jemand vergessen könnte, weil es nie einen gab.
### Entscheid 2 — Die Docker-Version bleibt unangetastet
Rippy auf der VM läuft weiter wie heute. Es wird nicht angefasst, nicht
migriert, nicht mitgepflegt. Zwei getrennte Produkte, keine geteilten Module.
*Begründung:* Geteilte Module zwischen zwei Betriebssystemen sind genau die
Konstruktion, an der v1 gelitten hat — drei Dateien lagen byte-identisch
doppelt im Repo, und `db.py` schrieb es sogar in den Kopfkommentar: *„Wer die
Struktur ändert, ändert BEIDE Dateien."*
### Entscheid 3 — Reiner Einzelplatz
Ein Rechner macht alles: erkennen, rippen, komprimieren, ablegen. Kein Server,
keine Netz-Queue, keine Worker-Verwaltung, keine Pfad-Übersetzung zwischen
Maschinen.
*Was damit ersatzlos wegfällt:* Celery, Redis, Postgres, die Worker-Tabelle,
`worker_direct`, die Lease-Mechanik über Netz, `RIPPY_PATH_MAP`, die
Zombie-Erkennung, die Mount-Wache, das Rate-Limit (es gibt keinen fremden
Client mehr), `/worker-setup/*`, die Encoder-Auswahl je Knoten.
Grob geschätzt sind das **40 % der heutigen Komplexität** — und zwar der Teil,
der die meisten Fehlerbilder erzeugt hat.
*Was bleibt:* Die Ablage darf trotzdem auf dem NAS liegen. Ein UNC-Pfad
(`\\nas\medien`) oder ein verbundenes Netzlaufwerk ist für Rippy nur ein Pfad.
Windows mountet, Rippy prüft — dieselbe Trennung wie in Entscheid 3 des
v2-Konzepts, nur ohne Container drumherum.
### Entscheid 4 — Eigener Branch im selben Gitea-Repo
Ein Git-Worktree unter `.claude/worktrees/windows-electron` auf dem Branch
`worktree-windows-electron`. `main` bleibt unberührt und jederzeit deploybar.
### Entscheid 5 — Kein Code-Signing-Zertifikat
*„ist egal, brauchen wir nich"*
Die EXE bleibt unsigniert. Folge: Beim ersten Start eines Downloads zeigt
Windows den SmartScreen-Hinweis („Der Computer wurde geschützt"); über
**Weitere Informationen → Trotzdem ausführen** geht es weiter. Das gehört in
die Anleitung, nicht unter den Teppich.
Eine Folge, die dazugehört und in § 6.8 steht: Ohne Signatur kann
`electron-updater` ein heruntergeladenes Update **nicht** auf Echtheit prüfen.
Die Absicherung liegt damit vollständig beim Transportweg — Updates werden nur
über **HTTPS** geladen, nie über einfaches HTTP.
### Entscheid 6 — Updates aus Gitea, auch für Fremde erreichbar
*„Gerne das Gitea Release, mit Möglichkeit dass auch EXTERNE diese Updates
fahren können"*
Zwei Teile, und der zweite hat eine Bedingung, die Rippy nicht selbst lösen
kann:
1. **Rippys Seite** — die Update-Adresse steht in der Konfiguration, vorbelegt
mit dem Gitea des Commanders. `electron-updater` braucht dafür nur einen
statischen HTTPS-Ort mit zwei Dateien (`latest.yml` und das Setup). Damit
ist Rippy von der Frage unabhängig, *wo* dieser Ort liegt.
2. **Die Netz-Seite** — ⚠️ **Gitea läuft auf `192.168.178.153` im Heimnetz.
Von außen ist das nicht erreichbar.** „Externe können Updates fahren"
verlangt also einen von drei Wegen, und die Wahl gehört dem Commander:
| Weg | Was zu tun ist | Bewertung |
|-----|----------------|-----------|
| **Gitea veröffentlichen** | Reverse-Proxy + DynDNS + TLS-Zertifikat | Ein Dienst im Heimnetz wird öffentlich — die Angriffsfläche wächst. Nur mit Bedacht |
| **Öffentlicher Spiegel** | Der Bau schiebt das Release zusätzlich zu einem öffentlichen Ort (GitHub-Release, Objektspeicher, gemieteter Webspace) | **Empfehlung.** Gitea bleibt privat, nur die fertigen Dateien liegen draußen |
| **Jeder trägt seine eigene Quelle ein** | Nichts — die Konfiguration kann es schon | Für einzelne Nutzer mit eigenem Server. Skaliert nicht |
Rippy wird für **alle drei** gebaut: eine Adresse in der Konfiguration,
sonst nichts. Die Entscheidung kann später fallen, ohne das Programm
anzufassen.
### Entscheid 7 — Der alte Windows-Weg wird entfernt, auch aus `main`
*„Nein — direkt weg, auch aus dem Original Repo. Das neue Standalone Windows
Rippy wird ein komplett neues Produkt und kein Aufbau auf die
Docker-Architektur."*
Die PyInstaller-Fassung bleibt **nicht** parallel installierbar, und ihr Code
verlässt das Repo. Das ist die schärfere Fassung von Entscheid 2: Nicht nur
werden die beiden Produkte nicht verwandt sein — der Zwitter dazwischen wird
abgeräumt.
**Das ist kein Widerspruch zu Entscheid 2.** „Docker bleibt unangetastet" meint
die Docker-*Funktion*; der Windows-Standalone-Zweig ist gerade nicht Docker,
sondern der Fremdkörper darin. Was genau entfernt wird, steht in § 11 — mit
einer Unterscheidung, die beim Aufräumen entscheidend ist.
---
## 3. Was gemessen wurde, bevor entschieden wurde
Electron bedeutet: Die Arbeit macht Node, nicht Python. Damit hängt das ganze
Konzept an einer Frage, die vorher niemand geprüft hat:
> **Kann Node überhaupt alles, was Rippy am Laufwerk braucht?**
Wäre die Antwort nein, wäre das Konzept wertlos. Also gemessen — am
30.08.2026, auf diesem Rechner, am echten Laufwerk.
### 3.1 Die Werkzeuglage
```
Node v24.19.0
npm 11.17.0
Electron 44.0.0 (Chromium 152.0.7977.54 · Node 24.18.1)
WebView2 151.0.4129.107
Laufwerk G: HL-DT-ST BD-RE BU40N USB Device (Medium eingelegt)
```
### 3.2 Win32 aus Node — die Laufwerks-Steuerung
`koffi` 3.1.6 installiert, dieselben Steuercodes wie in
`src/rippy/drives/win_ioctl.py` (die dort aus dem `CTL_CODE`-Makro hergeleitet
sind, nicht abgeschrieben). Nur lesende Aufrufe, nichts ausgeworfen:
```
1. GetLogicalDrives + GetDriveTypeW -> optische Laufwerke: [ 'G' ]
2. CreateFileW \\.\G: (Zugriff 0) -> offen
CreateFileW \\.\G: (GENERIC_READ) -> offen
3. IOCTL_STORAGE_QUERY_PROPERTY -> Hersteller: HL-DT-ST | Modell: BD-RE BU40N
| Rev: 1.03 | Serial: 0025114C0149
4. IOCTL_STORAGE_CHECK_VERIFY2 -> Medium eingelegt
5. IOCTL_CDROM_DISK_TYPE -> Win32-Fehler 50
6. IOCTL_DISK_GET_LENGTH_INFO -> 33.759.690.752 Bytes (33,76 GB) => Blu-ray
```
**Punkt 5 ist kein Fehlschlag, sondern eine Bestätigung.** Fehler 50 ist
`ERROR_NOT_SUPPORTED` — genau das, was auch der Python-Treiber an diesem
Laufwerk sieht (`SAVEPOINT.md`, rc11). Node verhält sich identisch. Und die
Lehre daraus steht schon im alten Code: **Die Disc-Einordnung darf sich nicht
auf `IOCTL_CDROM_DISK_TYPE` verlassen — die Größe ist der verlässliche Weg.**
### 3.3 Die Prozess-Leine — überlebt makemkvcon einen Absturz?
Der Befund aus rc10: Windows räumt Kindprozesse nicht auf. Ein hart beendeter
Rippy hinterlässt ein `makemkvcon`, das das Laufwerk festhält. Die Lösung dort
war eine Windows-Arbeitsgruppe (Job Object) mit
`JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE`.
In Node nachgestellt — Job Object gesetzt, Kind gestartet, Eltern mit
`taskkill /F` **ohne** `/T` abgeschossen (genau das, was bei einem Absturz und
beim Drüber-Installieren passiert):
```
1. CreateJobObjectW -> Arbeitsgruppe angelegt
2. SetInformationJobObject -> KILL_ON_JOB_CLOSE gesetzt
3. AssignProcessToJobObject -> dieser Prozess hängt in der Gruppe
4. Kindprozess gestartet, PID 16040
vor dem Abschuss: Eltern lebt: True Kind lebt: True
taskkill /F /PID 29488 -> ERFOLGREICH
danach: Eltern lebt: False Kind lebt: False
```
### 3.4 Datenbank ohne native Abhängigkeit
```
node:sqlite eingebaut -> geht
```
Node 24 bringt SQLite mit (`node:sqlite`, `DatabaseSync`). Keine
`better-sqlite3`-Kompilierung, kein `node-gyp`, kein Visual-Studio-Build in der
Bau-Kette.
> ⚠️ **Ungeprüft:** Electron kompiliert sein Node mit eigenen Flags. Ob
> `node:sqlite` in Electron 44 vorhanden ist, ist an *dieser* Stelle **nicht**
> gemessen — nur in Node 24.19.0 selbst. Das ist der erste Handgriff in
> Etappe W-0. Rückfallebene: `better-sqlite3` (bewährt, aber nativ und damit
> eine Kompilierung in der Bau-Kette).
### 3.5 Ein Nebenbefund für die Bau-Kette
`npm` 11 blockiert Install-Skripte fremder Pakete standardmäßig:
```
npm warn allow-scripts koffi@3.1.6 (install: node ./cnoke.cjs -P . --prebuild --release)
```
`koffi` lädt trotzdem — es bringt vorgebaute Binärdateien mit. Aber die
Warnung gehört in die Bau-Anleitung, sonst sucht jemand eines Tages an der
falschen Stelle.
---
## 4. Aufbau
### 4.1 Drei Teile, drei Prozesse
```
┌──────────────────────────────────────────────────────────────┐
│ Rippy.exe — Electron Main-Prozess │
│ ├─ Tray-Symbol, Fensterverwaltung, Autostart, Selbst-Update │
│ └─ hält die Prozess-Leine (§ 3.3) — ALLES stirbt mit ihm │
└───────────┬─────────────────────────────┬────────────────────┘
│ IPC │ MessagePort
┌───────────▼──────────────┐ ┌──────────▼───────────────────────┐
│ Fenster (Renderer) │ │ Kern (utilityProcess) │
│ React + Tailwind │◄──┤ die GANZE Arbeit │
│ zeigt an, sonst nichts │ │ ├─ Laufwerkswache (koffi/Win32) │
│ │ │ ├─ Ablauf-Steuerung │
│ kein Node-Zugriff │ │ ├─ Auftragstabelle (SQLite) │
│ (contextIsolation) │ │ └─ Werkzeuge als Kindprozesse: │
│ │ │ makemkvcon · HandBrakeCLI │
└──────────────────────────┘ └──────────────────────────────────┘
```
**Warum drei und nicht einer:**
1. **Ein Rip dauert 3090 Minuten.** Läuft er im Main-Prozess, friert die
Oberfläche ein — Electrons eigene Dokumentation sagt das ausdrücklich.
2. **Der Kern darf abstürzen, ohne das Fenster mitzureißen.** Dann steht dort
„Der Kern ist abgestürzt, ich starte ihn neu" statt eines toten Fensters.
3. **Das Fenster darf zugehen, ohne den Rip zu beenden.** Rippy lebt dann im
Tray weiter — genau wie heute, nur ohne den Umweg über einen HTTP-Server.
**Warum `utilityProcess` und nicht `child_process.fork`:** Electrons eigene
Empfehlung. `utilityProcess` kann einen `MessagePort` direkt zum Renderer
aufbauen — der Fortschritt eines Rips geht damit ohne Umweg über den
Main-Prozess ins Fenster.
**Was es NICHT gibt:** keinen HTTP-Server, keinen Port, kein `localhost:7788`,
keine REST-API, kein SSE. Fenster und Kern reden über Electrons IPC. Ein
lokaler Webserver in einer Einzelplatz-Anwendung ist eine offene Tür ohne
Gegenwert — und er war der Grund für das Rate-Limit, den Proxy-Kummer und die
Polling-Last im alten Rippy.
### 4.2 Ordner
```
rippy-windows/
├── package.json
├── electron.vite.config.ts
├── src/
│ ├── haupt/ Electron Main
│ │ ├── index.ts Start, Einzelinstanz-Sperre, Leine setzen
│ │ ├── fenster.ts Fenster anlegen, Zustand merken
│ │ ├── tray.ts Symbol + Kontextmenü
│ │ ├── autostart.ts HKCU\...\Run
│ │ └── update.ts electron-updater
│ │
│ ├── kern/ utilityProcess — die Arbeit
│ │ ├── laufwerk/
│ │ │ ├── win32.ts DER EINZIGE ORT MIT koffi
│ │ │ ├── wache.ts Einwurf/Auswurf erkennen
│ │ │ └── disc.ts Typ, Label, Größe, TOC
│ │ ├── metadaten/
│ │ │ ├── tmdb.ts tvdb.ts omdb.ts musicbrainz.ts
│ │ │ ├── discmerkmale.ts Label + Laufzeiten + BDMV-Titel
│ │ │ └── zuordnung.ts Treffer + Sicherheitsgrad
│ │ ├── rip/
│ │ │ ├── makemkv.ts Kommandobau + Aufruf
│ │ │ ├── parser.ts TINFO/SINFO/PRGV/MSG (rein, testbar)
│ │ │ ├── audiocd.ts Roh-Sektoren + FLAC
│ │ │ └── iso.ts Datendiscs 1:1 sichern
│ │ ├── komprimieren/
│ │ │ ├── handbrake.ts Kommandobau + Aufruf + Fortschritt
│ │ │ ├── encoder.ts GEMESSEN, nicht behauptet
│ │ │ └── presets.ts
│ │ ├── ablage/
│ │ │ ├── struktur.ts Jellyfin/Emby/Kodi/Plex
│ │ │ ├── nfo.ts poster.ts medienserver.ts
│ │ ├── ablauf/
│ │ │ ├── pipeline.ts die Verkettung — NUR HIER
│ │ │ ├── zustand.ts Zustandsmaschine
│ │ │ └── auftraege.ts Warteschlange, Slots
│ │ ├── werkzeuge/
│ │ │ ├── katalog.ts wo liegt makemkvcon/HandBrakeCLI/flac
│ │ │ ├── beschaffen.ts holen, prüfen, einrichten
│ │ │ └── leine.ts Job Object (§ 3.3)
│ │ └── speicher/
│ │ ├── db.ts SQLite
│ │ ├── einstellungen.ts EINE Quelle (§ 6.7)
│ │ └── bibliothek.ts
│ │
│ ├── gemeinsam/ Typen + Ereignis-Schema — Fenster UND Kern
│ └── fenster/ React
├── bau/ electron-builder, NSIS-Skript, Icon
└── test/ Vitest
```
### 4.3 Sechs Regeln, die den Aufbau tragen
Jede ist aus einem bezahlten Fehler abgeleitet. Sie sind mechanisch prüfbar
und bekommen je einen Wächter-Test.
**R1 — Win32 lebt nur in `laufwerk/win32.ts`.**
Kein `koffi`-Import irgendwo sonst. Ein Wächter-Test greift bei jedem anderen
Fundort. *(Grund: In v1 rief `main.py` `eject()` direkt auf — deshalb brauchte
der API-Container Geräte-Zugriff und `SYS_ADMIN`.)*
**R2 — Ein Fehlschlag löscht nie einen Zustand.**
Kein `catch { return [] }`. Wer nichts Neues weiß, behält, was er wusste.
*(Grund: Fünfmal `catch(() => [])` im alten UI — jeder verpasste Abruf hieß
„es gibt keine Jobs", die Liste leerte sich im Sekundentakt. Der Commander
meldete es als „wird oft neu geladen".)*
**R3 — Auswerfen heißt: entriegeln, auswerfen, nachsehen.**
In dieser Reihenfolge. Kein Rückgabewert gilt als Beweis. *(Grund: `CDROMEJECT`
quittiert auf einem verriegelten Laufwerk Erfolg und tut nichts. MakeMKV
verriegelt die Tür während des Rips.)*
**R4 — Jede Hintergrundschleife meldet ihren Fehler.**
Kein stilles `catch {}`. *(Grund: Ein `except Exception: pass` in einer
Vorrats-Schleife hat einmal eine Stunde gekostet.)*
**R5 — Externe Schnittstellen werden gemessen, nicht erinnert.**
Jeder Kommandozeilen-Schalter, jeder IOCTL-Code trägt im Kommentar, wo er
herkommt. *(Grund: `--audio-codec` gibt es bei HandBrakeCLI nicht — es heißt
`-E`/`--aencoder`. Ein unbekannter Schalter ist für HandBrake kein Fehler:
Rückgabewert 0. Das kostete einen fertigen 16,5-GB-Rip, und ein Test hatte den
Fehler festgeschrieben statt ihn zu finden.)*
**R6 — Alles läuft an der Leine.**
Jeder Werkzeugaufruf hängt in der Arbeitsgruppe aus § 3.3. Ausgenommen ist nur
das Aufräum-Skript der Deinstallation — es muss Rippy überleben.
---
## 5. Der Ablauf — was passiert, wenn eine Disc reingeht
Der rote Faden des Commanders: **so viel Arbeit abnehmen wie möglich.**
```
1. Disc rein
│ WM_DEVICECHANGE / DBT_DEVICEARRIVAL (Ereignis, kein Poll)
│ Rückfallebene: 3-Sekunden-Takt, falls die Nachricht ausbleibt
2. Was ist das?
│ Größe (33,76 GB → Blu-ray) · Audio-TOC? · Dateisystem lesbar?
│ → Film / Serie / Musik / Daten
3. Was ist da drauf? ← DER SCHRITT, DER HEUTE ZU DÜNN IST
│ Disc-Label · Titel-Laufzeiten · BDMV-Titel · DVD-IFO · CD-DiscID
│ → TMDb / TVDb / OMDb / MusicBrainz
│ → Treffer mit SICHERHEITSGRAD
4. Sicher genug?
│ ja → sofort losrippen, nur Bescheid sagen (Zero-Click)
│ nein → fragen: Fenster hoch, Vorschläge zur Wahl
5. Rippen makemkvcon, verlustfrei, Fortschritt aus PRGV
6. Komprimieren HandBrakeCLI, Encoder GEMESSEN, Preset je Disc-Typ
7. Ablegen Zielstruktur · NFO · Poster/Fanart
8. Melden Medienserver anstoßen · Benachrichtigung · Auswerfen
9. Merken Bibliothek fortschreiben — beim nächsten Mal erkannt
```
**Was an Schritt 3 neu ist.** Heute nimmt Rippy im Wesentlichen das Disc-Label
und die längste Laufzeit. Das reicht bei „PIRATES_OF_THE_CARIBBEAN", aber nicht
bei „LOGICAL_VOLUME_ID" — und dann fragt Rippy, obwohl die Antwort auf der
Disc steht. Vier Quellen kommen dazu:
| Quelle | Wo | Was sie liefert |
|--------|-----|-----------------|
| **BDMV-Metadaten** | `BDMV/META/DL/bdmt_*.xml` auf der Blu-ray | Der vom Studio hinterlegte Disc-Titel, oft mit Sprachvarianten. **Deutlich besser als das Volume-Label.** |
| **Titel-Struktur** | makemkvcon `info` | Die Anzahl und Länge der Titel ist ein Fingerabdruck: 1 langer + viele kurze = Film; 613 gleich lange = Serienstaffel. |
| **DVD-Struktur** | `VIDEO_TS`, IFO-Dateien | Kapitelzahl und Titelsatz-Aufbau |
| **CD-DiscID** | Audio-TOC | Bereits gebaut und bewiesen (rc8): Spurlage → MusicBrainz → Album, Interpret, Jahr, jeder Titel. |
Dazu ein **Sicherheitsgrad**, der ehrlich ist. Rippy sagt nicht „das ist
Akira", sondern „das ist sehr wahrscheinlich Akira (1988)" — und fragt nur,
wenn es das nicht sagen kann. Die Schwelle ist einstellbar; wer will, lässt
Rippy immer fragen, und wer will, lässt es immer durchlaufen.
**Und wenn Rippy sich irrt:** Der Titel lässt sich nachträglich ändern, und die
Ablage wandert mit. Das ist der Unterschied zwischen „falsch geraten" und
„falsch abgelegt".
---
## 6. Die Features
### 6.1 Automatische Erkennung (§ 5, Schritt 24)
Siehe oben. Kernsatz: **Rippy fragt nur, wenn es wirklich nicht weiß.**
### 6.2 Rippen
`makemkvcon` im Robot-Modus (`--robot --messages=-stdout --progress=-same`),
verlustfrei. Der Parser (TINFO/SINFO/PRGV/MSG) wandert aus dem Python-Code
nach TypeScript — er ist reine Textverarbeitung und damit vollständig
testbar, ohne Laufwerk.
Was aus dem alten Rippy zwingend mitkommt:
- **Lesefehler heißen Lesefehler.** MakeMKV kann 1 von 2 Titeln sichern und
sich trotzdem mit 0 beenden. Rippy warnt nach dem Rip — auch und gerade dann,
wenn er als Erfolg endete — und nennt die **MSG-Nummer**: MakeMKVs Texte sind
übersetzt, die Nummern nicht.
- **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. Aber: Wenn das Nachsehen selbst scheitert, wird trotzdem gerippt —
„ich weiß es nicht" darf nie zu „es geht nicht" werden.
- **Die Ausgabe binär lesen.** makemkvcon mischt Kodierungen; ein `ü` im Pfad
hat die Kompression einmal getötet (`UnicodeDecodeError` auf Byte 0x81).
### 6.3 Audio-CDs
Der Weg aus rc8, bewiesen: Roh-Sektoren über Win32 lesen (eine Audio-CD hat
kein Dateisystem — die `Track01.cda` sind 44-Byte-Platzhalter), dann durch
`flac.exe`. Dazu MusicBrainz über die DiscID.
Zwei Fallen, beide gemessen und beide dokumentiert: Die DiscID rechnet **mit**
den 150 Frames Vorlauf, das Lesen **ohne**. Und die Leseadresse zählt in
2048er-Einheiten, obwohl ein Audio-Sektor 2352 Bytes hat.
### 6.4 ISO-Backup für Datendiscs *(neu)*
Die einzige echte Lücke gegenüber ARM. Wird eine Disc weder als Film noch als
Musik erkannt, wird sie 1:1 gesichert statt abgelehnt: sequenziell aus
`\\.\G:` lesen, Länge aus `IOCTL_DISK_GET_LENGTH_INFO` (in § 3.2 gemessen:
liefert exakte Bytes). Kein Fremdwerkzeug nötig.
### 6.5 Mehrere Laufwerke gleichzeitig *(neu)*
Laufwerke sind eigenständige Objekte mit stabiler Kennung (Seriennummer, nicht
Buchstabe — `0025114C0149` in § 3.2 gemessen). Ein Rip-Auftrag belegt genau
ein Laufwerk; die Zahl gleichzeitiger Rips und Kompressionen ist einstellbar.
> ⚠️ **Annahme, zu messen bevor sie festgeschrieben wird:** Mehrere
> `makemkvcon`-Prozesse teilen sich ein Datenverzeichnis
> (`_private_data.tar`, `settings.conf`). Der Entwurf sieht deshalb vor:
> **Rip-Phase parallel, Schlüssel-Phase serialisiert.** Das ist aus der
> Aktenlage abgeleitet, nicht gemessen — und gehört an zwei Laufwerken geprüft,
> bevor die Grenze im Code steht.
### 6.6 Bibliothek *(neu)*
Eine Ansicht über alles Gerippte: Cover, Größe, Datum, Ablageort, Dauer des
Vorgangs. Und der praktische Teil: **Rippy erkennt beim Einlegen, wenn eine
Disc schon einmal durchgelaufen ist** (über die Disc-Merkmale aus § 5) und
fragt nach, statt stumm zu doppeln.
### 6.7 Arbeitsverzeichnis und Ablage — eine Quelle
Der Fund aus rc11: Die Oberfläche schrieb in die Datenbank, der Betrieb las
aus einer Konfigurationsdatei, die niemand schrieb. Eingestellt `E:\Rippy`,
angezeigt `C:\Users\...\Videos\Rippy`.
In v5 gibt es **genau einen Speicher für Einstellungen** (`speicher/einstellungen.ts`,
SQLite). Ein Wächter-Test prüft mechanisch, dass kein zweiter Ort entsteht.
Dazu die Pfad-Regeln aus rc11, als reine Funktionen mit Tests:
- Eine Laufwerkswurzel behält ihren Trenner (`F:\`, nicht `F:`)
- UNC-Wurzeln bleiben absolut
- Verzeichnisprüfungen sind **windows-nativ** — kein `timeout ls -d`
- Pro Rip darf ein anderes Arbeitsverzeichnis gewählt werden, und **gesucht
wird später unter der Wahl des Jobs**, nicht unter der heutigen Einstellung
### 6.8 Selbst-Update *(neu)*
`electron-updater` gegen die Releases im Gitea (Entscheid 6). Rippy prüft beim
Start und danach täglich, lädt im Hintergrund und installiert beim nächsten
Start — **nie mitten in einem Rip**.
Die Quelle steht in der Konfiguration und ist vorbelegt. Ein nicht erreichbarer
Update-Server ist kein Fehler, sondern eine Zeile im Protokoll.
**Vier Regeln, die zum Entscheid gehören:**
1. **Nur HTTPS.** Ohne Code-Signing (Entscheid 5) kann `electron-updater` ein
heruntergeladenes Update nicht auf Echtheit prüfen — die Absicherung liegt
damit ganz beim Transportweg. Eine `http://`-Update-Adresse wird abgelehnt,
nicht stillschweigend akzeptiert.
2. **Nie während eines Rips.** Weder herunterladen noch installieren. Ein
Neustart mitten in einem 90-Minuten-Rip wäre der teuerste Fehler, den ein
Update-Mechanismus machen kann.
3. **Ein Fehlschlag ist sichtbar, aber nicht laut.** Die Prüfung darf still
scheitern (WLAN weg, Server aus) — aber der Zeitpunkt der letzten
erfolgreichen Prüfung steht im Über-Dialog. Wer nie prüft, weiß sonst nicht,
dass er nie prüft.
4. **Die Version, die läuft, ist ablesbar.** Im Fenster und im Protokollkopf.
Eine Fehlermeldung ohne Versionsnummer ist bei einem sich selbst
aktualisierenden Programm die halbe Miete verloren.
### 6.9 Erst-Einrichtung *(neu gedacht)*
Der Befund des Commanders zur heutigen Fassung: *„Dann hätte ich beim Setup
auch erwartet, dass ein echtes Setup passiert — wo will ich das hinspeichern,
ein Pre-Requirement-Check usw. Da kommt gar nichts, Rippy geht einfach auf."*
Der Assistent läuft **nach** der Installation, im Programm selbst, in fünf
Schritten:
| Schritt | Was passiert |
|---------|--------------|
| 1 Willkommen | Was Rippy tut, in vier Sätzen |
| 2 Prüfung | Laufwerke · Werkzeuge · Plattenplatz · Internet · WebView2 — **jeder Befund sagt, was zu tun ist** |
| 3 Werkzeuge | Was fehlt, wird geholt (§ 9) — mit Fortschritt, nicht mit einem eingefrorenen Fenster |
| 4 Ordner | Ablage und Arbeitsverzeichnis, mit echten Windows-Ordnerdialogen und Platzanzeige |
| 5 Automatik | Wie viel soll Rippy allein entscheiden? Je Disc-Typ einstellbar. |
Zwei Regeln, die aus dem alten Setup übernommen werden:
- **Nur ein Fehler blockiert.** Eine Warnung, die den Knopf sperrt, ist
Bevormundung; ein Fehler, der nur warnt, ist eine Falle. Kein optisches
Laufwerk ist eine **Warnung** — eine reine Komprimier-Maschine ist ein
vorgesehener Betriebsfall.
- **Ein Setup meldet nie Erfolg, während ein Pflichtwerkzeug fehlt.** Der
Bestand wird VOR und NACH dem Versuch gelesen; berichtet wird, was danach
wirklich da ist.
### 6.10 Was unverändert erhalten bleibt
Alles, was der Commander heute benutzt:
Kompression je Disc-Typ abwählbar (4K verlustfrei durchreichen) · Preset-Wahl
je Typ · Sprachwahl für Ton und Untertitel vor dem Rip · Original behalten ·
Vollautomatik · Nur-Hauptfeature · automatischer Auswurf · Medienserver-Refresh
(Jellyfin/Emby/Plex/Kodi) · NFO + Poster + Fanart · Serien-Staffelablage mit
Episoden-Zuordnung per Laufzeitabgleich (**nur bei eindeutiger Zuordnung wird
umbenannt**) · Webhook-Benachrichtigungen (Discord/Slack/ntfy/generisch) ·
Live-Log · Job-Detailansicht · Wiederholen ab Rip oder ab Kompression ·
4K-Schlüsselkette in drei Stufen · Restzeit-Schätzung.
---
## 7. Die Oberfläche
**Entscheid:** Neu bauen, Look behalten.
Wiedererkennbar bleibt die Design-Sprache — dunkel/hell, die Kachel-Anordnung,
das Kino-Banner, die Farbwelt. Neu ist, dass die Oberfläche eine
**Einzelplatz-Anwendung** bedient statt eines Docker-Clusters.
**Was verschwindet:** Worker-Verwaltung · Container-Platte · „Prüfen:
`docker compose ps`" · Speicherziele-Einhängen · Pfad-Map · Worker-Setup-Paket ·
Encoder-Auswahl je Knoten · alles, was einen zweiten Rechner voraussetzt.
**Was dazukommt:**
- Echte **Windows-Ordnerdialoge** statt eines nachgebauten Dateibrowsers im
Web. Das ist ein spürbarer Unterschied im Alltag: Der Nutzer sieht seine
Netzlaufwerke, seine Schnellzugriffe, seine gewohnte Oberfläche.
- **Tray-Symbol mit Zustand** — auf einen Blick: läuft ein Rip, wie weit,
wie lange noch. Rechtsklick: Öffnen, Pause, Beenden.
- **Windows-Benachrichtigungen** (echte Toasts, nicht Browser-Popups), mit
Klick direkt in den Job.
- **Fortschritt in der Taskleiste** — Windows kann einen Fortschrittsbalken
über dem Symbol zeigen. Bei einem 90-Minuten-Rip ist das genau die
Information, die man will, ohne das Fenster zu öffnen.
- **Die Bibliothek** als eigene Ansicht (§ 6.6).
**Was die Oberfläche NIE tut:** Eine Liste auf leer setzen, weil eine Nachricht
ausblieb (R2). Bei einem Aussetzer bleibt der letzte Stand stehen, und ein
Banner sagt, dass gerade nichts Neues kommt.
---
## 8. Setup, Autostart, Deinstallation
**Entscheid: pro Benutzer, ohne Administratorrechte, Windows 10 (ab 1809) und 11, x64.**
| | |
|---|---|
| Installer | `electron-builder` → NSIS, ein `RippySetup.exe` |
| Ziel | `%LOCALAPPDATA%\Programs\Rippy` — kein UAC-Dialog |
| Autostart | `HKCU\Software\Microsoft\Windows\CurrentVersion\Run` |
| Update | `electron-updater`, NSIS-Ziel (§ 6.8) |
| Deinstallation | Über die Windows-Programmliste, räumt **wirklich** auf |
**Warum kein Windows-Dienst:** Ein Dienst startet vor der Anmeldung — klingt
besser, kostet aber bei jeder Installation und jedem Update eine
Administrator-Abfrage, und er kann kein Tray-Symbol zeigen (keine
Benutzersitzung). Für einen Rechner, an dem jemand sitzt und Discs einlegt, ist
der Autostart-Eintrag der richtige Weg.
**Drei Dinge, die aus den alten Fehlern übernommen werden:**
1. **Drüber-Installieren funktioniert.** Rippy läuft fast immer (Autostart) —
der Installer beendet die laufende Fassung, bevor er kopiert. Sonst landet
die neue Version als `Rippy.exe.neu` daneben. *(Genau das ist passiert.)*
2. **Die Deinstallation räumt die WebView-Reste weg.** Beim letzten Mal blieben
**148 Dateien** liegen, weil die Renderer-Hilfsprozesse den Zwischenspeicher
offen hielten — und der Deinstallierer meldete trotzdem „Rippy wurde
entfernt."
3. **Das Aufräum-Skript darf keine Laufwerkswurzel löschen.** Wenn der
Installationsort aus der Registry eine Wurzel wäre, löschte die
Deinstallation das Laufwerk. Nicht beobachtet — aber nicht wiedergutzumachen.
> **SmartScreen — entschieden (Entscheid 5): kein Zertifikat.** Die unsignierte
> EXE bekommt beim ersten Start eine Warnung („Der Computer wurde geschützt");
> über **Weitere Informationen → Trotzdem ausführen** geht es weiter. Das
> gehört in die Anleitung, nicht unter den Teppich — und die Anleitung sagt
> auch, woran man erkennt, dass die Datei aus der richtigen Quelle stammt
> (Größe und Prüfsumme stehen beim Release).
>
> Die Folge für den Update-Weg steht in § 6.8: Ohne Signatur ist der
> Transportweg die einzige Absicherung, also **nur HTTPS**.
---
## 9. Die Werkzeuge
Der Entscheid vom 28.08.2026 gilt unverändert: *„handbrake und makemkv MÜSSEN
mitgeliefert werden oder während des Setups separat installiert werden."* Die
Wege unterscheiden sich, und der Grund ist die **Lizenz**, nicht die
Bequemlichkeit.
| | Weg | Warum |
|---|---|---|
| **HandBrakeCLI** | **mitgeliefert** (~35 MB) | GPL-2 erlaubt die Weitergabe, solange Lizenztext und Quellverweis dabei sind. Damit komprimiert Rippy auch ohne Internet. |
| **FLAC** | **mitgeliefert** (~2 MB) | Xiph-Lizenz (BSD-artig), Weitergabe erlaubt. ⚠️ `flac.exe` braucht `libFLAC.dll` daneben — mit nur der EXE endet jeder Aufruf ohne eine Zeile Ausgabe. |
| **MakeMKV** | **beim Einrichten geholt** | Proprietär — die Lizenz erlaubt Dritten keine Weitergabe. Rippy lädt die offizielle Datei vom Hersteller und startet dessen Installer. |
**Die Bezugskette für MakeMKV** (aus dem 28.08.-Entscheid, unverändert):
eigene Quelle → Hersteller-Seite → Hersteller-Forum → Internet Archive. Mit
zwei Feinheiten, beide gemessen: **Die höchste Versionsnummer gewinnt, nicht
die erste** (das Archiv meldet sonst 1.18.2 statt 1.18.4), und **jeder Versuch
bekommt eine knappe Zeitgrenze** (8 s), weil das Forum in zwei von drei
Abrufen ausfiel.
MakeMKV signiert seinen Installer nicht (`NotSigned`, an der echten Datei
gemessen) — geprüft wird deshalb die Versions-Ressource
(`CompanyName: GuinpinSoft inc`). Das ersetzt keine Signatur, fängt aber ab,
was hier wirklich droht: eine Fehlerseite mit `.exe`-Namen, ein abgebrochener
Download, eine falsche Fassung.
### 9.1 Der Beta-Key — ein Termin, kein Detail
> **MakeMKVs freier Beta-Key ist bis Ende September 2026 gültig. Die Kaufseite
> für die Dauerlizenz ist seit Mai 2026 defekt.**
Das trifft Rippy in etwa vier Wochen, und ohne gültigen Key geht **keine
Blu-ray und keine 4K-Disc** mehr auf (DVDs laufen weiter).
Rippy v5 geht damit so um:
1. **Der Key wird automatisch geholt** (wie heute) — aus dem Forum-Thread, in
dem der Hersteller ihn veröffentlicht.
2. **Rippy zeigt das Ablaufdatum im Dashboard** und warnt **sieben Tage
vorher** — statt dass ein Rip mitten in der Nacht scheitert und niemand
weiß, warum.
3. **Ein abgelaufener Key ist eine klare Meldung**, kein rätselhafter
MakeMKV-Fehlercode.
4. **Ein eingetragener Dauerlizenz-Schlüssel schlägt alles.** Wer eine Lizenz
hat, trägt sie ein und ist raus aus dem Thema.
---
## 10. Was aus dem alten Rippy mitkommt
**Kein Code. Aber jede teuer bezahlte Erkenntnis.**
Das ist der Kern des Grüne-Wiese-Ansatzes: Der Python-Code wird nicht portiert,
aber er ist die beste verfügbare Dokumentation darüber, wie sich MakeMKV,
HandBrake und Windows-Laufwerke wirklich verhalten. Diese Tabelle ist die
Arbeitsliste für den Neubau.
| Herkunft | Was übernommen wird | Wohin |
|----------|--------------------|-------|
| `ripping.py` | TINFO/SINFO/PRGV/MSG-Parser, Kommandobau, `laengster_titel`, `episoden_titel`, Sprach-Zusammenfassung | `rip/parser.ts`, `rip/makemkv.ts` |
| `win_ioctl.py` | Die hergeleiteten Steuercodes samt Herleitung — **in § 3.2 aus Node bestätigt** | `laufwerk/win32.ts` |
| `windows.py`, `winlauf.py` | Zugriffsgründe je Fehlernummer, `CREATE_NO_WINDOW`, Standby verhindern, die Prozess-Leine | `laufwerk/`, `werkzeuge/leine.ts` |
| `caps.py` | Encoder **messen** statt behaupten (`HandBrakeCLI --help` auswerten) | `komprimieren/encoder.ts` |
| `handbrake_aufruf.py` | `--aencoder` (nicht `--audio-codec`), Container aus dem Preset erzwingen, letzte 12 Zeilen puffern, `unknown option`-Erkennung | `komprimieren/handbrake.ts` |
| `medien.py` | Zielstruktur, NFO-Schema, Episoden-Zuordnung per Laufzeit, Medienserver-Refresh | `ablage/` |
| `prescan.py`, `clients/*` | TMDb/TVDb/OMDb/MusicBrainz/Jikan-Abfragen, Zwischenspeicher, Confidence | `metadaten/` |
| `pfade.py` | Laufwerkswurzeln, UNC, `naechster_vorhandener` **ohne Endlosschleife** | `speicher/pfade.ts` |
| `musicbrainz_cd.py`, `audio_cd.py` | DiscID-Rechnung (mit/ohne 150 Frames), 2048-vs-2352-Falle | `rip/audiocd.ts` |
| `presets.py`, `eta.py`, `phasen.py` | Preset-Zuordnung je Disc-Typ, Restzeit, Phasenmodell | `komprimieren/`, `ablauf/` |
| `beschaffen.py`, `katalog.py` | Suchreihenfolge für Werkzeuge, Registry-Auswertung, Ausweichquellen | `werkzeuge/` |
| `AGENTS.md` | Die sechs Regeln aus § 4.3 | Wächter-Tests |
**Die Tests wandern sinngemäß mit.** Von den 977 heutigen Tests ist der größte
Teil reine Textverarbeitung (Parser, Pfade, Kommandobau) — die Testfälle
gelten unverändert, nur die Sprache wechselt. Das ist kein Nebeneffekt,
sondern die Absicherung: Ein neu geschriebener Parser, der dieselben
Testfälle besteht wie der alte, hat dieselben Fallen abgedeckt.
---
## 11. Was aus `main` entfernt wird
**Entscheid 7.** Die Arbeit gehört in den `main`-Branch, nicht hierher — dieser
Abschnitt ist die Arbeitsliste dafür, damit sie nicht aus dem Gedächtnis
gemacht wird.
### 11.1 Der Windows-Anteil in `main` sind DREI Dinge, nicht eins
Diese Unterscheidung ist der ganze Abschnitt. Wer sie überspringt, löscht
entweder zu wenig oder reißt die Docker-Version mit.
**Zuerst in Klartext, ohne Dateinamen:**
> **A ist Rippy. B ist ein Handlanger für die VM. C sind Notizzettel, die
> wegen A eingeklebt wurden.**
**A — „Rippy" auf einem Windows-PC.** Das Programm, das man heute kennt:
`RippySetup.exe`, Doppelklick, Symbol in der Taskleiste, ein Fenster geht auf.
Disc rein, Rippy erkennt sie, rippt, komprimiert, legt ab. Ein vollständiges
Rippy, das alles allein macht. **Genau das wird durch v5 ersetzt.**
**B — „Rippy Worker" auf einem Hilfsrechner.** Ein anderes Programm,
`RippyWorkerSetup.exe`. Es **rippt nichts** und hat keine Bedienoberfläche.
Beim Installieren fragt es drei Dinge: *IP der Rippy-VM? Name dieses Helfers?
Wie viele Kerne?* Danach wartet es. Will die **Docker-Rippy auf der VM** einen
Film komprimieren und ist selbst zu langsam, schickt sie die Arbeit dorthin.
```
VM (Docker-Rippy) Ein Windows-PC
├─ Disc einlesen
├─ rippen
└─ komprimieren? zu langsam ──────► B: „Rippy Worker"
komprimiert und
Ergebnis ◄───────────────────── schickt zurück
```
B läuft nur *zufällig* auf Windows — es gehört zur Docker-Welt.
**C — Notizzettel im Docker-Code.** Keine eigenen Dateien, sondern verstreute
Stellen *mitten in* Dateien, die Docker jeden Tag benutzt, wo jemand schrieb:
„falls wir gerade auf Windows laufen, mach es anders". Eingebaut wurden sie,
damit **A** funktioniert.
**Der Unterschied zwischen A und C ist nicht der Inhalt, sondern der Ort:**
| | Bild | Aufräumen heißt |
|---|------|-----------------|
| **A** | Ganze Ordner, auf denen „Windows" steht. Docker schlägt sie nie auf. | **Ordner wegwerfen.** Ganze Dateien löschen, fertig. |
| **C** | Einzelne Seiten mit Windows-Notizen, die in Ordnern liegen, die Docker täglich benutzt. | **Seiten einzeln durchgehen.** Die Datei bleibt, ein paar Zeilen gehen — und bei jeder Zeile ist zu prüfen, ob wirklich nur A sie brauchte. |
Deshalb ist C der heikle Teil: Erwischt man eine Zeile, die Docker doch
braucht, geht auf der VM etwas kaputt. Und manche Windows-Zeile braucht sogar
**B** weiter — etwa die Regel, dass eine Laufwerkswurzel `F:\` mit Trenner
geschrieben werden muss (§ 1). Der Handlanger läuft ja auf Windows.
**In Dateien ausgedrückt:**
| | Was es ist | Schicksal |
|---|---|---|
| **A** | **Die Standalone-App** — PyInstaller-Setup, Tray, WebView2-Fenster, Einrichtungs-Assistent, lokale Queue, Werkzeug-Beschaffung, Win32-Treiber | **weg** — v5 ersetzt sie |
| **B** | **Der Remote-Encode-Worker**`deploy/worker-windows/`, `/worker-setup/*`, Pfad-Map | **bleibt** |
| **C** | **Windows-Zweige im Docker-Code** — die `os.name == "nt"`-Stellen in `main.py`, `betrieb.py`, `ablauf.py`, `rohdaten.py`, dazu die Fähigkeiten-Auskunft im UI | **einzeln prüfen** |
> **Entscheid 8 (30.08.2026) — B bleibt bei Docker und kommt nicht nach v5.**
>
> Commander: *„Das soll NICHT Teil des neuen Standalone-Produktes sein, da
> dieser NUR für die Docker-Variante verwendet wird."*
>
> Damit ist bestätigt, was zuvor nur Auslegung war: Der Remote-Worker ist kein
> Standalone-Rippy, sondern eine **Funktion der Docker-Installation**. Er wird
> weder abgeräumt (er ist Docker, und Docker bleibt unangetastet) noch nach v5
> übernommen (v5 ist Einzelplatz, Entscheid 3 — es gibt dort keinen zweiten
> Knoten, dem man etwas schicken könnte).
### 11.2 Teil A — die Standalone-App (weg)
```
src/rippy/windows_app.py Installer, Dienst, Deinstallation
src/rippy/fenster.py WebView2-Fenster
src/rippy/setup_fenster.py Einrichtungs-Assistent
src/rippy/einrichtung.py Voraussetzungs-Prüfungen
src/rippy/standalone.py lokale Zustellung statt Celery
src/rippy/drives/windows.py Win32-Laufwerkstreiber
src/rippy/drives/win_ioctl.py die Steuercodes
src/rippy/platform/winlauf.py CREATE_NO_WINDOW, Waisen, Job Object
src/rippy/platform/win_registry.py
src/rippy/platform/verknuepfungen.py
src/rippy/platform/dateiangaben.py
src/rippy/tools/ Werkzeug-Katalog + Beschaffung (Windows-only)
src/rippy/rip/audio_cd.py Audio-CD über Win32
src/rippy/rip/musicbrainz_cd.py DiscID-Rechnung
packaging/windows/ der PyInstaller-Bau
dist/windows/ die gebaute EXE + vendor/
+ die zugehörigen test_*.py
```
Dazu: `src/rippy/queue/lokal.py` und `queue/laeufer.py` (die lokale Queue hat
außerhalb der Standalone-App keinen Aufrufer) und `bus/waechter.py`, soweit er
nur die Windows-Disc-Wache bedient.
**Die Steuercodes und Parser sterben nicht — sie sind bereits nach v5
übernommen** (§ 3.2 hat sie aus Node bestätigt, § 10 listet den Rest). Was
gelöscht wird, ist die Python-Fassung, nicht das Wissen.
### 11.3 Teil C — die verstreuten Zweige (einzeln prüfen)
Hier ist blindes Löschen gefährlich, weil manches auch **ohne** die
Standalone-App gebraucht wird — vom Remote-Worker (Teil B) oder von der
Pfad-Behandlung allgemein.
| Fundort | Prüfen |
|---------|--------|
| `docker/api/main.py``betrieb.windows_laufwerke()`, die Browse-Sonderfälle, `/betrieb`-Fähigkeiten | Die Fähigkeiten-Auskunft bleibt sinnvoll (Docker-AiO vs. verteilt), nur die Windows-Werte fallen weg |
| `src/rippy/betrieb.py` | Ganz raus oder auf Docker-Fälle eindampfen |
| `docker/worker/ablauf.py`, `rohdaten.py` | Die Windows-Pfadzweige raus — aber `nativ_nachsehen()` prüfen: gilt es auch für den Remote-Worker? |
| `docker/api/rohdaten.py`, `mounts.py` | dito |
| `src/rippy/pfade.py` | **Bleibt.** Laufwerkswurzeln und UNC braucht auch der Remote-Worker |
| UI: `useBetrieb`, `WorkerVerwaltung`, `Anleitung`, `Settings` | Nur die Standalone-Texte raus; die Worker-Verwaltung bedient Teil B |
| `src/rippy/test_keine_container_reste.py` | Der Wächter-Test wird gegenstandslos — mit weg |
### 11.4 Wie das gemacht wird
1. **Ein eigener Branch**, nicht direkt auf `main`. Der Umfang ist zu groß für
einen Freihand-Commit.
2. **In der Reihenfolge A → C → (B bleibt).** Erst die eindeutigen Dateien,
dann die verstreuten Zweige — sonst sucht man Aufrufer, die es noch gibt.
3. **Die Ampel nach jedem Schritt.** `AGENTS.md` Regel A gilt unverändert; ein
Aufräumen, das die Tests rot macht, ist nicht fertig, sondern angefangen.
4. **Erst wenn v5 die Etappe W-6 erreicht hat.** Solange v5 nicht installierbar
ist, ist die alte Fassung das einzige Windows-Rippy, das es gibt. Sie zu
löschen, bevor der Ersatz steht, wäre eine Lücke ohne Gegenwert — der
Entscheid nennt kein Datum, nur die Richtung.
5. **Ein SAVEPOINT-Eintrag** dazu, mit der Zahl der entfernten Zeilen und dem
Hinweis, wo das Wissen jetzt liegt.
---
## 12. Etappen
**Grundregel:** Jede Etappe endet mit grüner Ampel und einem Programm, das man
starten kann. Kein großer Umbau, nach dem monatelang nichts geht.
| # | Titel | Inhalt | Fertig, wenn |
|---|-------|--------|--------------|
| **W-0** | **Gerüst** | Electron 44 + TypeScript + Vite + Vitest, drei Prozesse, IPC, `node:sqlite` in Electron **prüfen** (§ 3.4), Prozess-Leine, CI-Ampel | Ein Fenster geht auf, der Kern läuft, ein Testlauf ist grün |
| **W-1** | **Laufwerk** | `laufwerk/win32.ts` aus dem § 3.2-Beweis, Wache über `WM_DEVICECHANGE`, Disc-Typ, Verriegeln, Auswerfen mit Nachsehen | Disc rein → Rippy sagt korrekt, was es ist. Auswerfen wirkt (nachgemessen, nicht geglaubt) |
| **W-2** | **Rippen** | makemkvcon-Aufruf, Parser, Fortschritt, Abbruch, Lesefehler-Warnung, Werkzeug-Beschaffung | Eine DVD und eine Blu-ray laufen durch, verlustfrei |
| **W-3** | **Komprimieren + Ablegen** | HandBrake, Encoder gemessen, Presets, Zielstruktur, NFO, Poster, Medienserver | Ein Film liegt fertig in Jellyfin und ist dort sichtbar |
| **W-4** | **Metadaten-Automatik** | Die vier Quellen aus § 5, Sicherheitsgrad, Nachkorrektur | Fünf verschiedene Discs richtig erkannt, ohne Nachfrage |
| **W-5** | **Oberfläche** | Dashboard, Einstellungen, Logs, Bibliothek, Tray, Windows-Toasts, Taskleisten-Fortschritt | Der Commander bedient Rippy einen Abend lang ohne Rückfrage |
| **W-6** | **Setup + Update** | NSIS, Autostart, Erst-Einrichtung, Selbst-Update, Deinstallation | Frischer Rechner: Setup → Disc rein → MKV raus. Und: drüber-installieren geht |
| **W-7** | **Der Rest** | Audio-CD, ISO-Backup, mehrere Laufwerke, 4K-Schlüsselkette | Je Feature ein Nachweis |
Reihenfolge-Begründung: W-1 bis W-3 sind die Kette, ohne die Rippy nichts tut.
W-4 und W-5 machen sie bequem. W-6 macht sie weitergebbar. W-7 sind die
Ergänzungen, die ohne die Kette keinen Sinn hätten.
---
## 13. Risiken und offene Punkte
| Risiko | Bewertung | Behandlung |
|--------|-----------|------------|
| **MakeMKV-Beta-Key läuft Ende September 2026 ab** | hoch × sicher | § 9.1 — Ablaufdatum sichtbar, Warnung 7 Tage vorher, klare Meldung, Dauerlizenz eintragbar. **Das ist der akuteste Punkt im ganzen Dokument.** |
| `node:sqlite` in Electron ungeprüft | mittel × unbekannt | Erster Handgriff in W-0. Rückfallebene `better-sqlite3` |
| Neubau dauert länger als eine Reparatur | sicher | Deshalb Entscheid 2: Die Docker-Version läuft weiter. Es entsteht keine Lücke |
| Electron-Paket ~150200 MB | sicher, gering | Ein einmaliger Download. Der Gegenwert ist eine Werkzeugkette statt zweier |
| SmartScreen bei unsignierter EXE | mittel | Entscheid 5: kein Zertifikat. Anleitung + Prüfsumme beim Release |
| **Update ohne Signaturprüfung** | mittel × Folge von Entscheid 5 | § 6.8 — nur HTTPS, `http://` wird abgelehnt. Der Transportweg ist die einzige Absicherung, deshalb darf er nicht optional sein |
| **Öffentliche Update-Quelle für Externe** | offen × Netz-Frage | Entscheid 6 — Gitea liegt im LAN. Rippy ist für alle drei Wege gebaut (Adresse in der Konfiguration); die Wahl ist eine Infrastruktur-Entscheidung, keine Programmänderung |
| **Aufräumen von `main` reißt Docker mit** | mittel × vermeidbar | § 11 — drei Teile sauber getrennt, Remote-Worker bleibt, Ampel nach jedem Schritt, und erst **nach** v5 W-6 |
| koffi + Electron + npm-Install-Sperre | niedrig | § 3.5 gemessen: koffi lädt mit vorgebauten Binärdateien. In W-0 im Electron-Kontext gegenprüfen |
| Parallel-Rip vs. MakeMKV-Datenverzeichnis | mittel | § 6.5 — **messen, bevor die Grenze festgeschrieben wird** |
| Ein neu geschriebener Parser bringt neue Fehler | mittel | Die alten Testfälle wandern mit (§ 10). Ein Parser, der sie besteht, kennt dieselben Fallen |
| 4K-UHD-Schlüssel | gelöst | Unter Windows holt makemkvcon sie selbst — der strukturelle Vorteil dieser Plattform, gemessen 25.07.2026 |
### Alle drei offenen Punkte sind entschieden (30.08.2026)
Code-Signing: **nein** (Entscheid 5) · Update-Quelle: **Gitea, auch für
Externe** (Entscheid 6) · alter Windows-Weg: **wird entfernt** (Entscheid 7) ·
der Remote-Worker bleibt bei Docker (Entscheid 8, § 11.1). Was daraus an
Arbeit folgt, steht in § 11.
---
## 14. Was NICHT gebaut wird
- **Kein Python.** Auch nicht „nur für den einen Parser".
- **Kein HTTP-Server, kein Port, kein localhost.** Fenster und Kern reden über
IPC.
- **Keine Docker-Kompatibilität.** Kein geteiltes Modul, kein gemeinsames
Schema, keine Container-Pfade.
- **Kein Linux, kein macOS.** „100 % für Windows gebaut" heißt: Wo eine
Windows-Lösung besser ist als eine portable, gewinnt die Windows-Lösung.
- **Kein ARM-Fork.** Rippy bleibt ein Eigenbau.
- **Keine Auth.** Einzelplatz, kein Netzdienst — es gibt nichts zu schützen.
---
## Anhang — Verhältnis zu den bestehenden Konzepten
`KONZEPT.md` (v1) und `KONZEPT-V2.md` (v2) **bleiben gültig für die
Docker-Version**. Dieses Dokument ersetzt sie nicht, es steht daneben.
Drei Punkte aus v2 werden für Windows bewusst anders entschieden — jeweils mit
Begründung:
| v2 sagt | v5 macht | Warum |
|---------|----------|-------|
| Entscheid 4: *kein Electron, pywebview + WebView2* (8 MB statt 150 MB) | **Electron** | Die Rechnung war richtig und die Schlussfolgerung trotzdem falsch: Sie hat nur die Fenster-Frage betrachtet. Der Preis für pywebview war nicht 0 MB, sondern **eine zweite Werkzeugkette** — PyInstaller, hidden imports, `pythonnet`, und ein Kern, der aus Container-Code bestand. Electron kostet 150 MB und schenkt uns *eine* Sprache von der Oberfläche bis zum Laufwerk. |
| Eine Anwendung, drei Verdrahtungen (Ports/Treiber) | **Ein Windows-Programm** | Die Ports-Architektur ist richtig für ein Produkt, das drei Betriebsarten tragen muss. Bei Entscheid 2 (Docker bleibt getrennt) trägt sie nichts mehr — sie wäre Abstraktion auf Vorrat. |
| REST + SSE als Schnittstelle | **IPC** | Ein lokaler Webserver in einer Einzelplatz-Anwendung ist eine offene Tür ohne Gegenwert. |
---
*Rippy v5 · Konzept · 30.08.2026 · Branch `worktree-windows-electron`*