Dedup: match artists across a leading "The" so "The X" and "X" don't download twice

_get_artist_variations only widened the candidate fetch by diacritics, so a
request for "The Black Eyed Peas" never pulled a library track filed under
"Black Eyed Peas" (or vice-versa) — it "failed to match" and re-downloaded a
duplicate. Toggle the leading "The" in both directions when widening the fetch;
the confidence scorer (50/50 title/artist, 0.882 across the "The" gap) still has
the final say, so this can only widen what gets fetched, never merge genuinely
different artists. Mid-word "The" (e.g. "Theory of a Deadman") is untouched.
This commit is contained in:
BoulderBadgeDad 2026-06-21 19:21:59 -07:00
parent 1782e36c78
commit 3496bb1800
2 changed files with 102 additions and 0 deletions

View file

@ -7288,6 +7288,18 @@ class MusicDatabase:
variations.append(normalized_name.title()) variations.append(normalized_name.title())
variations.append(normalized_name) variations.append(normalized_name)
# Leading-"The" toggle — a leading "The" is noise for artist identity
# ("The Black Eyed Peas" == "Black Eyed Peas"). Without this, a request for
# one variant never fetches a library track filed under the other, so it
# "fails to match" and re-downloads a duplicate. Search BOTH forms; the
# confidence scorer still decides (50/50 title/artist), so this only widens
# the candidate fetch — it can't merge genuinely different artists on its own.
stripped = artist_name.strip()
if stripped.lower().startswith("the ") and stripped[4:].strip():
variations.append(stripped[4:].strip()) # "The Black Eyed Peas" -> "Black Eyed Peas"
elif stripped:
variations.append("The " + stripped) # "Black Eyed Peas" -> "The Black Eyed Peas"
# Add more aliases here in the future # Add more aliases here in the future
if "korn" in name_lower: if "korn" in name_lower:
if "KoЯn" not in variations: if "KoЯn" not in variations:

View file

@ -0,0 +1,90 @@
"""Leading-"The" duplicate fix.
A user wanted "I Gotta Feeling" by "The Black Eyed Peas" but owned it under
"Black Eyed Peas" (or vice-versa). The dedup gate (check_track_exists) fetches
candidates via _get_artist_variations(), which had no "The" toggle so the
owned track was never fetched, the request "failed to match", and a duplicate
was downloaded. The toggle widens the fetch to both forms; the scorer still
decides, so it can't merge genuinely different artists.
"""
from __future__ import annotations
from database.music_database import MusicDatabase
def _variations(name):
db = object.__new__(MusicDatabase) # no DB / network init needed
return db._get_artist_variations(name)
def test_leading_the_is_stripped_to_search_the_bare_form():
v = _variations("The Black Eyed Peas")
assert "Black Eyed Peas" in v # owned-bare form now gets fetched
assert "The Black Eyed Peas" in v # original kept
def test_bare_name_also_searches_the_the_prefixed_form():
v = _variations("Black Eyed Peas")
assert "The The Black Eyed Peas" not in v # no double-"The"
assert "The Black Eyed Peas" in v # the "The"-variant gets fetched
assert "Black Eyed Peas" in v
def test_the_band_named_just_the_never_produces_an_empty_search():
# "The" alone must not collapse to an empty artist search (which would match
# the entire library). Adding "The The" is harmless — the scorer still gates.
v = _variations("The")
assert "" not in v
assert "The" in v
def test_a_leading_the_word_is_required_not_a_mid_word_the():
# "Theory of a Deadman" starts with "The" but not the WORD "The" — it must not
# be stripped mid-word. (It does still get the harmless "The "-prefixed widen.)
v = _variations("Theory of a Deadman")
assert "ory of a Deadman" not in v # NOT mangled mid-word
assert "Theory of a Deadman" in v
def test_the_toggle_lands_the_match_through_the_real_scorer():
# End-to-end on the confidence scorer: requesting one variant against the
# other owned variant must clear the 0.8 dedup threshold (50/50 title/artist
# → 1.0*0.5 + 0.882*0.5 = 0.94), so it's recognized as already owned.
db = object.__new__(MusicDatabase)
class _Track:
title = "I Gotta Feeling"
artist_name = "Black Eyed Peas"
track_artist = "Black Eyed Peas"
album = "The E.N.D."
conf = db._calculate_track_confidence("I Gotta Feeling", "The Black Eyed Peas", _Track())
assert conf >= 0.8, conf
# …and the reverse direction too.
class _Track2:
title = "I Gotta Feeling"
artist_name = "The Black Eyed Peas"
track_artist = "The Black Eyed Peas"
album = "The E.N.D."
conf2 = db._calculate_track_confidence("I Gotta Feeling", "Black Eyed Peas", _Track2())
assert conf2 >= 0.8, conf2
def test_toggle_does_not_falsely_merge_different_the_artists():
# "The Police" and "Police" are arguably the same band, but "The Weeknd" vs
# "Weeknd" etc. — the toggle only WIDENS the fetch; the scorer still gates.
# A clearly different artist must not score as a match just because both
# share no "The". (Title differs too — this is the real safety net.)
db = object.__new__(MusicDatabase)
class _Other:
title = "Some Other Song"
artist_name = "Black Eyed Peas"
track_artist = "Black Eyed Peas"
album = "Whatever"
conf = db._calculate_track_confidence("I Gotta Feeling", "The Killers", _Other())
assert conf < 0.8, conf