Raise artist discography limit from 50 to 200 with Deezer pagination

Deezer and iTunes defaulted to 50 albums max, silently truncating large
discographies. Deezer now paginates (100 per page) up to 200. iTunes
raised to 200 (single call). All callers in web_server.py updated to
use the new defaults instead of hardcoding limit=50.

Also adds diagnostic logging for allow_duplicates album comparison
to help debug inconsistent singles behavior.
This commit is contained in:
Broque Thomas 2026-04-12 21:46:04 -07:00
parent b4cf8b4cc1
commit 0edd8f5c81
4 changed files with 39 additions and 25 deletions

View file

@ -635,26 +635,37 @@ class DeezerClient:
'_raw_data': artist_data
}
def get_artist_albums_list(self, artist_id: str, album_type: str = 'album,single', limit: int = 50) -> List[Album]:
def get_artist_albums_list(self, artist_id: str, album_type: str = 'album,single', limit: int = 200) -> List[Album]:
"""Get albums by artist ID — returns Album dataclass list (metadata source interface).
Matches iTunesClient.get_artist_albums() interface."""
data = self._api_get(f'artist/{artist_id}/albums', {'limit': min(limit, 100)})
if not data or 'data' not in data:
return []
Matches iTunesClient.get_artist_albums() interface.
Paginates through all results up to the requested limit."""
albums = []
all_raw = []
requested_types = [t.strip() for t in album_type.split(',')]
offset = 0
page_size = 100 # Deezer API max per request
for album_data in data['data']:
album = Album.from_deezer_album(album_data)
while offset < limit:
fetch_limit = min(page_size, limit - offset)
data = self._api_get(f'artist/{artist_id}/albums', {'limit': fetch_limit, 'index': offset})
if not data or 'data' not in data or len(data['data']) == 0:
break
if album_type != 'album,single':
if album.album_type not in requested_types:
if not (album.album_type == 'ep' and 'single' in requested_types):
continue
for album_data in data['data']:
all_raw.append(album_data)
album = Album.from_deezer_album(album_data)
albums.append(album)
if album_type != 'album,single':
if album.album_type not in requested_types:
if not (album.album_type == 'ep' and 'single' in requested_types):
continue
albums.append(album)
if len(data['data']) < fetch_limit:
break # Last page
offset += len(data['data'])
cache = get_metadata_cache()
# Deezer's /artist/{id}/albums endpoint doesn't include artist info on each album.
@ -663,7 +674,7 @@ class DeezerClient:
if albums and albums[0].artists:
artist_stub = {'id': int(artist_id) if artist_id.isdigit() else 0, 'name': albums[0].artists[0]}
entries = []
for ad in data['data']:
for ad in all_raw:
if ad.get('id'):
if artist_stub and not ad.get('artist'):
ad['artist'] = artist_stub

View file

@ -991,7 +991,7 @@ class iTunesClient:
return None
def get_artist_albums(self, artist_id: str, album_type: str = 'album,single', limit: int = 50) -> List[Album]:
def get_artist_albums(self, artist_id: str, album_type: str = 'album,single', limit: int = 200) -> List[Album]:
"""
Get albums by artist ID

View file

@ -1542,10 +1542,13 @@ class WatchlistScanner:
from difflib import SequenceMatcher
album_sim = SequenceMatcher(None, album_name.lower(), lib_album.lower()).ratio()
if album_sim < 0.85:
logger.debug(f"Track found but different album (allow_duplicates=True): '{original_title}' — library: '{lib_album}', wanted: '{album_name}'")
logger.info(f"[AllowDup] Different album — allowing: '{original_title}' (wanted: '{album_name}', library: '{lib_album}', sim: {album_sim:.2f})")
continue # Different album — allow it
else:
logger.info(f"[AllowDup] Same album — skipping: '{original_title}' (wanted: '{album_name}', library: '{lib_album}', sim: {album_sim:.2f})")
else:
# No album info in library — can't compare, allow it
logger.info(f"[AllowDup] No album info in library — allowing: '{original_title}'")
continue
logger.debug(f"Track found in library: '{original_title}' by '{artist_name}' (confidence: {confidence:.2f})")
return False # Track exists in library

