2026-04-29 05:26:59 +02:00
|
|
|
|
# Music Metadata Enricher
|
|
|
|
|
|
|
|
|
|
|
|
KI-gestützter Musik-Metadaten-Enricher für Jellyfin-Bibliotheken.
|
|
|
|
|
|
|
|
|
|
|
|
Analysiert Album-Verzeichnisse, vervollständigt Tags, besorgt Cover-Art und
|
|
|
|
|
|
benennt Dateien optional nach einem einheitlichen Schema um — vollständig ohne
|
2026-04-29 06:03:39 +02:00
|
|
|
|
API-Key nutzbar, mit lokalem LLM (Ollama) für lückenhafte Metadaten.
|
2026-04-29 05:26:59 +02:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Features
|
|
|
|
|
|
|
|
|
|
|
|
- **Lokale Analyse** — Verzeichnisname, Dateinamen, bestehende ID3/FLAC/M4A-Tags,
|
|
|
|
|
|
Tracklist-Textdateien (.txt, .htm, .html)
|
2026-04-29 06:03:39 +02:00
|
|
|
|
- **YouTube-ID-Erkennung** — erkennt YouTube-Video-IDs im Dateinamen, holt Titel,
|
|
|
|
|
|
Uploader und Kapitel automatisch via `yt-dlp`
|
|
|
|
|
|
- **OCR Back-Cover** — liest Tracklisten aus `back.jpg` / Booklet-Bildern via
|
|
|
|
|
|
Ollama Vision (qwen3-vl, minicpm-v)
|
2026-04-29 05:26:59 +02:00
|
|
|
|
- **MusicBrainz-Lookup** — Textsuche + AcoustID-Fingerprinting (optional)
|
2026-04-29 11:38:52 +02:00
|
|
|
|
- **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)
|
2026-04-29 06:03:39 +02:00
|
|
|
|
- **LLM-Reasoning** — Ollama (lokal, kostenlos) → OpenRouter (DeepSeek V3) für
|
|
|
|
|
|
unklare / widersprüchliche Metadaten
|
2026-04-29 11:38:52 +02:00
|
|
|
|
- **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
|
2026-04-29 05:26:59 +02:00
|
|
|
|
- **Tag-Schreiben** — title, artist, album, albumartist, tracknumber, discnumber,
|
|
|
|
|
|
date, genre, label (mutagen, ID3v2.4)
|
2026-04-29 06:03:39 +02:00
|
|
|
|
- **Umbenennen** — normiertes Dateinamen-Schema mit Unterstrichen (Pop und Klassik)
|
|
|
|
|
|
- **M3U-Playlist** — wird nach dem Umbenennen automatisch neu generiert
|
2026-04-29 05:26:59 +02:00
|
|
|
|
- **Backup** — Sicherungskopien vor jeder Änderung
|
|
|
|
|
|
- **CSV-Report** — vollständiges Protokoll aller Änderungen
|
|
|
|
|
|
- **Interaktiver / Auto-Modus** — mit Konfidenz-Schwellwert
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Voraussetzungen
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
pip install mutagen musicbrainzngs pyacoustid discogs_client \
|
2026-04-29 06:03:39 +02:00
|
|
|
|
Pillow requests tqdm beautifulsoup4
|
2026-04-29 05:26:59 +02:00
|
|
|
|
sudo apt install libchromaprint-tools # fpcalc für AcoustID-Fingerprinting
|
2026-04-29 06:03:39 +02:00
|
|
|
|
sudo apt install yt-dlp # YouTube-Metadaten (optional)
|
2026-04-29 05:26:59 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
| Paket | Zweck | Optional? |
|
|
|
|
|
|
|-------|-------|-----------|
|
|
|
|
|
|
| `mutagen` | Tags lesen/schreiben | nein |
|
|
|
|
|
|
| `musicbrainzngs` | MusicBrainz-API | ja |
|
|
|
|
|
|
| `pyacoustid` | AcoustID-Fingerprinting | ja |
|
|
|
|
|
|
| `discogs_client` | Discogs-API | ja |
|
2026-04-29 06:03:39 +02:00
|
|
|
|
| `Pillow` | Cover-Bildgröße prüfen, WebP→JPEG | ja |
|
2026-04-29 05:26:59 +02:00
|
|
|
|
| `requests` | Cover-Art-Download | ja |
|
|
|
|
|
|
| `tqdm` | Fortschrittsbalken | ja |
|
2026-04-29 06:03:39 +02:00
|
|
|
|
| `beautifulsoup4` | HTML-Tracklisten parsen | ja |
|
2026-04-29 05:26:59 +02:00
|
|
|
|
| `fpcalc` | Audio-Fingerprinting-Binary | ja |
|
2026-04-29 06:03:39 +02:00
|
|
|
|
| `yt-dlp` | YouTube-Metadaten + Kapitel | ja |
|
|
|
|
|
|
|
|
|
|
|
|
Für LLM-Reasoning wird ein laufender **Ollama**-Server erwartet (Port 11434).
|
|
|
|
|
|
Empfohlenes Modell: `qwen3:8b` (5 GB). OCR: `qwen3-vl:latest`.
|
2026-04-29 05:26:59 +02:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Umgebungsvariablen (optional)
|
|
|
|
|
|
|
2026-04-29 06:03:39 +02:00
|
|
|
|
| Variable | Beschreibung | Default |
|
|
|
|
|
|
|----------|-------------|---------|
|
|
|
|
|
|
| `OLLAMA_HOST` | Ollama-Server-URL | `http://localhost:11434` |
|
|
|
|
|
|
| `OLLAMA_RESOLVE_MODEL` | LLM für Metadaten-Reasoning | `qwen3:8b` |
|
|
|
|
|
|
| `OLLAMA_OCR_MODEL` | Vision-Modelle für OCR (kommagetrennt) | `qwen3-vl:latest,minicpm-v:latest,deepseek-ocr:latest` |
|
|
|
|
|
|
| `OPENROUTER_API_KEY` | OpenRouter-Fallback (DeepSeek V3) | – |
|
|
|
|
|
|
| `ACOUSTID_API_KEY` | AcoustID-Fingerprinting | – |
|
|
|
|
|
|
| `DISCOGS_TOKEN` | Discogs-API | – |
|
2026-04-29 11:38:52 +02:00
|
|
|
|
| `LASTFM_API_KEY` | Last.fm Cover + Tracklist-Fallback | – |
|
2026-04-29 05:26:59 +02:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## CLI
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
music_enricher.py [Optionen] PFAD [PFAD ...]
|
|
|
|
|
|
music_enricher.py --album PFAD [Optionen]
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
| Option | Beschreibung |
|
|
|
|
|
|
|--------|-------------|
|
|
|
|
|
|
| `--dry-run` | Vorschläge anzeigen, nichts schreiben |
|
|
|
|
|
|
| `--auto` | Kein interaktiver Review-Schritt |
|
|
|
|
|
|
| `--confidence FLOAT` | Min-Konfidenz für `--auto` (default: 0.85) |
|
|
|
|
|
|
| `--rename` | Dateien nach Schema umbenennen |
|
|
|
|
|
|
| `--embed-cover` | Cover-Art in Audiodatei einbetten |
|
|
|
|
|
|
| `--backup PFAD` | Backup-Verzeichnis vor Änderungen |
|
|
|
|
|
|
| `--report PFAD` | CSV-Report der Änderungen |
|
|
|
|
|
|
| `--no-fingerprint` | AcoustID-Fingerprinting überspringen |
|
2026-04-29 06:03:39 +02:00
|
|
|
|
| `--no-api` | Keine externen API-Calls (MusicBrainz, Discogs, OCR) |
|
2026-04-29 05:26:59 +02:00
|
|
|
|
| `--no-cover` | Kein Cover-Art-Download |
|
|
|
|
|
|
| `--album PFAD` | Einzelnes Album verarbeiten |
|
|
|
|
|
|
| `--no-tqdm` | Fortschrittsanzeige deaktivieren |
|
2026-04-29 11:38:52 +02:00
|
|
|
|
| `--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) |
|
2026-04-29 05:26:59 +02:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Verwendung
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
2026-04-29 06:03:39 +02:00
|
|
|
|
# Einzelnes Album — Dry-Run, nur lokale Analyse
|
2026-04-29 05:26:59 +02:00
|
|
|
|
python3 music_enricher.py --dry-run --no-api \
|
|
|
|
|
|
--album ~/Musik/Abba_-_Greatest_Hits
|
|
|
|
|
|
|
2026-04-29 06:03:39 +02:00
|
|
|
|
# Mit MusicBrainz-Lookup und Ollama-LLM
|
2026-04-29 05:26:59 +02:00
|
|
|
|
python3 music_enricher.py --dry-run \
|
|
|
|
|
|
--album ~/Musik/Bach_Organ_-_Peter_Hurford
|
|
|
|
|
|
|
|
|
|
|
|
# Vollständig: Tags + Cover einbetten + umbenennen, mit Backup und Report
|
|
|
|
|
|
python3 music_enricher.py --embed-cover --rename \
|
|
|
|
|
|
--backup /tmp/musik_backup \
|
|
|
|
|
|
--report report.csv \
|
|
|
|
|
|
~/Musik
|
|
|
|
|
|
|
|
|
|
|
|
# Auto-Modus: nur Vorschläge ≥ 90% Konfidenz anwenden
|
|
|
|
|
|
python3 music_enricher.py --auto --confidence 0.90 \
|
2026-04-29 06:03:39 +02:00
|
|
|
|
--embed-cover --rename --backup /tmp/backup \
|
2026-04-29 05:26:59 +02:00
|
|
|
|
~/Musik
|
|
|
|
|
|
|
2026-04-29 11:38:52 +02:00
|
|
|
|
# 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
|
|
|
|
|
|
|
2026-04-29 06:03:39 +02:00
|
|
|
|
# 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
|
2026-04-29 05:26:59 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Verarbeitungs-Pipeline
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
Album-Verzeichnis
|
|
|
|
|
|
│
|
|
|
|
|
|
▼
|
2026-04-29 06:03:39 +02:00
|
|
|
|
1. AlbumScanner — Dateitypen klassifizieren (Audio, Bild, Tracklist, Playlist)
|
2026-04-29 05:26:59 +02:00
|
|
|
|
│
|
|
|
|
|
|
▼
|
|
|
|
|
|
2. HintExtractor — lokal, keine API
|
|
|
|
|
|
├─ Verzeichnisname → Artist, Album, Jahr
|
2026-04-29 06:03:39 +02:00
|
|
|
|
├─ Dateinamen → Tracknummer, Artist, Titel (YouTube-ID wird entfernt)
|
2026-04-29 05:26:59 +02:00
|
|
|
|
├─ ID3/FLAC-Tags → bestehende Werte
|
2026-04-29 06:03:39 +02:00
|
|
|
|
├─ Tracklist .txt/.htm → Tracklisten parsen (4-stufiges Matching)
|
|
|
|
|
|
├─ YouTube-IDs → Titel, Uploader, Kapitel via yt-dlp
|
|
|
|
|
|
└─ OCR Back-Cover → Tracklist aus back.jpg via Ollama Vision
|
2026-04-29 05:26:59 +02:00
|
|
|
|
│
|
|
|
|
|
|
▼
|
|
|
|
|
|
3. MetadataResolver
|
|
|
|
|
|
├─ AcoustID → MusicBrainz via Fingerprint
|
|
|
|
|
|
├─ Textsuche → MusicBrainz-API
|
2026-04-29 11:38:52 +02:00
|
|
|
|
├─ Discogs → Cover + vollständige Trackliste
|
|
|
|
|
|
├─ Last.fm → Trackliste-Fallback
|
2026-04-29 06:03:39 +02:00
|
|
|
|
└─ LLM-Reasoning → Ollama lokal → OpenRouter (DeepSeek V3)
|
2026-04-29 05:26:59 +02:00
|
|
|
|
│
|
|
|
|
|
|
▼
|
2026-04-29 11:38:52 +02:00
|
|
|
|
4. CoverHandler — lokal → MusicBrainz CAA (front+back) → Discogs → iTunes → Last.fm → einbetten
|
2026-04-29 05:26:59 +02:00
|
|
|
|
│
|
|
|
|
|
|
▼
|
2026-04-29 06:03:39 +02:00
|
|
|
|
5. ReviewStep — interaktiv oder --auto mit Konfidenz-Schwellwert
|
2026-04-29 05:26:59 +02:00
|
|
|
|
│
|
|
|
|
|
|
▼
|
2026-04-29 06:03:39 +02:00
|
|
|
|
6. Executor — Backup → Tags → Cover → Umbenennen → M3U → Report
|
2026-04-29 05:26:59 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Konfidenz-Modell
|
|
|
|
|
|
|
|
|
|
|
|
| Quelle | Bonus |
|
|
|
|
|
|
|--------|-------|
|
|
|
|
|
|
| AcoustID-Match ≥ 90% | +0.20 |
|
|
|
|
|
|
| MusicBrainz via Fingerprint | +0.25 |
|
|
|
|
|
|
| MusicBrainz-Texttreffer (Score/100) | +0.30 × Score |
|
|
|
|
|
|
| Discogs | +0.15 |
|
2026-04-29 06:03:39 +02:00
|
|
|
|
| LLM-Reasoning (Ollama/OpenRouter) | +0.10 |
|
|
|
|
|
|
| Lokale Hints (Ordner-/Dateiname, YouTube) | +0.05 |
|
2026-04-29 05:26:59 +02:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Dateinamen-Schema (mit `--rename`)
|
|
|
|
|
|
|
2026-04-29 06:03:39 +02:00
|
|
|
|
**Pop/Default** — Leerzeichen als Unterstriche, Teile durch `_-_` getrennt:
|
|
|
|
|
|
```
|
|
|
|
|
|
01_-_ABBA_-_Dancing_Queen.mp3
|
|
|
|
|
|
2-07_-_Die_Bergvagabunden_-_Hohe_Tannen.flac
|
2026-04-29 05:26:59 +02:00
|
|
|
|
```
|
2026-04-29 06:03:39 +02:00
|
|
|
|
|
|
|
|
|
|
**Klassik** — Performer und Komponist explizit getrennt:
|
2026-04-29 05:26:59 +02:00
|
|
|
|
```
|
2026-04-29 06:03:39 +02:00
|
|
|
|
01_-_Peter_Hurford_-_Johann_Sebastian_Bach_-_Toccata_And_Fugue_In_D_Minor_BWV_565.mp3
|
|
|
|
|
|
01_-_Gardiner_-_Bach_-_Kantate_BWV_147_-_English_Baroque_Soloists.flac
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Schema: `[D-]TT_-_Performer_-_Komponist_-_Werk[_-_Orchester_Dirigent].ext`
|
2026-04-29 05:26:59 +02:00
|
|
|
|
|
2026-04-29 06:03:39 +02:00
|
|
|
|
Bei Single-Disc entfällt die Disc-Nummer. Bei Multi-CD wird sie immer gesetzt (`1-01`, `2-01` etc.).
|
2026-04-29 05:26:59 +02:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Projektstruktur
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
Music_Metadata_Enricher/
|
|
|
|
|
|
├── music_enricher.py Haupt-CLI, Pipeline-Orchestrierung
|
|
|
|
|
|
├── models.py Dataclasses: AlbumScan, AlbumHints, TrackProposal, …
|
|
|
|
|
|
├── scanner.py Dateisystem-Scanner, Typ-Klassifikation
|
2026-04-29 06:03:39 +02:00
|
|
|
|
├── hint_extractor.py Dateiname/Tag/Tracklist/OCR/YouTube-Auswertung
|
|
|
|
|
|
├── 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
|
2026-04-29 11:38:52 +02:00
|
|
|
|
└── test_suite_enricher.py 33 Unit-/Integrationstests
|
2026-04-29 05:26:59 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Tests
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
python3 test_suite_enricher.py
|
2026-04-29 11:38:52 +02:00
|
|
|
|
# 📊 33/33 Tests erfolgreich
|
2026-04-29 05:26:59 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Unterstützte Formate
|
|
|
|
|
|
|
|
|
|
|
|
| Format | Tags | Cover-Einbettung |
|
|
|
|
|
|
|--------|------|-----------------|
|
|
|
|
|
|
| MP3 | ID3v2.4 (EasyID3) | APIC-Frame |
|
|
|
|
|
|
| FLAC | Vorbis-Comments | METADATA_BLOCK_PICTURE |
|
|
|
|
|
|
| M4A/AAC | MP4-Tags | covr-Atom |
|
|
|
|
|
|
| Sonstige | mutagen generic | – |
|