Files
rippy/ROADMAP.md
T
Hitonabi ab75134931
Ampel / ampel (push) Successful in 29s
feat: Download-Knopf fuer fertige Rips — Dateien direkt im Browser statt scp
- GET /jobs/{id}/files: Dateiliste aus job.output_path (Name + Groesse)
- GET /jobs/{id}/files/{name}: FileResponse-Stream; Validierung strikt —
  output_path muss unter /app/media liegen, nackter Dateiname (kein
  Slash/.., kein Dotfile), realpath-Check gegen Symlink-Ausbrueche.
  Mit Test (test_dateiname_validierung_blockt_pfad_tricks).
- UI: 'Download'-Knopf in der Aktion-Spalte bei fertigen Jobs; die
  Dateiliste mit Groessen + Download-Links lebt im Job-Detail-Popup
  (ein Dropdown wuerde im overflow-x-auto-Tabellencontainer clippen).
- nginx: proxy_buffering off + proxy_read_timeout 3600s waren fuer SSE
  schon gesetzt — grosse Downloads brauchen keine Aenderung.

Wunsch aus der Uebernahme-Session (Commander-Sammelliste 24.07.).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 09:01:38 +02:00

362 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ROADMAP — Rippy
> Meilenstein-Plan für den Bau von Rippy. Jede Etappe ist lauffähig für sich.
---
## Etappe 1: Fundament — Container-Infrastruktur + udev-Erkennung
**Ziel:** Das System bootet, erkennt eine eingelegte Disc und erstellt einen Job.
**Was gebaut wird:**
- Docker Compose mit `api`, `worker`, `ui`, `postgres`, `redis`
- Basis-Dockerfiles für jeden Service (Python/FastAPI, Python/Celery, Node/React, PostgreSQL, Redis)
- udev-Regel + separater Daemon (Python), der Disc-Einwurf erkennt und Jobs an den Worker sendet
- Device-Resolver: ermittelt UUID/Serial des Laufwerks, erzeugt Symlink `/dev/disc/<uuid>`
- Job-Erstellung in Celery-Queue mit Disc-Typ und Device-Pfad
**Fertig wenn:**
-`docker compose up` startet alle 5 Container
- ✅ udev-Regel erkennt Disc-Einwurf
- ✅ udev-Daemon erstellt Celery-Job
- ✅ Celery-Worker nimmt den Job entgegen und gibt "Disc erkannt: DVD, Device: /dev/disc/xxx" aus
**Status:** Abgeschlossen
---
## Etappe 2: Ripping-Pipeline — Verlustfreies Extrahieren
**Ziel:** Disc wird rippt und als rohe Dateien abgelegt.
**Was gebaut wird:**
- CD-Ripping via `abcde` → FLAC, AcoustID-Fingerprinting (chromaprint) + MusicBrainz-Lookup
- DVD/Blu-ray-Ripping via `makejungles` (makeMKV-Äquivalent, OpenSource) → MKV, mit `--all --progress`
- Ripping im Worker-Container, Read-Only Device-Passthrough
- Fortschritts-Reporting über Celery-Signale an SSE-Stream
**Fertig wenn:**
- CD → FLAC-Dateien + MusicBrainz-Metadaten
- DVD → MKV mit allen Titeln
- Blu-ray → MKV mit allen Titeln
- Fortschritt wird in Echtzeit im UI angezeigt
---
## Etappe 3: Metadaten-Lookup + Pre-Scan
**Ziel:** Vor dem Ripping wird die Disc identifiziert und der Commander bestätigt.
**Was gebaut wird:**
- Pre-Scan-Modul: liest TOC (kein Ripping), extrahiert Titel/Laufzeit/Scene-Labels
- TMDB-Integration für Film-/Serien-Matching (Confidence-Score)
- MusicBrainz-Integration für CD-Matching
- TheTVDB-Fallback für Serien
- SQLite-Cache für API-Antworten (LRU, 10k Einträge, TTL)
- Pre-Scan-Latenz: 515s, dokumentiert
**Fertig wenn:**
- Nach Disc-Einwurf: Pre-Scan läuft automatisch
- UI zeigt: Titel, Jahr, Cover, Confidence-Score, Trackliste
- Commander kann bestätigen oder manuell korrigieren
- Bestätigte Metadaten werden im Cache persistiert
**Status:** Abgeschlossen
---
## Etappe 4: Jellyfin-Formatierung + NFO-Generierung
**Ziel:** Gerippte Dateien liegen in Jellyfin-konformer Ordnerstruktur mit Metadaten.
**Was gebaut wird:**
- Ordnerstruktur:
- Filme: `<Filmname> (<Jahr>)/<Filmname>-<title>.mkv`
- Serien: `<Serienname>/<Staffel N>/<Serienname> - S{N}E{N} - <Episode>.mkv`
- Musik: `<Künstler>/<Album> (<Jahr>)/<Track-Nr>. <Titel>.flac`
- NFO-Generator im Kodi/NFO-Schema:
- `movie.nfo`, `series.nfo`, `episode.nfo`, `album.nfo`
- Alle Metadaten aus Pre-Scan + NFO-Attribution (Source: TMDB)
- Image-Downloader: poster.jpg (500x750), fanart.jpg, backdrop.jpg (1920x1080+) von TMDB
- Jellyfin-kompatible Dateibenennung
- Multi-Disc-Handling: `Disc 1.mkv`, `Disc 2.mkv` etc.
**Fertig wenn:**
- Gerippte Dateien + NFO + Poster in Jellyfin-Ordnerstruktur
- Jellyfin scannt und erkennt alles korrekt
- Multi-Disc-Sets werden als eine Entität angezeigt
**Status:** Abgeschlossen
---
## Etappe 5: API + Auth + WebUI
**Ziel:** Vollständiger Web-Dienst mit Echtzeit-Status und Job-Steuerung.
**Was gebaut wird:**
- FastAPI mit JWT-Auth (Access 15min, Refresh 7 Tage), Rate-Limiting (100/min/API-Key)
- REST-Endpoints: Jobs erstellen/listen/abbrechen, Geräte verwalten, Einstellungen
- SSE-Stream für Echtzeit-Jobstatus
- React-UI (Vite-Build → statisch via Nginx):
- Dashboard mit Echtzeit-Kacheln (Job-Status, Queue, Disc-Einwurf)
- Job-Verlauf mit Fortschrittsbalken
- Metadaten-Preview mit Bestätigungs-Dialog
- Job-Detail mit Live-Log
- Ergebnis-View mit Ordnerstruktur-Preview
- Einstellungen (API-Keys, Transcoding, Backup-Pfade, Jellyfin-Config)
- Geräte-Verwaltung
**Fertig wenn:**
- Commander kann UI im Browser öffnen und alles bedienen
- Jobs starten, stoppen, Verlauf einsehen
- Echtzeit-Updates via SSE funktionieren
- Alle Einstellungen werden gespeichert
**Status:** Abgeschlossen
---
## Etappe 6: Sicherheit + Compliance + Hardening
**Ziel:** Produktionsreif — sicher, compliant, robust.
**Was gebaut wird:**
- SELinux/AppArmor Profile pro Container
- Read-only Bind-Mounts für System-Bibliotheken
- mTLS zwischen API ↔ Worker
- Netzwerk-Policy: UI→API (HTTPS), API↔Worker (mTLS), Worker↔Internet (nur API)
- PostgreSQL mit verschlüsselten Connections
- Source-Release-Endpoint (GPL-v3-Compliance)
- Lizenz-Dokumentation (MakeMKV, OpenSource-Komponenten)
- Backup-Hooks (PBS-Snapshot-Integration)
- Error-Handling: exponential backoff Retry (max 5), Circuit-Breaker für APIs
**Fertig wenn:**
- Alle Container haben Security-Profile
- Netzwerkverkehr zwischen Containern ist verschlüsselt
- GPL-v3-Compliance-Checkliste abgehakt
- Backup-Hooks funktionieren
**Status:** Abgeschlossen
---
## Etappe 7: API UI Modernisiert
**Ziel:** Modernes React-UI mit Dark Mode Support.
**Was gebaut wird:**
- React UI mit TailwindCSS
- Sidebar Navigation
- Einstellungen mit Tab-Struktur
- Dark Mode mit Theme-Toggle und localStorage persistence
- Status-Badges und StatCards mit Dark Mode Support
**Fertig wenn:**
- UI mit Dark Mode läuft
- Navigation und Einstellungen funktionieren
- Commit `43dfce5` — Dark Mode Implementation
**Status:** Abgeschlossen
---
## Etappe 8: Separation of Concerns (Dark Mode)
**Ziel:** Code-Qualität durch saubere Trennung.
**Was gebaut wird:**
- Theme-Context aus App.tsx auslagern
- Dark Mode Utility für CSS-Klassen
- Theme props in Komponenten verstecken
- Tailwind-Config zentralisieren
**Fertig wenn:**
- Theme-Logik in eigenem Context
- Wiederverwendbare Dark Mode Helper
- Saubere Komponenten-Struktur
**Status:** Abgeschlossen (Teil von Etappe 9)
---
## Etappe 9: SoC Refactoring & Config Validation
**Ziel:** Code-Qualität durch saubere Trennung + Konfigurations-Validierung.
**Was gebaut wird:**
- Theme-Context aus App.tsx in eigenes Context auslagern
- Config Validation mit TMDB-API-Key Pflicht
- Cache Key Centralization (cache/keys.py)
- Worker Refactoring für bessere Trennung
- Docker Compose mit Healthchecks
**Fertig wenn:**
- Theme-Logik in eigenem Context (ThemeContext.tsx)
- Wiederverwendbare Dark Mode Helper (useDarkMode.ts)
- TMDB-API-Key ist Pflicht (Fehler bei Fallback-UI)
- Cache-Keys zentral definiert
- Docker Healthchecks für API
**Status:** In Arbeit
## Etappe 9: Proxmox-Integration
**Ziel:** Ein-Click-Deploy auf Proxmox LXC.
---
## Offene Punkte (aus KONZEPT.md)
| Punkt | Etappe | Behandlung |
|-------|--------|------------|
| TMDB-Matching-Fehler bei Nischentiteln | Etappe 3 | Pre-Scan Confidence-Score + manueller Korrektur-Mechanismus |
| NFO-Format-Abhängigkeit von Jellyfin-Version | Etappe 4 | Kodi/NFO-Schema (stabil, gut dokumentiert) |
| Pre-Scan-Latenz (515s) | Etappe 3 | Akzeptabel, Parallelisierung möglich |
| TMDB-Bildrechte | Etappe 4 | Gelöst: TMDB API-ToS erlaubt private Nutzung |
| Hybrid-Discs | Etappe 1 | MVP erkennt nur Standard; als "Kann" notiert |
| Redis Single-Instance | Etappe 1 | MVP reicht; Cluster als "Später" |
## Etappe 10: MakeMKV-Umstellung — das Muss-Feature „lossless" einlösen
**Ziel:** DVD/Blu-ray-Ripping auf `makemkvcon` umstellen (verlustfrei), wie im
KONZEPT als Muss definiert. HandBrake fliegt aus dem Ripp-Pfad (bleibt ggf. als
optionaler Transcode-Schritt NACH dem verlustfreien Rip).
- [x] Worker-Dockerfile: makemkv-oss/makemkv-bin 1.18.4 multi-stage gebaut,
Beta-Key via MAKEMKV_APP_KEY-Env (entrypoint.sh) — 23.07.
- [x] ripping.py: auf `makemkvcon -r --progress=-same mkv dev:… all` umgestellt,
PRGV-Fortschritts-Parsing laut makemkv.com/developers/usage.txt, Tests dabei — 23.07.
- [ ] E2E mit echter Disc verifizieren (Commander, Laufwerk nötig)
**Fertig wenn:** eine echte DVD landet verlustfrei als MKV im Output, Ampel grün.
**Entstanden aus:** Review 22.07. — HandBrake-lossy war eine stille Abweichung vom
Konzept; Entscheid 22.07. abends: Konzept gilt, Umbau als eigene Etappe.
---
## Etappe 11 (23.07.2026): Kernumbau — der Zweck funktioniert erstmals wirklich
**Befund des Komplett-Audits (Claude):** Der Kern „Disc rein → gerippt raus"
hatte NIE einen Code-Pfad: kein POST /jobs, kein udev-Daemon (das referenzierte
udev_daemon.py existierte nirgends), kein Ripping-Tool im Worker-Image,
Disc-Erkennung strukturell tot (`file -L` auf Block-Device), Geräte als
Bind-Mounts statt devices: (EPERM), UI rief hartkodiert localhost:8000 auf.
**Gebaut:**
- [x] POST /jobs (API → Celery → Worker) + Job-Persistenz in Postgres
- [x] Disc-Watcher in der API (ioctl-Poll statt udev — Container-tauglich)
- [x] Disc-Erkennung neu über Kernel-ioctls (detection.py, in API+Worker)
- [x] /logs-Endpoint + echte Logs-Seite (Mock-Daten raus)
- [x] /settings-Endpoints (Settings-Seite sprach vorher ins Leere)
- [x] UI: Vite-Build + nginx mit /api-Proxy statt Dev-Server; relative API-Pfade
- [x] compose: devices: statt Bind-Mounts; Ausgabe aufs media-Volume
- [x] Jellyfin-Post-Processing an den Worker anbinden (NFO/Poster nach dem
Rip) — 24.07., medien.py: Ordner „Titel (Jahr)" + movie.nfo + poster.jpg,
gesteuert übers mediaServer-Setting (Jellyfin/Emby/Kodi/Plex/keins)
- [ ] Auth vor die schreibenden Endpoints (JWT existiert, schützt aber nichts)
---
## Etappe 12: Besser als ARM (aus ARM-Analyse 23.07.2026)
**Quelle:** Vollanalyse von Automatic Ripping Machine v2.24 (Code, arm.yaml,
Wiki, Issues). Was Rippy strukturell schon besser macht: Lossless-first statt
Transcode-first (ARMs BD-Default ist ein 1080p30-Downscale!), Celery+Postgres
statt Prozess-pro-udev-Event+SQLite (ARM-UI friert beim Rippen ein, #1046),
ioctl-Watcher statt fragiler udev-Ketten (ARMs größte Support-Quelle), NFO +
Jellyfin-Konvention nativ (ARM kann nur Emby-Refresh, keine NFOs).
**Übernehmen (priorisiert):**
- [ ] **Disc-Fingerprint-DB (selbstlernend)** — ARMs beste Idee, besser gemacht:
Tabelle disc_fingerprints (DVD-CRC64/MusicBrainz-DiscID/BD-Label-Hash →
bestätigte Metadaten). Jede UI-Bestätigung schreibt zurück; zweite Disc
derselben Serie wird sofort erkannt. Lokal statt ARMs Crowd-Server
(Single Point of Failure auf Free-Hosting).
- [ ] **BD-Titel aus BDMV/META/DL/bdmt_eng.xml** als Identifikations-Stufe vor
den APIs (echter Disc-Titel statt Label-Raterei).
- [ ] **Progressive Suchdegradation** bei TMDB/OMDb: mit Jahr → ohne Jahr →
tokenweises Titel-Stripping; jede Stufe senkt die Confidence.
- [ ] **Duplikat-Schutz**: Fingerprint-Treffer → „schon gerippt am X — trotzdem?"
- [ ] **Track-Auswahl (Manual Mode), besser als ARM**: Job-Status
awaiting_selection nach makemkvcon-info; Track-Tabelle im UI mit
Heuristik-Vorauswahl; Timeout → Weiter mit Default (ARM bricht hart ab,
erlaubt nur EINE Auswahl).
- [ ] **Min/Max-Titellänge + Extras**: Bonusmaterial nach extras/ (versteht
Jellyfin nativ) statt alles flach.
- [x] **Benachrichtigungen bei Job-Ende** — 24.07., eigenes notify-Modul
(Discord/Slack/ntfy/generisches JSON, Dienst-Erkennung an der URL)
statt Apprise-Dependency; Test-Knopf im UI. Telegram/Deep-Links offen.
- [ ] **Multi-Drive-Verwaltung**: drives-Tabelle (Seriennummer, Custom-Name,
UHD-fähig-Flag), parallele Rips, Job↔Drive-Zuordnung.
- [ ] **Backup-Modus** (makemkvcon backup --decrypt) als Plan B für Problem-Discs.
- [ ] **Jellyfin-Refresh** nach Ablage (POST /Library/Refresh).
**ARM schlagen (Alleinstellung):**
- [ ] **Serien-Episoden-Erkennung** — ARMs offene Wunde seit 2019 (#395):
TVDB-Episodenlaufzeiten gegen Track-Laufzeiten matchen (±5 %,
Reihenfolge-Constraint, Disc-Nr aus Label), Vorschlag
„Show/Season 02/Show S02E05.mkv" im Preview bestätigen.
- [ ] **Main-Feature per Laufzeitabgleich** mit TMDB-Runtime (±3 min) statt
ARMs „größte Datei gewinnt" (#1297-Fehlklasse).
---
## Etappe 13 (24.07.2026): Universal-Komfort-Runde — Lücken aus dem Alltag schließen
**Quelle:** Commander-Sammelauftrag 24.07. — alles, was beim echten Benutzen
auffiel, in einem Rutsch. Alles gebaut, Tests dabei, Ampel-Blocker behoben.
**Gebaut:**
- [x] **Ampel entrostet**: bcrypt auf 4.0.1 gepinnt (passlib-1.7.4-Bruch) und
cache_keys-Test an das Fingerprint-Format vom 23.07. angepasst — DIE
beiden Rot-Ursachen, wegen derer `stable` 10 Commits zurückhing.
- [x] **Media-Server-Integration**: mediaServer-Setting (Wizard + Einstellungen),
Zielordner „Titel (Jahr)", movie.nfo/tvshow.nfo + poster.jpg für
Jellyfin/Emby/Kodi (medien.py im Worker, mit Tests).
- [x] **Benachrichtigungen echt gemacht**: notify.py (API+Worker), Meldung bei
fertig/fehlgeschlagen/abgebrochen, Anleitung + Test-Knopf im UI —
das Webhook-Feld war vorher ein Placebo, nichts hat je gesendet.
- [x] **SMB-Freigaben-Scan repariert**: NT_STATUS-Fehler → Klartext (Gast-
Abfrage verweigert ⇒ Benutzer/Passwort nötig), Credential-Felder im UI
VOR den „Freigaben auflisten"-Knopf gezogen.
- [x] **4K-UHD-Platzproblem**: Arbeitsverzeichnis konfigurierbar (workDir,
z. B. NAS-Freigabe), Platz-Check per Disc-Größe (ioctl) VOR dem Rip.
- [x] **MakeMKV-Key über UI** (Einstellungen → System): gilt ab dem nächsten
Rip, ohne Rebuild; Worker melden Werkzeug-Versionen + Key-Quelle.
- [x] **Job-Detail-Popup**: Klick auf Titel in „Neueste Jobs" → Poster, Jahr,
Beschreibung, Ablagepfad, Fehler (Job speichert Metadaten jetzt mit).
- [x] **UI-Feedback überall**: Toast-System (reinploppende Meldungen), drehende
Refresh-Knöpfe, Logs-Filter um Warnung/Fehler-Pills ergänzt.
- [x] **Datei-Browser zeigt Dateien**: Ordner wirkten „leer", weil nur
Unterordner gelistet wurden — Dateien erscheinen jetzt grau mit Größe.
- [x] Favicon (Disc im Logo-Verlauf) + Footer „Created with ❤️ by LucyAI,
Claude and KrBrZ".
- [x] README: Media-Server, Benachrichtigungen, UHD-Arbeitsverzeichnis,
„Rippy woanders bereitstellen" (beliebiger Docker-Host).
- [x] **Download-Knopf für fertige Rips** (Wunsch aus der Übernahme-Session):
GET /jobs/{id}/files (+ Datei-Stream, Pfad-Validierung strikt unter
/app/media inkl. realpath-Check), Download-Knopf in der Aktion-Spalte,
Dateiliste mit Größen im Job-Detail-Popup — vorher kam man an fertige
MKVs nur per scp. nginx hatte proxy_buffering off schon (SSE).
**Offen aus dieser Runde:** nichts — Rest siehe Etappe 12 und Ideen unten.
---
## Ideen-Katalog (24.07.2026, priorisiert) — Lücken, die noch offen sind
1. **Serien-Staffel-Flow**: Beim Rippen einer Serien-Disc Staffel/Disc-Nr
abfragen → Ablage `Show/Season 02/`, Episoden-Matching wie Etappe 12.
2. **Duplikat-Warnung**: Disc-Fingerprint schon in der Job-Historie →
„schon am X gerippt — trotzdem?" (Fingerprint-Infrastruktur existiert).
3. **Jellyfin-Bibliotheks-Refresh** nach Ablage (API-Key + URL in den
Einstellungen, POST /Library/Refresh) — der letzte Schritt zur
Null-Klick-Kette Disc→Bildschirm.
4. **Track-Auswahl vor dem Rip** (awaiting_selection, Etappe 12) — spart
bei Bonus-Material-Discs Stunden.
5. **Auth vor schreibende Endpoints** + Login-Flow im UI (JWT existiert).
6. **Speicherplatz-Kachel im Dashboard** (Media/Arbeitsverzeichnis als
Balken — /system/info liefert die Daten schon).
7. **Rip-Historie exportieren** (CSV/JSON) für die Sammlung-Übersicht.
8. **Design 2.0**: Design-Tokens statt Theme-Ternaries in jeder Zeile —
die `theme === 'dark' ? … : …`-Kaskaden sind der größte UI-Schuldenberg.