fix: korrekte Track-Nummerierung, Scanner-Rekursion, M3U-Reihenfolge
scanner: nicht in Unterordner wenn Root Audio-Dateien enthält (verhindert Doppel-Scan bei versehentlichen Unterordner-Kopien); nur Disc-Ordner (CD1, Disc 2…) werden bei Multi-CD-Alben rekursiert. hint_extractor: M3U/Playlist-Dateien als Track-Reihenfolge-Quelle; BOM- Bereinigung; Tracklist-Matching auch per Titel (nicht nur per Nummer); tracknumber=0 wird als 'keine Nummer' gewertet. metadata_resolver: sequenzielle Fallback-Nummerierung (1,2,3…) für Tracks ohne Tracknummer — verhindert '00'-Präfix beim --rename; dir_artist hat Vorrang vor 'Various Artists'-Heuristik; LLM darf bei Konfidenz <0.3 auch bestehende Werte korrigieren (Tippfehler im Verzeichnisnamen). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
c205fa8943
commit
d91eb36007
4 changed files with 189 additions and 48 deletions
|
|
@ -40,8 +40,8 @@ OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY", "")
|
|||
DISCOGS_TOKEN = os.getenv("DISCOGS_TOKEN", "")
|
||||
OLLAMA_HOST = os.getenv("OLLAMA_HOST", "http://localhost:11434")
|
||||
|
||||
# Lokales Reasoning-Modell für Metadaten-Ergänzung (passt auf RTX 3090)
|
||||
OLLAMA_RESOLVE_MODEL = os.getenv("OLLAMA_RESOLVE_MODEL", "qwen3.5:27b")
|
||||
# qwen3:8b (5.2GB) reicht für einfache JSON-Metadaten-Ergänzung und lädt schnell (~10s)
|
||||
OLLAMA_RESOLVE_MODEL = os.getenv("OLLAMA_RESOLVE_MODEL", "qwen3:8b")
|
||||
|
||||
|
||||
def _mb_wait():
|
||||
|
|
@ -184,10 +184,12 @@ def _build_resolve_prompt(hints: AlbumHints, partial: Dict) -> str:
|
|||
for t in hints.tracks[:20]
|
||||
)
|
||||
return (
|
||||
"Du bist ein Musikexperte. Analysiere diese Album-Daten und vervollständige die fehlenden Felder.\n\n"
|
||||
"Du bist ein Musikexperte. Analysiere diese Album-Daten.\n"
|
||||
"Vervollständige fehlende Felder UND korrigiere erkennbare Tippfehler "
|
||||
"(z.B. im Albumtitel oder Künstlernamen — Verzeichnisnamen enthalten oft Schreibfehler).\n\n"
|
||||
f"Verzeichnisname: {hints.album_dir.name}\n"
|
||||
f"Bekannte Artist: {hints.dir_artist or partial.get('artist', 'unbekannt')}\n"
|
||||
f"Bekannter Albumtitel: {hints.dir_album or partial.get('album', 'unbekannt')}\n"
|
||||
f"Künstler (aus Verzeichnis): {hints.dir_artist or partial.get('artist', 'unbekannt')}\n"
|
||||
f"Albumtitel (aus Verzeichnis, evtl. mit Tippfehlern): {hints.dir_album or partial.get('album', 'unbekannt')}\n"
|
||||
f"Jahr: {hints.dir_year or partial.get('year', 'unbekannt')}\n"
|
||||
f"Tracklist-Hinweise:\n{tracks_summary}\n\n"
|
||||
'Antworte NUR mit einem JSON-Objekt mit diesen Feldern (null wenn unbekannt):\n'
|
||||
|
|
@ -224,7 +226,7 @@ def _resolve_via_ollama(hints: AlbumHints, partial: Dict) -> Optional[Dict]:
|
|||
headers={"Content-Type": "application/json"},
|
||||
method="POST",
|
||||
)
|
||||
with urllib.request.urlopen(req, timeout=120) as resp:
|
||||
with urllib.request.urlopen(req, timeout=240) as resp:
|
||||
data = json.loads(resp.read())
|
||||
text = data.get("message", {}).get("content", "").strip()
|
||||
return _parse_json_response(text)
|
||||
|
|
@ -422,19 +424,34 @@ def resolve(
|
|||
if not artist or not album or confidence < 0.5:
|
||||
cl = _claude_resolve(hints, partial)
|
||||
if cl:
|
||||
artist = artist or cl.get("artist")
|
||||
album = album or cl.get("album")
|
||||
year = year or cl.get("year")
|
||||
genre = genre or cl.get("genre")
|
||||
label = label or cl.get("label")
|
||||
if confidence < 0.3:
|
||||
# Sehr unsicher: LLM darf auch bestehende Werte korrigieren
|
||||
# (z.B. Tippfehler im Albumtitel aus dem Verzeichnisnamen)
|
||||
artist = cl.get("artist") or artist
|
||||
album = cl.get("album") or album
|
||||
year = cl.get("year") or year
|
||||
genre = cl.get("genre") or genre
|
||||
label = cl.get("label") or label
|
||||
else:
|
||||
artist = artist or cl.get("artist")
|
||||
album = album or cl.get("album")
|
||||
year = year or cl.get("year")
|
||||
genre = genre or cl.get("genre")
|
||||
label = label or cl.get("label")
|
||||
confidence += 0.10
|
||||
sources.append("llm-resolve")
|
||||
|
||||
# Finalize albumartist
|
||||
# dir_artist hat Vorrang: wenn der Verzeichnisname einen Künstler nennt
|
||||
# (z.B. "Eugen_Cicero_-_Jazz_meets_Classic"), ist das der Albumkünstler —
|
||||
# auch wenn die Track-Dateinamen die Komponisten-Namen enthalten.
|
||||
track_artists = [t.artist for t in hints.tracks if t.artist]
|
||||
from collections import Counter
|
||||
distinct_artists = set(a for a in track_artists if a)
|
||||
if len(distinct_artists) >= 3:
|
||||
if hints.dir_artist:
|
||||
# Verzeichnisname nennt explizit einen Künstler → immer verwenden
|
||||
albumartist = hints.dir_artist
|
||||
elif len(distinct_artists) >= 3:
|
||||
albumartist = "Various Artists"
|
||||
elif track_artists:
|
||||
albumartist = artist or Counter(track_artists).most_common(1)[0][0]
|
||||
|
|
@ -500,4 +517,15 @@ def _build_track_proposals(
|
|||
mbid=None,
|
||||
))
|
||||
|
||||
# Sequenzielle Nummerierung als letzter Fallback:
|
||||
# Tracks ohne Nummer (None) erhalten eine laufende Nummer pro Disc.
|
||||
# Damit werden "00" und "??" im Dateinamen beim --rename verhindert.
|
||||
if any(p.track_number is None for p in proposals):
|
||||
disc_counters: Dict[int, int] = {}
|
||||
for p in proposals:
|
||||
if p.track_number is None:
|
||||
disc = p.disc_number or 1
|
||||
disc_counters[disc] = disc_counters.get(disc, 0) + 1
|
||||
p.track_number = disc_counters[disc]
|
||||
|
||||
return proposals
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue