diff --git a/BEDIENUNGSANLEITUNG.md b/BEDIENUNGSANLEITUNG.md index 7f57d8a..153a881 100644 --- a/BEDIENUNGSANLEITUNG.md +++ b/BEDIENUNGSANLEITUNG.md @@ -98,6 +98,9 @@ python3 ~/nvme2n1p7_home/Musik/Music_Metadata_Enricher/music_enricher.py \ | `--no-api` | Keine externen Zugriffe (MusicBrainz, OCR, YouTube) | | `--no-cover` | Kein Cover-Art-Download | | `--no-tqdm` | Fortschrittsbalken deaktivieren | +| `--status` | Bibliotheksstatus anzeigen (fehlende Cover, schlechte Tags) — nichts schreiben | +| `--skip-complete` | Alben überspringen, die bereits Cover + gute Tags haben | +| `--except PATTERN` | Album ausschließen, dessen Verzeichnisname das Muster enthält (Glob oder Substring, mehrfach verwendbar) | --- @@ -156,7 +159,33 @@ python3 ~/nvme2n1p7_home/Musik/Music_Metadata_Enricher/music_enricher.py \ Alben mit Konfidenz unter 0.85 werden übersprungen und müssen manuell mit `--album` und niedrigerem `--confidence`-Wert bearbeitet werden. -### E) Interaktiver Modus (ohne --auto) +### E) Batch-Lauf mit Ausschlüssen und Überspringen bereits fertiger Alben + +```bash +python3 ~/nvme2n1p7_home/Musik/Music_Metadata_Enricher/music_enricher.py \ + --auto --confidence 0.5 --rename --embed-cover --no-fingerprint \ + --skip-complete \ + --except 'Eigene_Aufnahmen*' \ + --except Hoerbuch \ + --backup /tmp/backup \ + ~/nvme2n1p7_home/Musik +``` + +- `--skip-complete` überspringt Alben, die bereits Cover und gute Tags haben. +- `--except` schließt Alben anhand des Verzeichnisnamens aus. + Glob-Muster (`*`, `?`) und einfache Substrings werden beide unterstützt. + Die Option kann mehrfach angegeben werden. + +### F) Bibliotheksstatus anzeigen + +```bash +python3 ~/nvme2n1p7_home/Musik/Music_Metadata_Enricher/music_enricher.py \ + --status ~/nvme2n1p7_home/Musik +``` + +Zeigt alle Alben mit fehlenden Covern oder schlechten Tags — ohne etwas zu schreiben. + +### G) Interaktiver Modus (ohne --auto) ```bash python3 ~/nvme2n1p7_home/Musik/Music_Metadata_Enricher/music_enricher.py \ @@ -251,13 +280,16 @@ export OLLAMA_OCR_MODEL=qwen3-vl:latest # Vision-Modell für OCR export OPENROUTER_API_KEY=sk-or-... # OpenRouter-Fallback (optional) export ACOUSTID_API_KEY=... # AcoustID-Fingerprinting (optional) export DISCOGS_TOKEN=... # Discogs-API (optional) +export LASTFM_API_KEY=... # Last.fm Cover + Tracklist (optional) ``` +Kostenloser Last.fm API-Key: https://www.last.fm/api/account/create + --- ## Tests ausführen ```bash python3 ~/nvme2n1p7_home/Musik/Music_Metadata_Enricher/test_suite_enricher.py -# 📊 17/17 Tests erfolgreich +# 📊 33/33 Tests erfolgreich ``` diff --git a/README.md b/README.md index e8a6803..c694c05 100644 --- a/README.md +++ b/README.md @@ -17,11 +17,14 @@ API-Key nutzbar, mit lokalem LLM (Ollama) für lückenhafte Metadaten. - **OCR Back-Cover** — liest Tracklisten aus `back.jpg` / Booklet-Bildern via Ollama Vision (qwen3-vl, minicpm-v) - **MusicBrainz-Lookup** — Textsuche + AcoustID-Fingerprinting (optional) -- **Discogs-Fallback** — bei MusicBrainz-Misses +- **Discogs-Fallback** — Cover + vollständige Trackliste bei MusicBrainz-Misses +- **iTunes Search API** — Cover-Fallback (gratis, kein Key) +- **Last.fm** — Cover + Tracklist-Fallback (kostenloser API-Key) - **LLM-Reasoning** — Ollama (lokal, kostenlos) → OpenRouter (DeepSeek V3) für unklare / widersprüchliche Metadaten -- **Cover-Art** — lokal (JPG/PNG/WebP) → MusicBrainz Cover Art Archive → einbetten - in MP3/FLAC/M4A; WebP wird automatisch zu JPEG konvertiert +- **Cover-Art** — lokal (JPG/PNG/WebP) → MusicBrainz Cover Art Archive (front + back) + → Discogs → iTunes Search API → Last.fm; einbetten in MP3/FLAC/M4A; + WebP wird automatisch zu JPEG konvertiert, Cover immer als `folder.jpg` gespeichert - **Tag-Schreiben** — title, artist, album, albumartist, tracknumber, discnumber, date, genre, label (mutagen, ID3v2.4) - **Umbenennen** — normiertes Dateinamen-Schema mit Unterstrichen (Pop und Klassik) @@ -69,6 +72,7 @@ Empfohlenes Modell: `qwen3:8b` (5 GB). OCR: `qwen3-vl:latest`. | `OPENROUTER_API_KEY` | OpenRouter-Fallback (DeepSeek V3) | – | | `ACOUSTID_API_KEY` | AcoustID-Fingerprinting | – | | `DISCOGS_TOKEN` | Discogs-API | – | +| `LASTFM_API_KEY` | Last.fm Cover + Tracklist-Fallback | – | --- @@ -93,6 +97,9 @@ music_enricher.py --album PFAD [Optionen] | `--no-cover` | Kein Cover-Art-Download | | `--album PFAD` | Einzelnes Album verarbeiten | | `--no-tqdm` | Fortschrittsanzeige deaktivieren | +| `--status` | Bibliotheksstatus anzeigen (fehlende Cover, schlechte Tags) | +| `--skip-complete` | Alben mit Cover + guten Tags überspringen (Batch-Optimierung) | +| `--except PATTERN` | Album ausschließen (Glob oder Substring, mehrfach verwendbar) | --- @@ -118,6 +125,15 @@ python3 music_enricher.py --auto --confidence 0.90 \ --embed-cover --rename --backup /tmp/backup \ ~/Musik +# Batch-Lauf: vollständige Alben überspringen, bestimmte ausschließen +python3 music_enricher.py --auto --confidence 0.5 --rename --embed-cover \ + --no-fingerprint --skip-complete \ + --except 'Abba*' --except Eigene_Aufnahmen \ + ~/Musik + +# Bibliotheksstatus anzeigen (kein Schreiben) +python3 music_enricher.py --status ~/Musik + # Album mit YouTube-IDs in Dateinamen (yt-dlp holt Titel + Kapitel automatisch) python3 music_enricher.py --auto --confidence 0.1 --rename --embed-cover \ --album ~/Musik/Die_Bergvagabunden_Am_Lagefeuer.audio @@ -146,11 +162,12 @@ Album-Verzeichnis 3. MetadataResolver ├─ AcoustID → MusicBrainz via Fingerprint ├─ Textsuche → MusicBrainz-API - ├─ Discogs → Fallback + ├─ Discogs → Cover + vollständige Trackliste + ├─ Last.fm → Trackliste-Fallback └─ LLM-Reasoning → Ollama lokal → OpenRouter (DeepSeek V3) │ ▼ -4. CoverHandler — lokal (JPG/PNG/WebP) → MusicBrainz → einbetten +4. CoverHandler — lokal → MusicBrainz CAA (front+back) → Discogs → iTunes → Last.fm → einbetten │ ▼ 5. ReviewStep — interaktiv oder --auto mit Konfidenz-Schwellwert @@ -205,7 +222,7 @@ Music_Metadata_Enricher/ ├── metadata_resolver.py MusicBrainz + Discogs + Ollama/OpenRouter LLM ├── cover_handler.py Cover-Art: Suche, WebP-Konvertierung, Download, Einbettung ├── executor.py Backup, Tag-Schreiben, Umbenennen, M3U, CSV-Report -└── test_suite_enricher.py 17 Unit-/Integrationstests +└── test_suite_enricher.py 33 Unit-/Integrationstests ``` --- @@ -214,7 +231,7 @@ Music_Metadata_Enricher/ ```bash python3 test_suite_enricher.py -# 📊 17/17 Tests erfolgreich +# 📊 33/33 Tests erfolgreich ``` --- diff --git a/music_enricher.py b/music_enricher.py index d14703f..3d846f3 100755 --- a/music_enricher.py +++ b/music_enricher.py @@ -10,6 +10,7 @@ Pipeline pro Album: from __future__ import annotations import argparse +import fnmatch import importlib.util import os import sys @@ -361,6 +362,10 @@ def main() -> None: help="Bibliotheksstatus anzeigen (fehlende Cover, schlechte Tags) — nichts schreiben") parser.add_argument("--skip-complete", action="store_true", dest="skip_complete", help="Alben überspringen die bereits Cover + gute Tags haben") + parser.add_argument("--except", action="append", dest="exclude_patterns", + metavar="PATTERN", default=[], + help="Album ausschließen dessen Verzeichnisname das Muster enthält\n" + "(Glob oder Substring, mehrfach verwendbar, z.B. --except 'Abba*')") args = parser.parse_args() @@ -402,6 +407,20 @@ def main() -> None: skipped_upfront = before - len(album_dirs) print(f"⏭️ {skipped_upfront}/{before} Alben bereits vollständig — übersprungen.") + # --except: Alben nach Namensmuster ausschließen + if args.exclude_patterns: + before_exc = len(album_dirs) + def _is_excluded(d: Path) -> bool: + name = d.name + return any( + fnmatch.fnmatch(name, pat) or pat in name + for pat in args.exclude_patterns + ) + album_dirs = [d for d in album_dirs if not _is_excluded(d)] + excluded_count = before_exc - len(album_dirs) + patterns_str = ", ".join(repr(p) for p in args.exclude_patterns) + print(f"🚫 {excluded_count}/{before_exc} Alben ausgeschlossen ({patterns_str}).") + print(f"🎵 {len(album_dirs)} Album-Verzeichnisse gefunden.") if os.getenv("OLLAMA_HOST") or True: # Ollama always attempted print("🤖 LLM-Resolve: Ollama → OpenRouter (kein Claude)")