diff --git a/KONZEPT-WINDOWS.md b/KONZEPT-WINDOWS.md index 68b9f51..51b6a9f 100644 --- a/KONZEPT-WINDOWS.md +++ b/KONZEPT-WINDOWS.md @@ -1,987 +1,1122 @@ -# 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) - -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 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 - -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 ~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) · -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`* +# 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, **zweite Fassung** · Branch `worktree-windows-electron` + +**Was die zweite Fassung vom selben Tag unterscheidet:** Das Konzept wurde +geprüft, tagesaktuell gegenrecherchiert und nachgemessen. Vier Dinge haben +sich geändert: (1) Ein Faktenfehler ist korrigiert — das Gitea des Commanders +ist entgegen der ersten Fassung **öffentlich erreichbar**, die Update-Frage +löst sich damit fast von selbst (§ 3.6, Entscheid 6). (2) Die letzte +technische Unbekannte ist gemessen — `node:sqlite` funktioniert in Electron 44, +im echten Einsatzkontext (§ 3.4). (3) Die Disc-Erkennung ist vom Kopf auf die +Füße gestellt — der Prüf-Takt ist Plan A, das Windows-Ereignis die spätere +Verfeinerung (§ 5). (4) Drei Entscheide des Commanders sind eingearbeitet: +Update-Quelle, Zielgruppe, und der Zeitpunkt des Aufräumens (Entscheide 6, 7 +und 9). + +--- + +## 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`), am 30.08.2026 gegen das +Netz geprüft 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. Die erste +Fassung dieses Konzepts hat es selbst vorgemacht: Sie behauptete, Gitea sei +von außen nicht erreichbar — eine Messung von dreißig Sekunden zeigte das +Gegenteil (§ 3.6). + +--- + +## 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) + +Neun Entscheide, alle am selben Tag — die Entscheide 6, 7 und 9 in ihrer +heutigen Form aus der Prüfung der ersten Fassung. 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. Zwei Folgen, beide bewusst getragen: + +1. **SmartScreen.** Beim ersten Start eines Downloads zeigt Windows den + 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 — samt der Angabe, woran man die echte Datei erkennt + (Größe und Prüfsumme stehen beim Release). +2. **Updates ohne Echtheitsprüfung.** Ohne Signatur kann `electron-updater` + ein heruntergeladenes Update nicht auf Echtheit prüfen. Die Absicherung + liegt damit vollständig beim Transportweg — deshalb die HTTPS-Pflicht in + § 6.8, Regel 1. + +### Entscheid 6 — Updates aus dem öffentlichen Gitea + +*„Gerne das Gitea Release, mit Möglichkeit dass auch EXTERNE diese Updates +fahren können"* — und in der zweiten Fassung konkretisiert: **das öffentliche +Gitea direkt, kein Spiegel.** + +Die erste Fassung hielt das für ein Infrastruktur-Problem („Gitea ist von +außen nicht erreichbar") und entwarf drei Auswege. Die Messung in § 3.6 hat +das erledigt: **Gitea ist längst öffentlich erreichbar** — +`https://git.tobisniceshomelab.ddnsfree.com`, mit gültigem TLS-Zertifikat, +Lesen ohne Anmeldung. Die HTTPS-Pflicht aus § 6.8 ist damit erfüllbar, und +zwar für den Commander **und** für Externe, ohne dass irgendetwas neu +aufgebaut wird. + +Was bleibt: + +- Die Update-Adresse steht in der Konfiguration, vorbelegt mit den Releases + dieses Gitea. Wer einen eigenen Server hat, trägt seine eigene Adresse ein — + `electron-updater` braucht nur einen statischen HTTPS-Ort. +- Jedes Release besteht aus **drei Dateien**, nicht zwei: `latest.yml` + (die Update-Auskunft), das Setup und die `.blockmap` (macht aus einem + Voll-Download einen Differenz-Download). +- ⚠️ **Ein Handgriff für Etappe W-6:** Gitea-Release-Downloads hängen an + ihrem Tag (`/releases/download/v5.0.1/...`) — `electron-updater` braucht + für `latest.yml` aber eine **feste** Adresse. Ob Gitea 1.27 das + GitHub-Muster `releases/latest/download/...` bedient, ist ungeprüft (2024 + war es dort noch ein offener Wunsch). Falls nein, ist die Rückfallebene + simpel: ein festes Update-Tag, dessen drei Dateien der Bau bei jedem + Release ersetzt. Beides ist eine Bau-Einstellung, keine Programmänderung. + +### Entscheid 7 — Der alte Windows-Weg wird entfernt, sofort, 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. Die erste Fassung hatte das Aufräumen auf „erst wenn v5 +installierbar ist" gelegt; der Commander hat bei der Prüfung der zweiten +Fassung anders entschieden: **sofort, nicht erst nach Etappe W-6.** + +Was das bedeutet, gehört ehrlich hingeschrieben: Zwischen dem Aufräumen und +W-6 liefert das Repo **kein installierbares Windows-Rippy**. Die auf dem +Commander-PC installierte rc11-Fassung läuft lokal weiter, bekommt aber keine +Reparaturen mehr; wer in der Zwischenzeit rippen will, nutzt sie oder die +Docker-Version. Das ist der Preis für ein Repo ohne Zwitter — und er ist +bewusst bezahlt. + +**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. + +### Entscheid 8 — Der Remote-Worker bleibt bei Docker + +*„Das soll NICHT Teil des neuen Standalone-Produktes sein, da dieser NUR für +die Docker-Variante verwendet wird."* + +Der „Rippy Worker" auf einem Hilfsrechner ist eine Funktion der +Docker-Installation, kein Standalone-Rippy. Er wird weder abgeräumt noch nach +v5 übernommen. Warum das so ist und was genau er tut, steht in § 11.1 — dort, +wo die Unterscheidung gebraucht wird. + +### Entscheid 9 — Zielgruppe: Bekannte mit Link + +Neu in der zweiten Fassung: Rippy v5 wird **kein öffentliches Produkt**. Der +Commander gibt die Download-Adresse gezielt weiter — Familie, Freunde. Keine +öffentliche Ankündigung, kein Support-Anspruch Fremder. + +Konsequenzen: + +- Anleitung, SmartScreen-Erklärung und Prüfsummen bleiben **schlank** — sie + müssen einen wohlwollenden Bekannten durchbringen, kein anonymes Publikum. +- Die **Lizenztexte gehören trotzdem dazu.** Weitergabe an Bekannte ist + Weitergabe: HandBrake (GPL-2) und FLAC (BSD-artig) verlangen Lizenztext und + Quellverweis im Paket (§ 9). Das ist keine Formalie für später, sondern + Bestandteil von Etappe W-6. +- Das Repo bleibt lesbar wie bisher; beworben wird nichts. Sollte v5 eines + Tages doch öffentlich werden, ändert sich am Programm nichts — nur an + Anleitung und Sorgfaltstiefe. + +--- + +## 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. Die Skripte liegen in +`beweise/` und sind dort nachstellbar dokumentiert. + +### 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) +``` + +Zur Einordnung, am 30.08.2026 gegen das Netz geprüft: Electron 44 ist die +aktuelle stabile Linie, koffi 3.1.6 die aktuelle Version, Node 24 ist LTS. +Das Konzept baut also nicht auf Auslaufendem auf — was Electron-Versionen +betrifft, gilt aber die Pflege-Regel in § 6.8. + +### 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 — in Electron gemessen + +Node 24 bringt SQLite mit (`node:sqlite`, `DatabaseSync`). Keine +`better-sqlite3`-Kompilierung, kein `node-gyp`, kein Visual-Studio-Build in der +Bau-Kette. + +Die erste Fassung musste hier eine Warnung setzen: Electron kompiliert sein +Node mit eigenen Flags, und ob `node:sqlite` dort vorhanden ist, war nur für +Node pur gemessen. **Das ist erledigt — am 30.08.2026 in Electron 44.0.0 +gemessen, und zwar im `utilityProcess`**, also exakt dem Kontext, in dem +`kern/speicher/db.ts` laufen wird (§ 4.1): + +``` +KERN-ANTWORT: {"ok":true,"wert":"geht","node":"24.18.1"} +Kern beendet mit 0 +``` + +Anlegen, Schreiben, Lesen — alles grün. Die Rückfallebene `better-sqlite3` +wird nicht gebraucht. Skripte: `beweise/sqlite-main.js` + `sqlite-kern.js`. + +### 3.5 Ein Nebenbefund für die Bau-Kette — zweimal bestätigt + +`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. **Electron +selbst trifft die Sperre härter:** Sein Install-Skript lädt die eigentliche +Programmdatei herunter; wird es geblockt, liegt nach `npm install` ein Paket +**ohne `dist/electron.exe`** da, und nichts startet. Bei der sqlite-Messung +(§ 3.4) selbst hineingelaufen; `node node_modules/electron/install.js` holt +die Binary nach. Beides gehört in die Bau-Anleitung, sonst sucht jemand eines +Tages an der falschen Stelle. + +### 3.6 Die Update-Quelle — gemessen statt vermutet + +Die erste Fassung behauptete: *„Gitea läuft auf `192.168.178.153` im Heimnetz. +Von außen ist das nicht erreichbar."* Am 30.08.2026 von außerhalb des +Heimnetzes gemessen: + +``` +https://git.tobisniceshomelab.ddnsfree.com/Hitonabi/rippy + -> lädt anonym, ohne Anmeldung, über den Reverse-Proxy des Commanders + -> TLS-Zertifikat gültig + -> Gitea 1.27.2 (/api/v1/version) + -> bisher 0 Releases, 0 Tags — der Release-Weg ist frei +``` + +Die interne Adresse `192.168.178.153:3000` existiert daneben weiter; für +Updates zählt die öffentliche. Damit ist Entscheid 6 keine Infrastruktur-Frage +mehr, sondern eine Bau-Einstellung. Offen bleibt nur die +Fest-Adresse-Feinheit für `latest.yml` (Entscheid 6, ⚠️-Punkt) — die wird in +W-6 am ersten echten Release gemessen. + +--- + +## 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. Und § 3.4 hat gezeigt: `node:sqlite` steht genau +dort zur Verfügung. + +**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 (§ 5, Schritt 1) +│ │ │ └── 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 +│ │ │ ├── schluessel.ts 4K-Schlüsselkette, Beta-Key samt Ablaufdatum (§ 9.1) +│ │ │ └── leine.ts Job Object (§ 3.3) +│ │ └── speicher/ +│ │ ├── db.ts SQLite (node:sqlite — § 3.4 gemessen) +│ │ ├── 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 + │ Wache im 3-Sekunden-Takt (IOCTL_STORAGE_CHECK_VERIFY2 — § 3.2) + │ Verfeinerung später: WM_DEVICECHANGE-Ereignis (siehe unten) + ▼ + 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 + ▼ +``` + +**Warum der Takt Plan A ist und das Ereignis nicht.** Die erste Fassung +versprach „Ereignis, kein Poll" — die Prüfung hat die Reihenfolge umgedreht. +`WM_DEVICECHANGE` ist eine **Fenster**-Nachricht: Sie wird nur an Fenster mit +Nachrichtenschleife zugestellt, und der Kern hat keines. Beide Auswege sind +machbar, aber krumm — entweder fängt der Main-Prozess die Nachricht am +Fenster ab und muss dann die Frage „welches Laufwerk?" aus einer +Zeiger-Struktur klauben, oder der Kern baut sich per koffi ein unsichtbares +Nachrichten-Fenster samt eigener Schleife. Der 3-Sekunden-Takt dagegen ist +eine Zeile (`IOCTL_STORAGE_CHECK_VERIFY2`, in § 3.2 gemessen, praktisch +kostenlos), und bei einem Vorgang, der 30–90 Minuten dauert, sind drei +Sekunden Erkennungsverzögerung bedeutungslos. Also: **Takt zuerst, Ereignis +als spätere Verfeinerung** — und wenn das Ereignis kommt, bleibt der Takt als +Rückfallebene bestehen, falls die Nachricht ausbleibt. + +**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 öffentlichen 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 — das gilt +ausdrücklich auch, wenn das Heimnetz des Commanders gerade nicht antwortet. + +**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. (Die Prüfsumme aus `latest.yml` sichert + nur die Übertragung — sie kommt über denselben Kanal wie die Datei und ist + deshalb kein Ersatz für den sicheren Kanal selbst.) +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. + +**Und eine Pflege-Regel, die das Selbst-Update erst wertvoll macht:** +Electron bringt etwa alle acht Wochen eine neue Hauptversion und pflegt nur +die letzten drei — wer stehen bleibt, sitzt nach rund einem halben Jahr auf +einem Chromium mit bekannten Sicherheitslücken. Deshalb: **Bei jedem +Rippy-Release wird geprüft, ob ein Electron-Sprung ansteht.** Das Verteilen +kostet dank Selbst-Update nichts; das Nicht-Verteilen kostet irgendwann +Vertrauen. + +### 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 | + +Zu Windows 10, tagesaktuell eingeordnet: Die Verbraucher-Sicherheitsupdates +(ESU) enden im Oktober 2026, die Chromium-Familie läuft auf Windows 10 aber +erklärtermaßen weiter (Edge/WebView2 bis mindestens Oktober 2028). Electron 44 +läuft dort. Für Rippy heißt das: bauen ja, und ein Satz in der Anleitung, dass +Windows 10 selbst ausläuft — mehr nicht. + +**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. + +Zum SmartScreen-Hinweis beim ersten Start und zur Update-Folge der fehlenden +Signatur: beides steht bei Entscheid 5 — die Anleitung erklärt den Weg, das +Release nennt Größe und Prüfsumme, und Updates laufen nur über HTTPS (§ 6.8). + +--- + +## 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 Lizenztexte und Quellverweise für HandBrake und FLAC liegen dem Setup bei +— wegen Entscheid 9 wird die EXE weitergegeben, und damit ist das Pflicht, +nicht Kür. + +**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 defekt — der Hersteller verweist selbst auf den +> Beta-Key.** *(Am 30.08.2026 gegen Forum und Fachpresse geprüft.)* + +Zur Einordnung, die in der ersten Fassung fehlte: Das ist **kein frischer +Unfall, sondern ein Dauerzustand seit Sommer 2025** — das Kaufsystem fällt +seither immer wieder aus, und der Beta-Key wurde bisher stets kurz vor Ablauf +verlängert (zuletzt Anfang August 2026, gültig bis Ende September). Rippy +plant also nicht für eine einmalige Panne, sondern für einen Rhythmus. Das +Restrisiko — der Hersteller verschwindet ganz — kann kein Programm der Welt +auffangen; was ein Programm auffangen kann, ist das Überraschtwerden: + +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. Ohne gültigen Key geht keine Blu-ray und keine 4K-Disc mehr + auf; DVDs laufen weiter. +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` | +| `makemkv_daten.py` | Die 4K-Schlüsselkette: Beta-Key-Abruf aus dem Forum-Thread, Schlüsselspeicher (`_private_data.tar`), KEYDB als Notnagel — samt der dort dokumentierten Fehldiagnosen | `werkzeuge/schluessel.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 — und zwar sofort.** 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. **Entscheid 8: +B bleibt bei Docker** — es wird weder abgeräumt (es 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). + +**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** | + +### 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. Wichtig fürs +Löschen von `makemkv_daten.py`-Verwandten: Die Schlüsselketten-Erkenntnisse +(§ 10) VOR dem Löschen in v5-Notizen sichern, falls W-7 noch nicht begonnen +hat — das Wissen darf nicht nur im Git-Verlauf liegen. + +### 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. **Jetzt, nicht erst wenn v5 steht.** Commander-Entscheid vom 30.08.2026 + (zweite Fassung): Das Aufräumen wartet **nicht** auf Etappe W-6. Die + Konsequenz steht bei Entscheid 7 — zwischen Aufräumen und W-6 liefert das + Repo kein installierbares Windows-Rippy; die installierte rc11-Fassung + läuft lokal weiter, bekommt aber keine Reparaturen mehr. +2. **Ein eigener Branch**, nicht direkt auf `main`. Der Umfang ist zu groß für + einen Freihand-Commit. +3. **In der Reihenfolge A → C → (B bleibt).** Erst die eindeutigen Dateien, + dann die verstreuten Zweige — sonst sucht man Aufrufer, die es noch gibt. +4. **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. +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. + +**Parallel dazu, unabhängig von den Etappen:** das Aufräumen von `main` +(§ 11.4) — es startet sofort und braucht auf nichts hiervon zu warten. + +| # | Titel | Inhalt | Fertig, wenn | +|---|-------|--------|--------------| +| **W-0** | **Gerüst** | Electron 44 + TypeScript + Vite + Vitest, drei Prozesse, IPC, `node:sqlite` anbinden (§ 3.4 — bereits bewiesen), Prozess-Leine, CI-Ampel, npm-Skript-Sperre in der Bau-Anleitung (§ 3.5) | 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 im 3-Sekunden-Takt (§ 5), 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 gegen das öffentliche Gitea (inkl. Messung der Fest-Adresse für `latest.yml`, Entscheid 6), Lizenztexte beilegen (Entscheid 9), 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 (`werkzeuge/schluessel.ts`), WM_DEVICECHANGE als Wache-Verfeinerung (§ 5) | 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. Einordnung: Dauerzustand seit Sommer 2025, der Key wurde bisher stets verlängert. **Bleibt der akuteste Termin im Dokument.** | +| **Kein installierbares Windows-Rippy zwischen Aufräumen und W-6** | sicher × akzeptiert | Folge von Entscheid 7 („sofort"). Die installierte rc11-EXE läuft lokal weiter; die Docker-Version deckt die VM ab. Bewusst bezahlter Preis | +| Neubau dauert länger als eine Reparatur | sicher | Deshalb Entscheid 2: Die Docker-Version läuft weiter. Es entsteht keine Lücke auf der VM | +| Electron-Paket ~150–200 MB | sicher, gering | Ein einmaliger Download. Der Gegenwert ist eine Werkzeugkette statt zweier | +| **Electron altert schnell** (alle ~8 Wochen neue Hauptversion, nur 3 gepflegt) | sicher × planbar | Pflege-Regel in § 6.8: bei jedem Rippy-Release den Electron-Sprung prüfen. Das Selbst-Update macht das Verteilen billig | +| SmartScreen bei unsignierter EXE | mittel | Entscheid 5: kein Zertifikat. Anleitung + Prüfsumme beim Release — reicht für die Zielgruppe aus Entscheid 9 | +| **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 | +| **Feste `latest.yml`-Adresse im Gitea** | niedrig × messbar | Entscheid 6, ⚠️-Punkt — in W-6 am ersten Release messen; Rückfallebene festes Update-Tag. Eine Bau-Einstellung, keine Programmänderung | +| Updates hängen am Heimnetz des Commanders | niedrig | § 6.8 Regel 3 — ein stummer Server ist eine Protokollzeile, kein Fehler. Wer eine eigene Quelle will, trägt sie ein | +| **Aufräumen von `main` reißt Docker mit** | mittel × vermeidbar | § 11 — drei Teile sauber getrennt, Remote-Worker bleibt, eigener Branch, Ampel nach jedem Schritt | +| koffi + Electron + npm-Install-Sperre | niedrig | § 3.5 gemessen: koffi lädt mit vorgebauten Binärdateien, Electron braucht `install.js` von Hand. Beides steht in der Bau-Anleitung | +| 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 | +| ~~`node:sqlite` in Electron ungeprüft~~ | **gelöst** | § 3.4 — am 30.08.2026 im `utilityProcess` von Electron 44 gemessen. Rückfallebene `better-sqlite3` nicht gebraucht | +| ~~Öffentliche Update-Quelle für Externe~~ | **gelöst** | § 3.6 — Gitea ist öffentlich per HTTPS erreichbar, Entscheid 6 nutzt es direkt | + +### Stand der Entscheidungen (30.08.2026, zweite Fassung) + +Alle offenen Punkte der ersten Fassung sind entschieden **und** die Prüfung +hat zwei davon zusätzlich vereinfacht: Code-Signing **nein** (Entscheid 5) · +Update-Quelle **öffentliches Gitea, direkt** (Entscheid 6, durch Messung § 3.6 +von einer Infrastruktur-Frage zu einer Bau-Einstellung geschrumpft) · alter +Windows-Weg **wird sofort entfernt** (Entscheid 7) · Remote-Worker **bleibt +bei Docker** (Entscheid 8) · Zielgruppe **Bekannte mit Link** (Entscheid 9). +Was daraus an Arbeit folgt, steht in § 11 und § 12. + +--- + +## 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. +- **Kein öffentlicher Auftritt.** Entscheid 9: Weitergabe per Link an + Bekannte, kein Marketing, kein Support-Versprechen an Fremde. + +--- + +## 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, zweite Fassung · 30.08.2026 · Branch `worktree-windows-electron`* diff --git a/beweise/README.md b/beweise/README.md index 2b909a8..1ec558f 100644 --- a/beweise/README.md +++ b/beweise/README.md @@ -89,25 +89,47 @@ Get-Process -Id $kind -ErrorAction SilentlyContinue # muss LEER sein --- -## Zwei weitere Messungen aus § 3, ohne eigenes Skript +## `sqlite-main.js` + `sqlite-kern.js` — node:sqlite in Electron -**`node:sqlite` ist in Node 24 eingebaut:** +Beantwortet die Frage, die in der ersten Fassung des Konzepts als ⚠️ offen +stand: Electron baut sein Node mit eigenen Flags — ist `node:sqlite` dort +überhaupt vorhanden? + +Gemessen am 30.08.2026 mit **Electron 44.0.0**, und zwar im +**utilityProcess** — dem Kontext, in dem Rippy v5 seine Datenbank betreiben +wird (`kern/speicher/db.ts`), nicht bloß im Node-Modus der Binary: ``` -node -e "const s=require('node:sqlite'); const db=new s.DatabaseSync(':memory:'); db.exec('CREATE TABLE t (a TEXT)'); db.prepare('INSERT INTO t VALUES (?)').run('geht'); console.log(db.prepare('SELECT a FROM t').get().a)" -geht +KERN-ANTWORT: {"ok":true,"wert":"geht","node":"24.18.1"} +Kern beendet mit 0 ``` -⚠️ Das ist in **Node 24.19.0** gemessen, nicht in Electron. Electron baut sein -Node mit eigenen Flags — ob `node:sqlite` dort vorhanden ist, ist der erste -Handgriff in Etappe W-0. +**Antwort: ja.** Die im Konzept vorgesehene Rückfallebene `better-sqlite3` +wird nicht gebraucht. (Zur Einordnung: In Node 24.19.0 pur war dieselbe +Messung zuvor ebenfalls grün.) -**npm 11 blockiert Install-Skripte:** +Nachstellen: + +``` +npm install electron@44 --no-save +node node_modules/electron/install.js +./node_modules/electron/dist/electron.exe sqlite-main.js +``` + +Die zweite Zeile ist kein Versehen — siehe nächster Abschnitt. + +--- + +## npm 11 blockiert Install-Skripte — und das trifft Electron selbst ``` 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. Die Warnung -gehört in die Bau-Anleitung, sonst sucht eines Tages jemand an der falschen -Stelle. +koffi lädt trotzdem — es bringt vorgebaute Binärdateien mit. **Electron +trifft die Sperre härter:** Sein Install-Skript lädt die eigentliche +Programmdatei herunter; wird es geblockt, liegt nach `npm install` ein +Paket **ohne `dist/electron.exe`** da, und nichts startet. Am 30.08.2026 +beim sqlite-Beweis selbst hineingelaufen; `node node_modules/electron/install.js` +holt die Binary nach. Beides gehört in die Bau-Anleitung, sonst sucht eines +Tages jemand an der falschen Stelle. diff --git a/beweise/sqlite-kern.js b/beweise/sqlite-kern.js new file mode 100644 index 0000000..f44a5db --- /dev/null +++ b/beweise/sqlite-kern.js @@ -0,0 +1,12 @@ +// Läuft als utilityProcess — der Kontext, in dem Rippy v5 seine Datenbank +// betreiben wird (kern/speicher/db.ts laut KONZEPT-WINDOWS.md § 4.2). +try { + const s = require('node:sqlite'); + const db = new s.DatabaseSync(':memory:'); + db.exec('CREATE TABLE t (a TEXT)'); + db.prepare('INSERT INTO t VALUES (?)').run('geht'); + const wert = db.prepare('SELECT a FROM t').get().a; + process.parentPort.postMessage({ ok: true, wert: wert, node: process.versions.node }); +} catch (e) { + process.parentPort.postMessage({ ok: false, fehler: e.message }); +} diff --git a/beweise/sqlite-main.js b/beweise/sqlite-main.js new file mode 100644 index 0000000..ac02c8e --- /dev/null +++ b/beweise/sqlite-main.js @@ -0,0 +1,20 @@ +// Beweis: node:sqlite funktioniert im utilityProcess von Electron 44. +// Gemessen 30.08.2026 — siehe README.md in diesem Ordner. +// +// Startet den Kern-Prozess (sqlite-kern.js) genau so, wie Rippy v5 seinen +// Kern starten wird (utilityProcess.fork), und wartet auf dessen Antwort. +const { app, utilityProcess } = require('electron'); +const path = require('path'); + +app.whenReady().then(() => { + const kind = utilityProcess.fork(path.join(__dirname, 'sqlite-kern.js')); + kind.on('message', (antwort) => { + console.log('KERN-ANTWORT: ' + JSON.stringify(antwort)); + app.exit(antwort.ok ? 0 : 1); + }); + kind.on('exit', (code) => { + console.log('Kern beendet mit ' + code); + if (code !== 0) app.exit(1); + }); + setTimeout(() => { console.log('TIMEOUT'); app.exit(2); }, 15000); +});