diff --git a/docs/refactoring_plan.md b/docs/refactoring_plan.md index ec47a7f..ff29974 100644 --- a/docs/refactoring_plan.md +++ b/docs/refactoring_plan.md @@ -335,3 +335,189 @@ class DiscMetadata: Nach dem letzten `rip_disc`-Aufruf ruft `interactive_rip` einmalig `merge_album()` auf und schreibt album.json. + +--- + +## Wiederholbares Apply (Re-Apply in-place) + +### Ziel + +`apply` soll beliebig oft wiederholbar sein — auch nachträglich im +Jellyfin-Zielverzeichnis. Der User editiert album.json und ruft apply +erneut auf; Dateinamen, Tags und Playlist werden aktualisiert. + +### Invariante: album.json bleibt immer erhalten + +`apply` berührt album.json nie. Sie ist die einzige persistente +Wahrheitsquelle für alle Metadaten. Selbst wenn Tags oder Dateinamen +abweichen, definiert album.json den Sollzustand. + +### Stabilität durch Tracknummer-Präfix + +Der stabile Anker beim Re-Apply ist die führende Tracknummer im +Dateinamen: + +``` +01_-_Alter_Titel.flac → nach Edit → 01_-_Neuer_Titel.flac +``` + +`discover_audio_files()` identifiziert Tracks bereits nach dem +numerischen Präfix (unabhängig vom Titelanteil). Re-Apply ist damit +robust gegenüber beliebigen Titeländerungen. + +### Was apply bei jedem Aufruf tut (idempotent) + +1. Audiodateien per Tracknummer-Präfix identifizieren +2. Umbenennen gemäß aktuellem album.json (in-place) +3. Alle Audio-Tags neu schreiben (überschreiben) +4. Cover neu einbetten (überschreiben) +5. Playlist neu generieren (überschreiben, gleicher Dateiname) +6. album.json unangetastet lassen + +### Verzeichnisumbenennung bei Albumname-Änderung + +Wenn sich `album` oder `year` in album.json ändern, ändert sich der +korrekte Verzeichnisname. Im Jellyfin-Ordner ist das heikel (Jellyfin +erkennt das Album ggf. als neu). + +Vorschlag: Verzeichnisumbenennung nur mit explizitem Flag: +``` +musiksammlung apply --in-place --rename-dir +``` +Ohne Flag: Dateien und Tags werden aktualisiert, Verzeichnisname bleibt. + +--- + +## Cleanup nach dem Ripping + +abcde hinterlässt Arbeitsverzeichnisse unterhalb der CDn-Ordner: + +``` +Album/ + CD1/ + abcde.XXXXX/ ← Temp-Verzeichnis von abcde + track01.wav ← evtl. noch vorhandene WAV-Zwischendateien + status + mbid.1 +``` + +### Wann aufräumen + +**Automatisch am Ende von Phase B** (nach erfolgreichem Ripping): + +```python +for abcde_dir in cd_dir.glob("abcde.*"): + shutil.rmtree(abcde_dir) +``` + +Alternativ als expliziter Befehl für manuelles Aufräumen: +``` +musiksammlung cleanup +``` + +Beide Varianten sollten implementiert werden: automatisch als Default, +manuell als Fallback wenn Phase B mit Fehler abgebrochen wurde. + +--- + +## Neuer CLI-Befehl: gen_json + +### Zweck + +Rekonstruiert album.json aus dem vorhandenen Dateibaum, wenn die Datei +versehentlich gelöscht wurde. Kann auch für Alben genutzt werden, die +mit anderen Tools gerippt wurden. + +``` +musiksammlung gen_json +``` + +### Namenskonvention (verbindlich festgelegt) + +**Album-Verzeichnis:** +``` +Sanitized_Artist_-_Sanitized_Album_Title_(YYYY)/ +Sanitized_Album_Title_(YYYY)/ ← wenn Artist "Various Artists" +``` + +Beispiele: +``` +Pink_Floyd_-_The_Wall_(1979)/ +Bach_-_Brandenburg_Concertos_(1967)/ +Various_Artists_-_Now_Thats_What_I_Call_Music_(2001)/ +``` + +**CDn-Unterverzeichnis** (bei Multi-Disc): +``` +CD1/ CD2/ ... +``` +Bei Single-Disc: Audiodateien liegen direkt im Album-Verzeichnis oder +in `CD1/` — beides wird akzeptiert. + +**Track-Dateiname:** +``` +NN_-_Sanitized_Track_Title.flac +NN_-_Sanitized_Track_Title_-_Sanitized_Track_Artist.flac +``` + +`NN` ist zweistellig mit führender Null (`01`, `02`, ..., `99`). +`_-_` (Unterstrich-Bindestrich-Unterstrich) ist der strukturelle +Trenner — kein einfacher `-` innerhalb eines Feldes ist `_-_`. + +### gen_json Algorithmus + +``` +1. Verzeichnisname parsen: + "Pink_Floyd_-_The_Wall_(1979)" → + artist = "Pink Floyd" (vor erstem _-_) + album = "The Wall" (zwischen _-_ und _(YYYY)) + year = 1979 (aus _(YYYY)) + +2. CDn-Unterverzeichnisse → disc_number (Zahl aus "CD1", "CD2" etc.) + Keine CDn-Dirs → Single-Disc, disc_number = 1 + +3. Pro Disc: Audiodateien nach Tracknummer-Präfix sortieren: + "01_-_In_the_Flesh.flac" → track_number=1, title="In the Flesh" + "02_-_The_Thin_Ice_-_Roger_Waters.flac" + → track_number=2, title="The Thin Ice", + artist="Roger Waters" + +4. Audio-Tags lesen (mutagen) — höhere Priorität als Dateiname: + - title, artist: aus Tags übernehmen wenn vorhanden + (Tags enthalten Original-Strings ohne Sanitizing) + - duration_ms: aus Audiodatei (exakt, besser als MB oder TOC) + - genre: nur aus Tags verfügbar, nirgends im Dateinamen kodiert + +5. album.json schreiben (fehlende Felder als null) +``` + +### Daten-Rangfolge in gen_json + +| Feld | Quelle 1 (bevorzugt) | Quelle 2 (Fallback) | +|------|---------------------|---------------------| +| title | Audio-Tag | Dateiname (desanitized) | +| artist | Audio-Tag | Dateiname / Verzeichnis | +| album | Audio-Tag | Verzeichnisname | +| year | Audio-Tag | Verzeichnisname _(YYYY) | +| genre | Audio-Tag | — (bleibt null) | +| duration_ms | Audiodatei (mutagen) | — | +| disc_number | CDn-Verzeichnis | — | +| track_number | Dateiname-Präfix | Audio-Tag | + +### Was gen_json nicht rekonstruieren kann + +- `disc.disc_id` (physischer TOC-Fingerprint — nicht in Dateien) +- `disc.name` (z.B. "Live in Berlin" — nicht im Dateinamen kodiert) +- `genre` wenn Audio-Tags fehlen + +### Klassik: Komponist vs. Interpret + +Beide Felder werden in den Audio-Tags gespeichert (COMPOSER-Tag bei +FLAC/MP3). `gen_json` liest COMPOSER aus den Tags und kann Komponist +von Interpret trennen — ohne spezielle Namenskonvention im Dateinamen. + +Der Dateinamen-Titel enthält bei Klassik typischerweise Werk + Satz: +``` +01_-_Symphony_No_5_Op_67_I_Allegro_con_brio.flac +``` +Komponist steht im Verzeichnisnamen (`Bach_-_...`) und im COMPOSER-Tag.