From 425a973d85fa0432dd43afd0067d4d36427f507b Mon Sep 17 00:00:00 2001 From: zebra Date: Mon, 8 Jun 2026 20:39:03 -0700 Subject: [PATCH] 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 --- musicfetch | 5 ++++- server/actions.py | 11 ++++++++--- tests/test_actions.py | 11 +++++++++++ 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/musicfetch b/musicfetch index e8cae20..be9c55f 100755 --- a/musicfetch +++ b/musicfetch @@ -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: diff --git a/server/actions.py b/server/actions.py index 1393d5e..47a4fb5 100644 --- a/server/actions.py +++ b/server/actions.py @@ -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: diff --git a/tests/test_actions.py b/tests/test_actions.py index 9f1f58b..422b90a 100644 --- a/tests/test_actions.py +++ b/tests/test_actions.py @@ -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",