View file

@ -10458,17 +10458,17 @@ def get_artist_discography(artist_id):
active_source = 'spotify'
elif source_override == 'itunes':
itunes_cl = _get_itunes_client()
albums = itunes_cl.get_artist_albums(artist_id, album_type='album,single', limit=50)
albums = itunes_cl.get_artist_albums(artist_id, album_type='album,single')
if albums:
active_source = 'itunes'
elif source_override == 'deezer':
deezer_cl = _get_deezer_client()
albums = deezer_cl.get_artist_albums(artist_id, album_type='album,single', limit=50)
albums = deezer_cl.get_artist_albums(artist_id, album_type='album,single')
if albums:
active_source = 'deezer'
elif source_override == 'discogs':
discogs_cl = _get_discogs_client()
albums = discogs_cl.get_artist_albums(artist_id, album_type='album,single', limit=50)
albums = discogs_cl.get_artist_albums(artist_id, album_type='album,single')
if albums:
active_source = 'discogs'
elif source_override == 'hydrabase':
@ -10479,7 +10479,7 @@ def get_artist_discography(artist_id):
hb_cl = _get_itunes_client()
else:
hb_cl = spotify_client
albums = hb_cl.get_artist_albums(artist_id, album_type='album,single', limit=50)
albums = hb_cl.get_artist_albums(artist_id, album_type='album,single')
if albums:
active_source = plugin or 'hydrabase'
@ -10506,7 +10506,7 @@ def get_artist_discography(artist_id):
search_artists = cl.search_artists(artist_name, limit=5)
if search_artists:
best = next((a for a in search_artists if a.name.lower() == artist_name.lower()), search_artists[0])
albums = cl.get_artist_albums(best.id, album_type='album,single', limit=50)
albums = cl.get_artist_albums(best.id, album_type='album,single')
if albums:
active_source = source_override
@ -10544,7 +10544,7 @@ def get_artist_discography(artist_id):
try:
if is_numeric_id:
# It's a numeric ID (iTunes/Deezer), use directly
albums = fallback_client.get_artist_albums(artist_id, album_type='album,single', limit=50)
albums = fallback_client.get_artist_albums(artist_id, album_type='album,single')
if albums:
active_source = fallback_source
print(f"Got {len(albums)} albums from {fallback_source} (direct ID)")
@ -10563,7 +10563,7 @@ def get_artist_discography(artist_id):
best_match = fallback_artists[0]
print(f"Found {fallback_source} artist: {best_match.name} (ID: {best_match.id})")
albums = fallback_client.get_artist_albums(best_match.id, album_type='album,single', limit=50)
albums = fallback_client.get_artist_albums(best_match.id, album_type='album,single')
if albums:
active_source = fallback_source
print(f"Got {len(albums)} albums from {fallback_source} (name search)")
@ -48064,7 +48064,7 @@ def playlist_explorer_build_tree():
try:
# skip_cache only supported by spotify_client — other clients don't cache this call
_skip = {'skip_cache': True} if hasattr(active_client, 'sp') else {}
all_albums = active_client.get_artist_albums(artist_id, album_type='album,single', limit=50, **_skip)
all_albums = active_client.get_artist_albums(artist_id, album_type='album,single', **_skip)
except Exception as e:
return {'success': False, 'error': f'Album fetch failed: {e}'}
@ -49269,7 +49269,7 @@ def get_spotify_artist_discography(artist_name):
print(f"Found Spotify artist: {artist.name} (ID: {spotify_artist_id}, confidence: {highest_score:.3f})")
# Get all albums (albums, singles, and compilations)
all_albums = spotify_client.get_artist_albums(spotify_artist_id, album_type='album,single,compilation', limit=50)
all_albums = spotify_client.get_artist_albums(spotify_artist_id, album_type='album,single,compilation')
if not all_albums:
return {