Add --except PATTERN option and update documentation
- --except filters albums by directory name (glob or substring, repeatable) - README.md: new options table entries, new cover sources, updated pipeline, corrected test count (33), added batch example - BEDIENUNGSANLEITUNG.md: new options table, sections E (batch+except), F (--status), LASTFM_API_KEY env var, corrected test count Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
cd6c0ae185
commit
0ca05e91d4
3 changed files with 77 additions and 9 deletions
|
|
@ -98,6 +98,9 @@ python3 ~/nvme2n1p7_home/Musik/Music_Metadata_Enricher/music_enricher.py \
|
||||||
| `--no-api` | Keine externen Zugriffe (MusicBrainz, OCR, YouTube) |
|
| `--no-api` | Keine externen Zugriffe (MusicBrainz, OCR, YouTube) |
|
||||||
| `--no-cover` | Kein Cover-Art-Download |
|
| `--no-cover` | Kein Cover-Art-Download |
|
||||||
| `--no-tqdm` | Fortschrittsbalken deaktivieren |
|
| `--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
|
Alben mit Konfidenz unter 0.85 werden übersprungen und müssen manuell
|
||||||
mit `--album` und niedrigerem `--confidence`-Wert bearbeitet werden.
|
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
|
```bash
|
||||||
python3 ~/nvme2n1p7_home/Musik/Music_Metadata_Enricher/music_enricher.py \
|
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 OPENROUTER_API_KEY=sk-or-... # OpenRouter-Fallback (optional)
|
||||||
export ACOUSTID_API_KEY=... # AcoustID-Fingerprinting (optional)
|
export ACOUSTID_API_KEY=... # AcoustID-Fingerprinting (optional)
|
||||||
export DISCOGS_TOKEN=... # Discogs-API (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
|
## Tests ausführen
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python3 ~/nvme2n1p7_home/Musik/Music_Metadata_Enricher/test_suite_enricher.py
|
python3 ~/nvme2n1p7_home/Musik/Music_Metadata_Enricher/test_suite_enricher.py
|
||||||
# 📊 17/17 Tests erfolgreich
|
# 📊 33/33 Tests erfolgreich
|
||||||
```
|
```
|
||||||
|
|
|
||||||
31
README.md
31
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
|
- **OCR Back-Cover** — liest Tracklisten aus `back.jpg` / Booklet-Bildern via
|
||||||
Ollama Vision (qwen3-vl, minicpm-v)
|
Ollama Vision (qwen3-vl, minicpm-v)
|
||||||
- **MusicBrainz-Lookup** — Textsuche + AcoustID-Fingerprinting (optional)
|
- **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
|
- **LLM-Reasoning** — Ollama (lokal, kostenlos) → OpenRouter (DeepSeek V3) für
|
||||||
unklare / widersprüchliche Metadaten
|
unklare / widersprüchliche Metadaten
|
||||||
- **Cover-Art** — lokal (JPG/PNG/WebP) → MusicBrainz Cover Art Archive → einbetten
|
- **Cover-Art** — lokal (JPG/PNG/WebP) → MusicBrainz Cover Art Archive (front + back)
|
||||||
in MP3/FLAC/M4A; WebP wird automatisch zu JPEG konvertiert
|
→ 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,
|
- **Tag-Schreiben** — title, artist, album, albumartist, tracknumber, discnumber,
|
||||||
date, genre, label (mutagen, ID3v2.4)
|
date, genre, label (mutagen, ID3v2.4)
|
||||||
- **Umbenennen** — normiertes Dateinamen-Schema mit Unterstrichen (Pop und Klassik)
|
- **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) | – |
|
| `OPENROUTER_API_KEY` | OpenRouter-Fallback (DeepSeek V3) | – |
|
||||||
| `ACOUSTID_API_KEY` | AcoustID-Fingerprinting | – |
|
| `ACOUSTID_API_KEY` | AcoustID-Fingerprinting | – |
|
||||||
| `DISCOGS_TOKEN` | Discogs-API | – |
|
| `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 |
|
| `--no-cover` | Kein Cover-Art-Download |
|
||||||
| `--album PFAD` | Einzelnes Album verarbeiten |
|
| `--album PFAD` | Einzelnes Album verarbeiten |
|
||||||
| `--no-tqdm` | Fortschrittsanzeige deaktivieren |
|
| `--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 \
|
--embed-cover --rename --backup /tmp/backup \
|
||||||
~/Musik
|
~/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)
|
# Album mit YouTube-IDs in Dateinamen (yt-dlp holt Titel + Kapitel automatisch)
|
||||||
python3 music_enricher.py --auto --confidence 0.1 --rename --embed-cover \
|
python3 music_enricher.py --auto --confidence 0.1 --rename --embed-cover \
|
||||||
--album ~/Musik/Die_Bergvagabunden_Am_Lagefeuer.audio
|
--album ~/Musik/Die_Bergvagabunden_Am_Lagefeuer.audio
|
||||||
|
|
@ -146,11 +162,12 @@ Album-Verzeichnis
|
||||||
3. MetadataResolver
|
3. MetadataResolver
|
||||||
├─ AcoustID → MusicBrainz via Fingerprint
|
├─ AcoustID → MusicBrainz via Fingerprint
|
||||||
├─ Textsuche → MusicBrainz-API
|
├─ Textsuche → MusicBrainz-API
|
||||||
├─ Discogs → Fallback
|
├─ Discogs → Cover + vollständige Trackliste
|
||||||
|
├─ Last.fm → Trackliste-Fallback
|
||||||
└─ LLM-Reasoning → Ollama lokal → OpenRouter (DeepSeek V3)
|
└─ 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
|
5. ReviewStep — interaktiv oder --auto mit Konfidenz-Schwellwert
|
||||||
|
|
@ -205,7 +222,7 @@ Music_Metadata_Enricher/
|
||||||
├── metadata_resolver.py MusicBrainz + Discogs + Ollama/OpenRouter LLM
|
├── metadata_resolver.py MusicBrainz + Discogs + Ollama/OpenRouter LLM
|
||||||
├── cover_handler.py Cover-Art: Suche, WebP-Konvertierung, Download, Einbettung
|
├── cover_handler.py Cover-Art: Suche, WebP-Konvertierung, Download, Einbettung
|
||||||
├── executor.py Backup, Tag-Schreiben, Umbenennen, M3U, CSV-Report
|
├── 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
|
```bash
|
||||||
python3 test_suite_enricher.py
|
python3 test_suite_enricher.py
|
||||||
# 📊 17/17 Tests erfolgreich
|
# 📊 33/33 Tests erfolgreich
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ Pipeline pro Album:
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
import fnmatch
|
||||||
import importlib.util
|
import importlib.util
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
@ -361,6 +362,10 @@ def main() -> None:
|
||||||
help="Bibliotheksstatus anzeigen (fehlende Cover, schlechte Tags) — nichts schreiben")
|
help="Bibliotheksstatus anzeigen (fehlende Cover, schlechte Tags) — nichts schreiben")
|
||||||
parser.add_argument("--skip-complete", action="store_true", dest="skip_complete",
|
parser.add_argument("--skip-complete", action="store_true", dest="skip_complete",
|
||||||
help="Alben überspringen die bereits Cover + gute Tags haben")
|
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()
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
|
@ -402,6 +407,20 @@ def main() -> None:
|
||||||
skipped_upfront = before - len(album_dirs)
|
skipped_upfront = before - len(album_dirs)
|
||||||
print(f"⏭️ {skipped_upfront}/{before} Alben bereits vollständig — übersprungen.")
|
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.")
|
print(f"🎵 {len(album_dirs)} Album-Verzeichnisse gefunden.")
|
||||||
if os.getenv("OLLAMA_HOST") or True: # Ollama always attempted
|
if os.getenv("OLLAMA_HOST") or True: # Ollama always attempted
|
||||||
print("🤖 LLM-Resolve: Ollama → OpenRouter (kein Claude)")
|
print("🤖 LLM-Resolve: Ollama → OpenRouter (kein Claude)")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue