Add thread-safe MusicBrainz release cache
Introduce an in-memory, locked cache (_mb_release_cache and _mb_release_cache_lock) for MusicBrainz release lookups to ensure concurrent post-processing threads use a consistent release MBID and avoid splitting albums in players. Use the track artist (first artist in multi-artist fields) for artist MBID matching. Remove usage of AudioDB's per-track strMusicBrainzAlbumID as a fallback for MUSICBRAINZ_RELEASE_ID and add a comment explaining why (AudioDB links tracks to original albums which can differ per track and split albums). Includes safeguards around match_release calls and stores empty results in the cache to avoid repeated failed lookups.
This commit is contained in:
parent
fb7b373d71
commit
44297a210b
1 changed files with 35 additions and 11 deletions
|
|
@ -1300,6 +1300,13 @@ album_artists = {} # album_key -> artist_name
|
||||||
album_editions = {} # album_key -> "standard" or "deluxe"
|
album_editions = {} # album_key -> "standard" or "deluxe"
|
||||||
album_name_cache = {} # album_key -> cached_final_name
|
album_name_cache = {} # album_key -> cached_final_name
|
||||||
|
|
||||||
|
# Thread-safe cache for MusicBrainz release MBID lookups.
|
||||||
|
# Prevents concurrent post-processing threads from getting different
|
||||||
|
# release IDs for the same album, which causes players like Navidrome
|
||||||
|
# to split one album into multiple entries.
|
||||||
|
_mb_release_cache = {} # (album_lower, artist_lower) -> mbid_string_or_empty
|
||||||
|
_mb_release_cache_lock = threading.Lock()
|
||||||
|
|
||||||
def _prepare_stream_task(track_data):
|
def _prepare_stream_task(track_data):
|
||||||
"""
|
"""
|
||||||
Background streaming task that downloads track to Stream folder and updates global state.
|
Background streaming task that downloads track to Stream folder and updates global state.
|
||||||
|
|
@ -9379,19 +9386,34 @@ def _embed_source_ids(audio_file, metadata: dict):
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
# Also try to get artist MBID (may already be cached from worker)
|
# Use track artist (not album artist) for artist MBID tag
|
||||||
artist_result = mb_service.match_artist(artist_name)
|
track_artist_name = metadata.get('artist', '') or artist_name
|
||||||
|
# For multi-artist tracks, use the first artist only
|
||||||
|
if ', ' in track_artist_name:
|
||||||
|
track_artist_name = track_artist_name.split(', ')[0]
|
||||||
|
artist_result = mb_service.match_artist(track_artist_name)
|
||||||
if artist_result and artist_result.get('mbid'):
|
if artist_result and artist_result.get('mbid'):
|
||||||
artist_mbid = artist_result['mbid']
|
artist_mbid = artist_result['mbid']
|
||||||
id_tags['MUSICBRAINZ_ARTIST_ID'] = artist_mbid
|
id_tags['MUSICBRAINZ_ARTIST_ID'] = artist_mbid
|
||||||
|
|
||||||
# Get release (album) MBID via dedicated search (not per-recording release lists,
|
# Get release (album) MBID via thread-safe in-memory cache.
|
||||||
# which return different release variants per track and split albums in players)
|
# Without this, concurrent threads each call match_release
|
||||||
|
# and get different release variants, splitting albums.
|
||||||
album_name_for_mb = metadata.get('album', '')
|
album_name_for_mb = metadata.get('album', '')
|
||||||
if album_name_for_mb:
|
if album_name_for_mb:
|
||||||
release_result = mb_service.match_release(album_name_for_mb, artist_name)
|
_rc_key = (album_name_for_mb.lower().strip(), artist_name.lower().strip())
|
||||||
if release_result and release_result.get('mbid'):
|
with _mb_release_cache_lock:
|
||||||
id_tags['MUSICBRAINZ_RELEASE_ID'] = release_result['mbid']
|
if _rc_key in _mb_release_cache:
|
||||||
|
_rc_mbid = _mb_release_cache[_rc_key]
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
_rc_result = mb_service.match_release(album_name_for_mb, artist_name)
|
||||||
|
_rc_mbid = _rc_result.get('mbid', '') if _rc_result else ''
|
||||||
|
except Exception:
|
||||||
|
_rc_mbid = ''
|
||||||
|
_mb_release_cache[_rc_key] = _rc_mbid
|
||||||
|
if _rc_mbid:
|
||||||
|
id_tags['MUSICBRAINZ_RELEASE_ID'] = _rc_mbid
|
||||||
else:
|
else:
|
||||||
print("⚠️ MusicBrainz worker not available, skipping MBID lookup")
|
print("⚠️ MusicBrainz worker not available, skipping MBID lookup")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
@ -9453,10 +9475,12 @@ def _embed_source_ids(audio_file, metadata: dict):
|
||||||
id_tags['MUSICBRAINZ_RECORDING_ID'] = adb_mb_track
|
id_tags['MUSICBRAINZ_RECORDING_ID'] = adb_mb_track
|
||||||
recording_mbid = adb_mb_track
|
recording_mbid = adb_mb_track
|
||||||
print(f"🎵 MusicBrainz recording ID from AudioDB fallback: {adb_mb_track}")
|
print(f"🎵 MusicBrainz recording ID from AudioDB fallback: {adb_mb_track}")
|
||||||
adb_mb_album = adb_result.get('strMusicBrainzAlbumID')
|
# NOTE: AudioDB's strMusicBrainzAlbumID is intentionally
|
||||||
if adb_mb_album and 'MUSICBRAINZ_RELEASE_ID' not in id_tags:
|
# NOT used as a fallback for MUSICBRAINZ_RELEASE_ID.
|
||||||
id_tags['MUSICBRAINZ_RELEASE_ID'] = adb_mb_album
|
# AudioDB links each track to its original album in MB,
|
||||||
print(f"🎵 MusicBrainz release ID from AudioDB fallback: {adb_mb_album}")
|
# which differs per track on compilations and splits
|
||||||
|
# albums in players like Navidrome. Album MBID must come
|
||||||
|
# from match_release (cached) to stay consistent.
|
||||||
adb_mb_artist = adb_result.get('strMusicBrainzArtistID')
|
adb_mb_artist = adb_result.get('strMusicBrainzArtistID')
|
||||||
if adb_mb_artist and 'MUSICBRAINZ_ARTIST_ID' not in id_tags:
|
if adb_mb_artist and 'MUSICBRAINZ_ARTIST_ID' not in id_tags:
|
||||||
id_tags['MUSICBRAINZ_ARTIST_ID'] = adb_mb_artist
|
id_tags['MUSICBRAINZ_ARTIST_ID'] = adb_mb_artist
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue