docs: add parallel metadata/ripping workflow plan (Phase A/B/C)

Describes the restructured workflow where metadata gathering (TOC, CDDB,
MB, Vision-LLM) happens before ripping starts, so the user can review and
edit album.json before committing to the long rip — not after.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-02-20 10:57:04 +01:00
commit 6a7602387a

View file

@ -204,3 +204,134 @@ Testvorgaben:
- Track-Matching bei unterschiedlicher Track-Anzahl - Track-Matching bei unterschiedlicher Track-Anzahl
- disc_id korrekt pro Disc zugeordnet - disc_id korrekt pro Disc zugeordnet
- Fehlende Quellen (None) robust behandelt - Fehlende Quellen (None) robust behandelt
---
## Parallelisierung: Informationsbeschaffung vor dem Rippen
### Kernidee
Die CD muss für zwei Operationen physisch im Laufwerk liegen, aber diese
brauchen sich nicht zu überlappen:
```
Operation A: TOC lesen (cd-discid) ~2 Sekunden
Operation B: Audio rippen (cdparanoia) 2060 Minuten pro Disc
```
Heute sind beide in einem monolithischen Durchlauf verschmolzen. Das
Umordnen kostet nichts — die CD darf ruhig zweimal im Laufwerk liegen.
### Neuer Ablauf (Drei Phasen A / B / C)
```
┌─────────────────────────────────────────────────────────┐
│ Phase A: Informationsbeschaffung (~30s 5 min) │
│ │
│ 1. CD einlegen (einmalig oder pro Disc bei Multi-Disc) │
│ 2. cd-discid → TOC: disc_id, Offsets, Laufzeiten │
│ 3. CDDB-Lookup → artist, album, year, genre, tracks │
│ 4. EAN-Scan → MusicBrainz → Album + MBID │
│ 5. CAA-Download → front.jpg, back.jpg │
│ 6. back.jpg → Vision-LLM (läuft parallel, ~13 min) │
│ 7. merge_album() → album.json (preliminary) │
│ 8. "album.json bereit — bitte prüfen." │
│ 9. User editiert album.json (optional, zweites │
│ Terminal oder nach Prompt) │
│ 10. "Rippen starten? [Enter]" │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ Phase B: Audio-Ripping (~2060 min pro Disc) │
│ │
│ abcde läuft im Vordergrund (Terminal zeigt Progress) │
│ Während des Rippens im zweiten Terminal: │
│ - album.json editieren (jederzeit möglich) │
│ - Vision-LLM-Ergebnis (falls noch ausstehend) │
│ wird nach Abschluss nachgemergt │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ Phase C: Apply (~30s) │
│ │
│ Liest finales album.json (evtl. editiert während B) │
│ organize → tag → embed → playlist │
└─────────────────────────────────────────────────────────┘
```
### Was der Hauptgewinn ist
```
Heute: Metadaten ── Rippen (60 min) ── album.json sehen ── editieren
Neu: Metadaten ── album.json sehen ── [editieren] ── Rippen (60 min)
└── Vision-LLM läuft parallel zum Rippen
```
Der User sieht die Metadaten **vor** dem langen Warten. Editing erfolgt
mit vollständiger Information, nicht blind im Nachhinein.
### Warum kein Hintergrund-Ripping
Hintergrund-Ripping (subprocess mit PID-Datei) ist technisch machbar,
erhöht aber die Komplexität (PID-Management, Fehlerbehandlung, Status-
Tracking) ohne entscheidenden Mehrwert: Der User kann während des
Vordergrund-Rippings jederzeit ein zweites Terminal öffnen und album.json
dort editieren. `$EDITOR album.json` reicht.
### Abcde: keine Änderung nötig
abcde hat Aktions-Flags (`-a cddb,read,encode,tag`), die eine Trennung
erlauben würden. Da wir CDDB und TOC aber bereits **vor** abcde über
eigene Module abfragen (`lookup_by_discid`, `cd-discid`), kann abcde
unverändert für Phase B genutzt werden — nur mit dem Wissen, dass seine
CDDB-Ausgabe bereits in album.json konsolidiert ist.
### Multi-Disc-Verhalten
```
Disc 1: Phase A (~2 min) → "Rippen starten?" → Phase B Disc 1 (~45 min)
Disc 2: Phase A (~2 min) → "Rippen starten?" → Phase B Disc 2 (~45 min)
...
```
Jede Disc wird sequenziell behandelt (ein Laufwerk). Phase A aller Discs
könnte theoretisch vorab gebündelt werden (alle Discs kurz einlegen, TOC
lesen), ist aber als optionale Optimierung einzustufen.
### Änderungsbedarf in ripper.py
Die heutige `interactive_rip`-Funktion wird aufgeteilt in:
```python
def gather_metadata(disc_num, config, scanner) -> DiscMetadata:
"""Phase A: TOC, CDDB, MB, Vision-LLM — kein Rippen."""
...
def rip_disc(disc_num, config) -> Path:
"""Phase B: abcde — kein Netzwerk, kein LLM."""
...
def interactive_rip(config):
"""Orchestriert: für jede Disc gather_metadata → confirm → rip_disc."""
...
```
`DiscMetadata` ist ein internes Dataclass/NamedTuple das alle Rohdaten
einer Disc bis zum Merge trägt:
```python
@dataclass
class DiscMetadata:
disc_number: int
discid_line: str # Rohstring aus cd-discid
cddb_result: CddbResult | None
mb_album: Album | None # nur bei erster Disc sinnvoll
mb_mbid: str | None
vision_album: Album | None # Ergebnis des Background-Threads
uploaded_photo: Path | None
```
Nach dem letzten `rip_disc`-Aufruf ruft `interactive_rip` einmalig
`merge_album()` auf und schreibt album.json.