feat: real mutagen tagger and MusicBrainz tracklist

This commit is contained in:
Jonathan
2026-07-10 23:52:19 +02:00
parent 1c02a870f3
commit 32deac65a6
5 changed files with 103 additions and 0 deletions
+3
View File
@@ -47,6 +47,7 @@ class MusicBrainzResolver:
track_count = None
total_duration_s = None
titles: tuple[str, ...] = ()
rgid = rg.get("id")
if rgid:
rgfull = musicbrainzngs.get_release_group_by_id(rgid, includes=["releases"])
@@ -62,6 +63,7 @@ class MusicBrainzResolver:
track_count = len(tracks)
total_ms = sum(int((t.get("recording") or {}).get("length") or 0) for t in tracks)
total_duration_s = total_ms // 1000 if total_ms else None
titles = tuple((t.get("recording") or {}).get("title", "") for t in tracks)
return MBTarget(
artist=canonical_artist,
@@ -69,4 +71,5 @@ class MusicBrainzResolver:
track_count=track_count,
total_duration_s=total_duration_s,
year=year,
tracklist=titles,
)
+48
View File
@@ -0,0 +1,48 @@
import os
from lyra_worker.library import safe_name
_AUDIO_EXT = {".flac", ".mp3", ".m4a", ".opus", ".ogg", ".aac", ".wav"}
def plan_track_names(names: list[str], tracklist: tuple[str, ...]) -> list[tuple[str, str, str, int]]:
"""Map audio filenames -> (old_name, new_name, title, track_number).
Files are sorted; titles come from `tracklist` by position when available,
else the filename stem. Pure — no filesystem or tag I/O.
"""
plan: list[tuple[str, str, str, int]] = []
audio = sorted(n for n in names if os.path.splitext(n)[1].lower() in _AUDIO_EXT)
for i, name in enumerate(audio):
ext = os.path.splitext(name)[1].lower()
title = tracklist[i] if i < len(tracklist) else os.path.splitext(name)[0]
new_name = f"{i + 1:02d} {safe_name(title)}{ext}"
plan.append((name, new_name, title, i + 1))
return plan
class MutagenTagger:
"""Real Tagger using mutagen (lazily imported). The tag-write is not unit-tested
offline (needs real audio); the naming logic (`plan_track_names`) is."""
def tag_album(self, album_dir: str, target) -> None:
import mutagen
names = os.listdir(album_dir)
for old_name, new_name, title, number in plan_track_names(names, target.tracklist):
path = os.path.join(album_dir, old_name)
audio = mutagen.File(path, easy=True)
if audio is None: # unreadable/unsupported file — leave it untouched
continue
audio["artist"] = target.artist
audio["albumartist"] = target.artist
audio["album"] = target.album
audio["title"] = title
audio["tracknumber"] = str(number)
if target.year:
audio["date"] = str(target.year)
audio.save()
new_path = os.path.join(album_dir, new_name)
if os.path.abspath(new_path) != os.path.abspath(path) and not os.path.exists(new_path):
os.rename(path, new_path)