Fix MusicBrainz release matching preferring standard over deluxe
When searching for "Playing the Angel (Deluxe)", MusicBrainz would match the standard release because its higher popularity score outweighed the lower title similarity. Now match_release extracts version qualifiers (Deluxe, Remastered, etc.) from both the query and each result, applying a +10 bonus for exact qualifier match, +5 for partial, and -5 penalty when the query has a qualifier but the result doesn't. This ensures deluxe downloads get the deluxe MBID, preventing Navidrome from splitting albums.
This commit is contained in:
parent
85044261a4
commit
f991d02f0c
1 changed files with 40 additions and 10 deletions
|
|
@ -1,5 +1,6 @@
|
||||||
from typing import Optional, Dict, Any
|
from typing import Optional, Dict, Any
|
||||||
import json
|
import json
|
||||||
|
import re
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from difflib import SequenceMatcher
|
from difflib import SequenceMatcher
|
||||||
from utils.logging_config import get_logger
|
from utils.logging_config import get_logger
|
||||||
|
|
@ -186,6 +187,18 @@ class MusicBrainzService:
|
||||||
logger.error(f"Error matching artist '{artist_name}': {e}")
|
logger.error(f"Error matching artist '{artist_name}': {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
# Version qualifiers that distinguish releases (Deluxe, Remastered, etc.)
|
||||||
|
_VERSION_QUALIFIERS = re.compile(
|
||||||
|
r'\b(deluxe|expanded|remaster(?:ed)?|anniversary|special|collector|'
|
||||||
|
r'limited|bonus|platinum|gold|super\s*deluxe|standard)\b',
|
||||||
|
re.IGNORECASE
|
||||||
|
)
|
||||||
|
|
||||||
|
def _extract_version_qualifier(self, title: str) -> str:
|
||||||
|
"""Extract version qualifiers from an album title, normalized and sorted."""
|
||||||
|
qualifiers = sorted(set(q.lower() for q in self._VERSION_QUALIFIERS.findall(title)))
|
||||||
|
return ' '.join(qualifiers)
|
||||||
|
|
||||||
def match_release(self, album_name: str, artist_name: Optional[str] = None) -> Optional[Dict[str, Any]]:
|
def match_release(self, album_name: str, artist_name: Optional[str] = None) -> Optional[Dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
Match a release (album) by name to MusicBrainz
|
Match a release (album) by name to MusicBrainz
|
||||||
|
|
@ -213,6 +226,9 @@ class MusicBrainzService:
|
||||||
self._save_to_cache('release', album_name, artist_name, None, None, 0)
|
self._save_to_cache('release', album_name, artist_name, None, None, 0)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
# Extract version qualifier from search query for preference matching
|
||||||
|
query_qualifier = self._extract_version_qualifier(album_name)
|
||||||
|
|
||||||
# Find best match
|
# Find best match
|
||||||
best_match = None
|
best_match = None
|
||||||
best_confidence = 0
|
best_confidence = 0
|
||||||
|
|
@ -236,8 +252,22 @@ class MusicBrainzService:
|
||||||
artist_bonus = 20
|
artist_bonus = 20
|
||||||
break
|
break
|
||||||
|
|
||||||
|
# Version qualifier matching: prefer releases with the same
|
||||||
|
# edition qualifier (Deluxe, Remastered, etc.) as the query.
|
||||||
|
# This prevents "Playing the Angel (Deluxe)" from matching the
|
||||||
|
# standard "Playing the Angel" release.
|
||||||
|
version_bonus = 0
|
||||||
|
if query_qualifier:
|
||||||
|
mb_qualifier = self._extract_version_qualifier(mb_title)
|
||||||
|
if query_qualifier == mb_qualifier:
|
||||||
|
version_bonus = 10 # Same edition — strong preference
|
||||||
|
elif mb_qualifier and mb_qualifier in query_qualifier:
|
||||||
|
version_bonus = 5 # Partial match (e.g. "deluxe" in "super deluxe")
|
||||||
|
elif not mb_qualifier:
|
||||||
|
version_bonus = -5 # Query has qualifier but result doesn't — penalize
|
||||||
|
|
||||||
# Combine scores - cap at 100
|
# Combine scores - cap at 100
|
||||||
confidence = min(100, int((title_similarity * 50) + (mb_score / 100 * 30) + artist_bonus))
|
confidence = min(100, int((title_similarity * 50) + (mb_score / 100 * 30) + artist_bonus + version_bonus))
|
||||||
|
|
||||||
if confidence > best_confidence:
|
if confidence > best_confidence:
|
||||||
best_confidence = confidence
|
best_confidence = confidence
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue