From 6b7e6bff6196bea16ebb84b193f064c39f83e89f Mon Sep 17 00:00:00 2001 From: Francesco Durighetto Date: Wed, 3 Jun 2026 00:28:18 +0200 Subject: [PATCH] Fix false library match between normal and Extended Mix titles Featuring cleanup after bracket flattening stripped version suffixes, so Download Missing treated distinct tracks (e.g. Latinamerica vs Extended Mix) as already in library. Remove feat blocks before flattening and bound unparenthesized feat removal; add regression tests. Co-authored-by: Cursor --- database/music_database.py | 34 ++++++++-- tests/test_track_title_version_distinction.py | 67 +++++++++++++++++++ 2 files changed, 96 insertions(+), 5 deletions(-) create mode 100644 tests/test_track_title_version_distinction.py diff --git a/database/music_database.py b/database/music_database.py index 08d42d14..8ecc3b74 100644 --- a/database/music_database.py +++ b/database/music_database.py @@ -7296,6 +7296,22 @@ class MusicDatabase: cleaned = re.sub(r'\s*\(with\s+[^)]*\)', '', cleaned, flags=re.IGNORECASE) cleaned = re.sub(r'\s*\[with\s+[^\]]*\]', '', cleaned, flags=re.IGNORECASE) + # Featuring in parentheses/brackets — must run BEFORE bracket flattening. + # Otherwise ``feat.`` removal after flattening is greedy and strips + # version suffixes ("Extended Mix", "Radio Edit", …) from the same title. + cleaned = re.sub( + r'\s*\([^)]*(?:feat\.?|ft\.?|featuring)[^)]*\)', + '', + cleaned, + flags=re.IGNORECASE, + ) + cleaned = re.sub( + r'\s*\[[^\]]*(?:feat\.?|ft\.?|featuring)[^\]]*\]', + '', + cleaned, + flags=re.IGNORECASE, + ) + # STEP 1: Normalize bracket/dash styles for consistent matching # Convert all bracket styles to spaces for better matching cleaned = re.sub(r'\s*[\[\(]\s*', ' ', cleaned) # Convert opening brackets/parens to space @@ -7311,11 +7327,6 @@ class MusicDatabase: r'\s*explicit\s*', # Remove explicit markers r'\s*clean\s*', # Remove clean markers - # Featuring/collaboration (metadata, not different version) - r'\s*feat\..*', # Remove featuring - r'\s*featuring.*', # Remove featuring - r'\s*ft\..*', # Remove ft. - # Remasters (same recording, different mastering) r'\s*\d{4}\s*remaster.*', # Remove "2015 remaster" r'\s*remaster.*', # Remove "remaster/remastered" @@ -7352,6 +7363,19 @@ class MusicDatabase: for pattern in patterns_to_remove: cleaned = re.sub(pattern, '', cleaned, flags=re.IGNORECASE).strip() + # Unparenthesized featuring (e.g. after bracket flatten) — bounded so + # "feat. Vika - Extended Mix" keeps the version tail. + _version_boundary = ( + r'(?:extended|radio|club|dub|instrumental|acoustic|live|remix|' + r'original|edit|mix|version)\b' + ) + cleaned = re.sub( + rf'\s+(?:feat\.?|ft\.?|featuring)\s+.+?(?=\s+-\s+{_version_boundary}|\s+{_version_boundary}|$)', + '', + cleaned, + flags=re.IGNORECASE, + ).strip() + # STEP 3: Clean up extra spaces cleaned = re.sub(r'\s+', ' ', cleaned).strip() diff --git a/tests/test_track_title_version_distinction.py b/tests/test_track_title_version_distinction.py new file mode 100644 index 00000000..acf7cb62 --- /dev/null +++ b/tests/test_track_title_version_distinction.py @@ -0,0 +1,67 @@ +"""Regression: normal vs Extended Mix must not collapse to the same cleaned title.""" + +import tempfile +from pathlib import Path + +import pytest + +from database.music_database import DatabaseTrack, MusicDatabase + + +@pytest.fixture +def db(tmp_path: Path): + return MusicDatabase(database_path=str(tmp_path / "test.db")) + + +def test_clean_title_preserves_extended_mix_after_feat(db): + normal = db._clean_track_title_for_comparison("Latinamerica (feat. Vika)") + extended = db._clean_track_title_for_comparison( + "Latinamerica (feat. Vika) - Extended Mix" + ) + assert normal == "latinamerica" + assert extended == "latinamerica extended mix" + assert normal != extended + + +def test_normal_does_not_match_extended_in_library(db): + extended_track = DatabaseTrack( + id="t1", + album_id="a1", + artist_id="ar1", + title="Latinamerica (feat. Vika) - Extended Mix", + track_number=1, + duration=300000, + file_path="/music/ext.flac", + bitrate=320, + ) + extended_track.artist_name = "Raffa FL" + extended_track.album_title = "Latinamerica (feat. Vika)" + + confidence = db._calculate_track_confidence( + "Latinamerica (feat. Vika)", + "Raffa FL", + extended_track, + ) + assert confidence < 0.7 + + +def test_extended_matches_extended_in_library(db): + extended_track = DatabaseTrack( + id="t1", + album_id="a1", + artist_id="ar1", + title="Latinamerica (feat. Vika) - Extended Mix", + track_number=1, + duration=300000, + file_path="/music/ext.flac", + bitrate=320, + ) + extended_track.artist_name = "Raffa FL" + extended_track.album_title = "Latinamerica (feat. Vika)" + + confidence = db._calculate_track_confidence( + "Latinamerica (feat. Vika) - Extended Mix", + "Raffa FL", + extended_track, + ) + assert confidence >= 0.7