diff --git a/KONZEPT-WINDOWS.md b/KONZEPT-WINDOWS.md index 0fd06a0..0ab794a 100644 --- a/KONZEPT-WINDOWS.md +++ b/KONZEPT-WINDOWS.md @@ -1,771 +1,939 @@ -# 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? | -| 4–5 | Wie ist das Programm aufgebaut, und was passiert, wenn eine Disc reingeht? | -| 6–9 | Features, Oberfläche, Setup, Werkzeuge | -| 10 | Was kommt aus dem alten Rippy mit? | -| 11–13 | 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 ` 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) - -Vier Fragen wurden gestellt, vier sind beantwortet. 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. - ---- - -## 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 30–90 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; 6–13 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 2–4) - -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. 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 mit dem Gitea des -Commanders. Ein nicht erreichbarer Update-Server ist kein Fehler, sondern eine -Zeile im Protokoll. - -### 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.** Eine unsignierte EXE bekommt beim ersten Start eine -> Warnung („Der Computer wurde geschützt"). Ein Code-Signing-Zertifikat kostet -> etwa 200–400 € im Jahr. Für den Eigengebrauch ist die Warnung hinnehmbar; -> das gehört in die Anleitung, nicht unter den Teppich. **Offener Punkt für den -> Commander.** - ---- - -## 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. 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. - ---- - -## 12. 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 ~150–200 MB | sicher, gering | Ein einmaliger Download. Der Gegenwert ist eine Werkzeugkette statt zweier | -| SmartScreen bei unsignierter EXE | mittel | § 8 — Anleitung, oder Zertifikat. **Entscheidung offen** | -| 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 | - -### Offene Punkte für den Commander - -1. **Code-Signing-Zertifikat** — ja oder nein? (~200–400 €/Jahr, sonst - SmartScreen-Warnung beim ersten Start) -2. **Update-Quelle** — Gitea-Releases auf `192.168.178.153`, oder ein Ordner - im Netz? -3. **Der alte Windows-Weg** — bleibt `RippySetup.exe` (PyInstaller) parallel - installierbar, bis v5 W-6 erreicht hat? *(Empfehlung: ja, und v5 bekommt - einen eigenen Installationsordner, damit sich die beiden nicht überschreiben.)* - ---- - -## 13. 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`* +# 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? | +| 4–5 | Wie ist das Programm aufgebaut, und was passiert, wenn eine Disc reingeht? | +| 6–9 | Features, Oberfläche, Setup, Werkzeuge | +| 10 | Was kommt aus dem alten Rippy mit? | +| 11 | Was aus `main` entfernt wird | +| 12–14 | 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 ` 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) + +Sieben Entscheide, alle am selben Tag. Die ersten vier legen die Architektur +fest, die letzten drei beantworten die Punkte, die das Konzept offengelassen +hatte. 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 30–90 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; 6–13 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 2–4) + +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 + +Das ist die Unterscheidung, an der ein unvorsichtiges Aufräumen scheitern +würde: + +| | Was es ist | Schicksal | +|---|---|---| +| **A** | **Die Standalone-App** — PyInstaller-Setup, Tray, WebView2-Fenster, Einrichtungs-Assistent, lokale Queue, Werkzeug-Beschaffung, Win32-Treiber | **weg** — sie wird durch v5 ersetzt | +| **B** | **Der Remote-Encode-Worker** — ein Windows-PC als Encoder-Knoten *für die Docker-Installation* (`deploy/worker-windows/`, `/worker-setup/*`, Pfad-Map) | **bleibt** — siehe Kasten | +| **C** | **Windows-Anpassungen im Docker-Code** — die `os.name == "nt"`-Zweige in `main.py`, `betrieb.py`, `ablauf.py`, `rohdaten.py` und die Fähigkeiten-Auskunft im UI | **einzeln prüfen** — der heikelste Teil | + +> **Warum B bleibt:** Der Remote-Worker ist kein Standalone-Rippy, sondern eine +> **Funktion der Docker-Installation** — ein zweiter Rechner nimmt der VM das +> Komprimieren ab. Entscheid 2 sagt, dass die Docker-Version unangetastet +> bleibt; ihn mit abzuräumen würde der VM eine Fähigkeit nehmen, die heute +> funktioniert und die niemand gekündigt hat. +> +> Das ist eine **Auslegung** des Entscheids, keine Anweisung des Commanders. +> Wenn der Remote-Worker auch weg soll, ist das ein Satz — aber dann bewusst +> und nicht als Nebenwirkung. + +### 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 ~150–200 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). +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`*