feat: --retag-from-path to recover tags damaged by a prior --repair

Offline re-tag of artist/title from the artist folder + filename: strips
(Official Video)/(Lyrics)-style decorations and trailing [id], and treats an
'Artist - Title' filename as authoritative (recovering the real artist for
music videos filed under a channel name). Overwrites artist/title only; leaves
album/year. Honors --dry-run.

Refactors the source-folder walk into _iter_source_files, shared by --repair
and --retag-from-path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-10 22:30:25 -07:00
parent 567d7578ad
commit 9af7f91a25
3 changed files with 172 additions and 9 deletions

View File

@@ -157,3 +157,67 @@ def test_repair_library_scans_only_source_dirs(tmp_path, monkeypatch):
def test_repair_library_missing_root():
assert mf.repair_library("/no/such/dir", dry_run=False) == (0, 0)
# ---- offline retag-from-path ----
def test_title_from_filename():
assert mf._title_from_filename(f"Song [{YT_ID}].opus") == "Song"
assert mf._title_from_filename("STARDUST (Official Music Video) [3nsYNXtALhA].opus") \
== "STARDUST (Official Music Video)"
assert mf._title_from_filename("no brackets.mp3") == "no brackets"
def test_strip_decorations():
assert mf._strip_decorations("STARDUST (Official Music Video)") == "STARDUST"
assert mf._strip_decorations("Away From You (Lyrics)") == "Away From You"
assert mf._strip_decorations("More Than a Feeling (Official HD Video)") == "More Than a Feeling"
# real info like a feature credit is kept
assert mf._strip_decorations("WHO GON' SLIDE (Feat. Shakewell) [Official Music Video]") \
== "WHO GON' SLIDE (Feat. Shakewell)"
def test_derive_from_filename():
# plain title -> folder is the artist
assert mf._derive_from_filename(f"Aerodynamic [{YT_ID}].opus", "Daft Punk") == ("Daft Punk", "Aerodynamic")
# decorated music video filed under the artist
assert mf._derive_from_filename("STARDUST (Official Music Video) [3nsYNXtALhA].opus", "1nonly") \
== ("1nonly", "STARDUST")
# 'Artist - Title' name wins over a channel folder
assert mf._derive_from_filename("BLCKLGHT - Away From You (Lyrics) [QapF4b1jYw8].opus", "7clouds Techno") \
== ("BLCKLGHT", "Away From You")
def test_retag_file_from_path_fixes_clobbered_tags(monkeypatch):
audio = _FakeAudio({"artist": ["7clouds Techno"], "title": ["BLCKLGHT - Away From You (Lyrics)"]})
monkeypatch.setattr(mf, "_open_audio", lambda path: (audio, None))
changed = mf.retag_file_from_path(
"X/7clouds Techno/youtube/BLCKLGHT - Away From You (Lyrics) [QapF4b1jYw8].opus",
"7clouds Techno", dry_run=False)
assert set(changed) == {"artist=BLCKLGHT", "title=Away From You"}
assert audio["artist"] == ["BLCKLGHT"]
assert audio["title"] == ["Away From You"]
assert audio.saved is True
def test_retag_file_from_path_dry_run(monkeypatch):
audio = _FakeAudio({"artist": ["wrong"], "title": ["wrong"]})
monkeypatch.setattr(mf, "_open_audio", lambda path: (audio, None))
changed = mf.retag_file_from_path(f"X/Daft Punk/youtube/Aerodynamic [{YT_ID}].opus",
"Daft Punk", dry_run=True)
assert changed
assert audio == {"artist": ["wrong"], "title": ["wrong"]}
assert audio.saved is False
def test_retag_library_walks_source_files(tmp_path, monkeypatch):
root = tmp_path
(root / "Daft Punk" / "youtube").mkdir(parents=True)
(root / "Daft Punk" / "youtube" / f"Aerodynamic [{YT_ID}].opus").write_text("x")
(root / "Daft Punk" / "Discovery").mkdir(parents=True) # album folder -> skip
(root / "Daft Punk" / "Discovery" / "x.flac").write_text("x")
visited = []
monkeypatch.setattr(mf, "retag_file_from_path",
lambda path, artist, dry_run: visited.append(artist) or ["artist=x"])
scanned, changed = mf.retag_library_from_path(str(root), dry_run=False)
assert (scanned, changed) == (1, 1)
assert visited == ["Daft Punk"]