diff --git a/core/matching_engine.py b/core/matching_engine.py index 9d9b47b6..3bfba4ae 100644 --- a/core/matching_engine.py +++ b/core/matching_engine.py @@ -613,16 +613,89 @@ class MusicMatchingEngine: """ Enhanced version of calculate_slskd_match_confidence with version-aware scoring. Returns (confidence, version_type) tuple. + + STRICT VERSION MATCHING: + - Live versions are ONLY accepted if Spotify track title contains "live" or "live version" + - Remixes are ONLY accepted if Spotify track title contains "remix" or "mix" + - Acoustic versions are ONLY accepted if Spotify track title contains "acoustic" + - etc. """ # Get base confidence using existing logic base_confidence = self.calculate_slskd_match_confidence(spotify_track, slskd_track) - - # Detect version type and get penalty + + # Detect version type in Soulseek result version_type, penalty = self.detect_version_type(slskd_track.filename) - - # Apply version penalty + + # Check if Spotify track title contains version indicators + spotify_title_lower = spotify_track.name.lower() + + # STRICT VERSION MATCHING: Reject mismatched versions + if version_type == 'live': + # Only accept live versions if Spotify title has live as a VERSION INDICATOR + # Patterns: (Live), - Live, [Live], Live at, Live from, Live in, Live Version + # NOT: words ending with 'live' like "Let Me Live" or starting like "Lively" + live_patterns = [ + r'\(live\)', # (Live) or (Live at Wembley) + r'\[live\]', # [Live] + r'[-–—]\s*live\b', # - Live or – Live + r'\blive\s+at\b', # Live at + r'\blive\s+from\b', # Live from + r'\blive\s+in\b', # Live in + r'\blive\s+version\b', # Live Version + r'\blive\s+recording\b' # Live Recording + ] + has_live_indicator = any(re.search(pattern, spotify_title_lower) for pattern in live_patterns) + + if not has_live_indicator: + # Reject: Soulseek has live version but Spotify doesn't want it + return 0.0, 'rejected_version_mismatch' + + elif version_type == 'remix': + # Only accept remixes if Spotify title has remix as a VERSION INDICATOR + # Patterns: (Remix), - Remix, [Remix], Remix, Mix + remix_patterns = [ + r'\(.*?(remix|mix|rmx).*?\)', # (Remix) or (DJ Remix) + r'\[.*?(remix|mix|rmx).*?\]', # [Remix] + r'[-–—]\s*(remix|mix|rmx)\b', # - Remix + r'\b(remix|mix|rmx)\s*$', # Remix at end + ] + has_remix_indicator = any(re.search(pattern, spotify_title_lower) for pattern in remix_patterns) + + if not has_remix_indicator: + # Reject: Soulseek has remix but Spotify wants original + return 0.0, 'rejected_version_mismatch' + + elif version_type == 'acoustic': + # Only accept acoustic if Spotify title has acoustic as a VERSION INDICATOR + acoustic_patterns = [ + r'\(.*?acoustic.*?\)', # (Acoustic) + r'\[.*?acoustic.*?\]', # [Acoustic] + r'[-–—]\s*acoustic\b', # - Acoustic + r'\bacoustic\s+version\b', # Acoustic Version + ] + has_acoustic_indicator = any(re.search(pattern, spotify_title_lower) for pattern in acoustic_patterns) + + if not has_acoustic_indicator: + # Reject: Soulseek has acoustic but Spotify wants original + return 0.0, 'rejected_version_mismatch' + + elif version_type == 'instrumental': + # Only accept instrumental if Spotify title has instrumental as a VERSION INDICATOR + instrumental_patterns = [ + r'\(.*?instrumental.*?\)', # (Instrumental) + r'\[.*?instrumental.*?\]', # [Instrumental] + r'[-–—]\s*instrumental\b', # - Instrumental + r'\binstrumental\s+version\b', # Instrumental Version + ] + has_instrumental_indicator = any(re.search(pattern, spotify_title_lower) for pattern in instrumental_patterns) + + if not has_instrumental_indicator: + # Reject: Soulseek has instrumental but Spotify wants original + return 0.0, 'rejected_version_mismatch' + + # Apply version penalty (for matching versions, slight penalty for quality differences) if version_type != 'original': - adjusted_confidence = max(0.0, base_confidence - penalty) + adjusted_confidence = max(0.0, base_confidence - (penalty * 0.5)) # Reduced penalty since it's a match # Store version info on the track object for UI display slskd_track.version_type = version_type slskd_track.version_penalty = penalty @@ -630,7 +703,7 @@ class MusicMatchingEngine: adjusted_confidence = base_confidence slskd_track.version_type = 'original' slskd_track.version_penalty = 0.0 - + return adjusted_confidence, version_type def find_best_slskd_matches_enhanced(self, spotify_track: SpotifyTrack, slskd_results: List[TrackResult]) -> List[TrackResult]: