docs: add Phase 5 merger.py plan with intelligent metadata merging
Concrete plan for Option B: new merger.py module with field-by-field priority merging, duration_ms/disc_id model extensions, cover strategy, and track-matching logic for sources with differing track counts. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
1ca88b0d6d
commit
c791812755
1 changed files with 130 additions and 1 deletions
|
|
@ -1,6 +1,6 @@
|
|||
# Refactoring-Plan: Workflow-Phasen in ripper.py
|
||||
|
||||
*Stand: 2026-02-19 — Entwurf zur Diskussion*
|
||||
*Stand: 2026-02-20 — Entwurf zur Diskussion*
|
||||
|
||||
## Aktueller Zustand
|
||||
|
||||
|
|
@ -75,3 +75,132 @@ Soll die Disc-Schleife (Phase 4) eine einheitliche Schnittstelle haben, die
|
|||
beide Pfade bedient? Im MB-Pfad ist die Disc-Anzahl bekannt, im Fallback-Pfad
|
||||
offen (while-Schleife mit "Nächste CD?"). Das ist der größte strukturelle
|
||||
Unterschied.
|
||||
|
||||
---
|
||||
|
||||
## Phase 5 im Detail: Intelligente Metadaten-Zusammenführung (merger.py)
|
||||
|
||||
### Ziel
|
||||
|
||||
Statt "Winner-takes-all" (bisher: Vision > MB > CDDB) eine **feldweise
|
||||
Zusammenführung** aller verfügbaren Quellen. Jedes Feld wird aus der besten
|
||||
verfügbaren Quelle befüllt. Zusätzlich werden bisher verworfene technische
|
||||
Daten (Disc-ID, Tracklängen) persistiert.
|
||||
|
||||
### Neues Modul: `src/musiksammlung/merger.py`
|
||||
|
||||
Einzige öffentliche Funktion:
|
||||
|
||||
```python
|
||||
def merge_album(
|
||||
mb_album: Album | None, # Phase 1: MusicBrainz-Treffer
|
||||
cddb_results: list[CddbResult], # Phase 4: ein CddbResult pro Disc
|
||||
vision_album: Album | None, # Phase 3: Vision-LLM-Ergebnis
|
||||
disc_ids: list[str], # Phase 4: cd-discid-Output pro Disc
|
||||
user_album_name: str | None, # Phase 1/4: manuell eingetippter Name
|
||||
) -> Album:
|
||||
...
|
||||
```
|
||||
|
||||
### Datenmodell-Erweiterungen (models.py)
|
||||
|
||||
```python
|
||||
class Track(BaseModel):
|
||||
track_number: int
|
||||
title: str
|
||||
artist: str | None = None
|
||||
duration_ms: int | None = None # NEU: aus MB (ms) oder TOC berechnet
|
||||
|
||||
class Disc(BaseModel):
|
||||
disc_number: int
|
||||
name: str | None = None
|
||||
tracks: list[Track]
|
||||
disc_id: str | None = None # NEU: 8-Hex CDDB Disc-ID aus cd-discid
|
||||
```
|
||||
|
||||
`Album.genre` ist bereits vorhanden, wird aber bisher nie befüllt — das
|
||||
wird mit dieser Änderung behoben.
|
||||
|
||||
### Feldweise Priorisierung
|
||||
|
||||
| Feld | Priorität | Begründung |
|
||||
|------|-----------|------------|
|
||||
| `album.artist` | MB > Vision > CDDB | MB normalisiert (z.B. "Bach, J.S.") |
|
||||
| `album.album` | MB > Vision > CDDB | MB autoritativ |
|
||||
| `album.year` | MB > CDDB > Vision | MB hat exaktes Release-Datum |
|
||||
| `album.genre` | CDDB (einzige Quelle) | MB und Vision liefern nie Genre |
|
||||
| `disc.name` | Vision (einzige Quelle) | MB und CDDB kennen kein disc.name |
|
||||
| `disc.disc_id` | cd-discid (einzige Quelle) | Physischer Fingerprint der CD |
|
||||
| `track.title` | Vision > MB > CDDB | Vision bereinigt (ohne Zeitangaben) |
|
||||
| `track.artist` | MB > CDDB > Vision | MB normalisiert Track-Artists |
|
||||
| `track.duration_ms` | MB > TOC-berechnet | MB in ms; TOC aus Sektor-Offsets |
|
||||
|
||||
### Tracklängen: zwei Quellen
|
||||
|
||||
**Quelle A — MusicBrainz** (`track.length` in Millisekunden):
|
||||
- Zuverlässigste Quelle, von MB-Redakteuren gepflegt
|
||||
- Wird in `_parse_release()` (musicbrainz.py) bereits ignoriert → dort ergänzen
|
||||
|
||||
**Quelle B — TOC aus cd-discid** (Sektor-Offsets):
|
||||
- cd-discid liefert: `discid ntrks offset_1 offset_2 ... total_sectors`
|
||||
- Laufzeit Track n = `(offset_{n+1} - offset_n) / 75` Sekunden
|
||||
- Für letzten Track: `(total_sectors - offset_n) / 75`
|
||||
- Genau, weil direkt vom physischen Medium gemessen
|
||||
- Erfordert: `disc_ids`-Liste als strukturierte Offsets parsen (heute nur als
|
||||
Rohstring gespeichert)
|
||||
|
||||
### Track-Matching zwischen Quellen
|
||||
|
||||
Wenn Quellen unterschiedlich viele Tracks liefern (z.B. Vision-LLM liest
|
||||
12 Tracks, MB hat 13 wegen Hidden Track):
|
||||
|
||||
```
|
||||
Merge-Strategie: track_number als Primary Key
|
||||
for track_number in union(alle Quellen):
|
||||
title = erste verfügbare Quelle nach Priorität
|
||||
artist = erste verfügbare Quelle nach Priorität
|
||||
duration_ms = MB[track_number] oder TOC[track_number] oder None
|
||||
```
|
||||
|
||||
Tracks die nur in einer Quelle existieren, werden übernommen (kein
|
||||
stilles Verwerfen).
|
||||
|
||||
### Cover-Strategie (Phase 2 — Änderung gegenüber heute)
|
||||
|
||||
```
|
||||
front.jpg / back.jpg Priorität:
|
||||
1. CAA (Cover Art Archive via MBID) ← bevorzugt, standardisiert
|
||||
2. Handy-Foto ← nur wenn CAA nicht verfügbar
|
||||
|
||||
Handy-Fotos back.jpg:
|
||||
- Primärer Zweck: Text-Extraktion durch Vision-LLM (ephemer)
|
||||
- Als back.jpg speichern: nur wenn kein CAA-Cover vorhanden
|
||||
- Qualitätsproblem: Handy-Foto ≠ standardisiertes Cover-Artwork
|
||||
|
||||
Handy-Fotos front.jpg:
|
||||
- Nie nötig, wenn CAA verfügbar
|
||||
- Scanner-Upload für front.jpg entfällt bei MB-Treffern komplett
|
||||
```
|
||||
|
||||
### Zu erfassende Daten (Änderungen in anderen Modulen)
|
||||
|
||||
| Modul | Änderung | Zweck |
|
||||
|-------|----------|-------|
|
||||
| `musicbrainz.py` | `track.get("length")` → `Track.duration_ms` | Laufzeiten aus MB |
|
||||
| `cddb.py` | `disc_id` und Offsets aus discid_line parsen | TOC-Laufzeiten berechnen |
|
||||
| `ripper.py` | `discid_line` pro Disc speichern (heute verworfen) | Weitergabe an merger |
|
||||
| `cddb.py` | EXTT-Felder parsen (oft leer, aber opportunistisch) | Laufzeiten als Fallback |
|
||||
|
||||
### Testbarkeit
|
||||
|
||||
`merger.py` hat keine Abhängigkeit zu Hardware, Netzwerk oder Subprocess.
|
||||
Input: Python-Objekte. Output: `Album`. Vollständig unit-testbar mit
|
||||
Fixture-Daten — kein Mocking erforderlich.
|
||||
|
||||
Testvorgaben:
|
||||
- MB-Album + CDDB-Ergebnis + Vision-Album → korrekte Feldauswahl
|
||||
- Genre immer aus CDDB übernommen
|
||||
- Tracklängen aus MB, falls vorhanden
|
||||
- Track-Matching bei unterschiedlicher Track-Anzahl
|
||||
- disc_id korrekt pro Disc zugeordnet
|
||||
- Fehlende Quellen (None) robust behandelt
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue