From abb08efe741f1f338ad41902ac21df98e47bb1c6 Mon Sep 17 00:00:00 2001 From: Broque Thomas <26755000+Nezreka@users.noreply.github.com> Date: Sun, 19 Apr 2026 01:02:01 -0700 Subject: [PATCH] Fix album 404 on library page when Spotify is rate limited _resolve_db_album_id was missing deezer_album_id from stored ID checks and hardcoded Spotify for the name-based search fallback. When Spotify was rate limited (common for new Navidrome users), no fallback was tried and the album returned 404. Now checks all stored IDs (spotify, deezer, itunes, discogs) in priority order matching the active metadata source, and falls back through all available sources for name-based search instead of only Spotify. --- web_server.py | 65 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 38 insertions(+), 27 deletions(-) diff --git a/web_server.py b/web_server.py index 286b3bb4..e807e1c5 100644 --- a/web_server.py +++ b/web_server.py @@ -11353,19 +11353,20 @@ def get_artist_discography(artist_id): return jsonify({"error": str(e)}), 500 def _resolve_db_album_id(album_id, artist_id=None): - """Resolve a database album ID to a real Spotify/iTunes album ID. + """Resolve a database album ID to a real external album ID. - When the artist detail page falls back to owned_releases (Spotify artist - search failed), the album cards carry a database auto-increment ID. - This helper looks up stored external IDs first, then falls back to an - iTunes/Spotify search by album title + artist name. + When the artist detail page falls back to owned_releases, the album cards + carry a database ID. This helper looks up stored external IDs first, then + falls back to a search by album title + artist name using the primary + metadata source (with fallback to other sources). """ try: database = get_database() with database._get_connection() as conn: cursor = conn.cursor() cursor.execute(""" - SELECT a.title, a.spotify_album_id, a.itunes_album_id, a.discogs_id, ar.name as artist_name + SELECT a.title, a.spotify_album_id, a.itunes_album_id, a.discogs_id, + a.deezer_album_id, ar.name as artist_name FROM albums a JOIN artists ar ON a.artist_id = ar.id WHERE a.id = ? @@ -11374,32 +11375,42 @@ def _resolve_db_album_id(album_id, artist_id=None): if not row: return None - # Prefer stored external IDs — match the active fallback source + # Prefer stored external IDs — match the active primary source first fallback = _get_metadata_fallback_source() - if fallback == 'discogs' and row.get('discogs_id'): - return row['discogs_id'] - if row['spotify_album_id']: - return row['spotify_album_id'] - if row['itunes_album_id']: - return row['itunes_album_id'] - if row.get('discogs_id'): - return row['discogs_id'] - - # No stored external ID — search by name + id_priority = { + 'spotify': [('spotify_album_id', None), ('deezer_album_id', None), ('itunes_album_id', None), ('discogs_id', None)], + 'deezer': [('deezer_album_id', None), ('spotify_album_id', None), ('itunes_album_id', None), ('discogs_id', None)], + 'itunes': [('itunes_album_id', None), ('spotify_album_id', None), ('deezer_album_id', None), ('discogs_id', None)], + 'discogs': [('discogs_id', None), ('spotify_album_id', None), ('deezer_album_id', None), ('itunes_album_id', None)], + } + for col, _ in id_priority.get(fallback, id_priority['spotify']): + val = row[col] if col in row.keys() else None + if val: + return val + + # No stored external ID — search by name using primary source with fallback album_title = row['title'] artist_name = row['artist_name'] query = f"{artist_name} {album_title}" logger.debug("Searching for album by name: %s", query) - results = spotify_client.search_albums(query, limit=5) - if results: - # Pick the best match (search already ranks by relevance) - for album in results: - if album.name.lower().strip() == album_title.lower().strip(): - logger.debug("Found exact album match: %s (id=%s)", album.name, album.id) - return album.id - # Fall back to first result if no exact title match - logger.debug("No exact match, using best result: %s (id=%s)", results[0].name, results[0].id) - return results[0].id + + from core.metadata_service import get_source_priority, get_client_for_source + for source in get_source_priority(fallback): + try: + client = get_client_for_source(source) + if not client: + continue + results = client.search_albums(query, limit=5) + if results: + for album in results: + if album.name.lower().strip() == album_title.lower().strip(): + logger.debug("Found exact album match via %s: %s (id=%s)", source, album.name, album.id) + return album.id + logger.debug("No exact match via %s, using best result: %s (id=%s)", source, results[0].name, results[0].id) + return results[0].id + except Exception as e: + logger.debug("Album search via %s failed: %s", source, e) + continue except Exception as e: logger.debug("Error resolving DB album ID %s: %s", album_id, e)