fix: write single first-artist tag, not doubled/multi-artist

Live end-to-end test surfaced two bugs in youtube tagging:
- `--replace-in-metadata artist .* NAME` matched twice and doubled the
  artist tag (e.g. "SLVMLORDSLVMLORD"). Anchor with ^.*$ to match once.
- Use only the first artist when several are present (SLVMLORD, not
  "SLVMLORD, Travis Bradley, ...") for both the embedded tag and the
  spoken/echoed API messages.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-08 20:39:03 -07:00
parent 9984c162c6
commit 425a973d85
3 changed files with 23 additions and 4 deletions

View File

@@ -474,7 +474,10 @@ def yt_download(url_or_query: str, target_folder: str, quality: str, dry_run: bo
# Override tags from the chosen hit so they don't rely on scraped titles.
if hit:
if hit.artist:
cmd += ["--replace-in-metadata", "artist", ".*", hit.artist]
# First artist only; anchored ^.*$ replaces the whole field exactly once
# (a bare .* matches twice and doubles the value).
primary_artist = hit.artist.split(",")[0].strip()
cmd += ["--replace-in-metadata", "artist", "^.*$", primary_artist]
if hit.album:
cmd += ["--parse-metadata", f"{hit.album}:%(album)s"]
if hit.title:

View File

@@ -13,16 +13,21 @@ def _title(hit) -> str:
return hit.album if hit.kind == "album" else (hit.title or hit.album or hit.artist)
def _primary_artist(hit) -> str:
"""First artist only — ignore featured/secondary artists."""
return (hit.artist.split(",")[0].strip() if hit.artist else "") or "unknown artist"
def started_message(hit) -> str:
return f"Found '{_title(hit)}' by {hit.artist or 'unknown artist'} on {_source_label(hit)}. Downloading now."
return f"Found '{_title(hit)}' by {_primary_artist(hit)} on {_source_label(hit)}. Downloading now."
def done_message(hit) -> str:
return f"Finished downloading '{_title(hit)}' by {hit.artist or 'unknown artist'}."
return f"Finished downloading '{_title(hit)}' by {_primary_artist(hit)}."
def failed_message(hit) -> str:
return f"Failed to download '{_title(hit)}' by {hit.artist or 'unknown artist'}."
return f"Failed to download '{_title(hit)}' by {_primary_artist(hit)}."
def _yt_path(hit, root: str) -> str:

View File

@@ -28,6 +28,17 @@ def test_done_message_mentions_title():
assert "Avril Lavigne" in msg
def test_messages_use_only_first_artist():
hit = mf.Hit(source="youtube", kind="track", title="Under My Skin",
artist="SLVMLORD, James John, BobbyGee", album="X", year="",
payload={"videoId": "abc"})
for msg in (actions.started_message(hit), actions.done_message(hit),
actions.failed_message(hit)):
assert "SLVMLORD" in msg
assert "James John" not in msg
assert "BobbyGee" not in msg
def test_perform_youtube_calls_act_youtube(monkeypatch):
calls = {}
monkeypatch.setattr(mf, "act_youtube",