diff --git a/database/music_database.py b/database/music_database.py index b8702752..c1c595e1 100644 --- a/database/music_database.py +++ b/database/music_database.py @@ -8513,6 +8513,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 @@ -8528,11 +8544,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" @@ -8569,6 +8580,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