From d98d3b33c4c59d4772cdd8362d9270caa0beb1d2 Mon Sep 17 00:00:00 2001 From: Broque Thomas <26755000+Nezreka@users.noreply.github.com> Date: Thu, 2 Apr 2026 18:22:02 -0700 Subject: [PATCH] =?UTF-8?q?Fix=20Discogs=20artist=20page=20load=20time=20?= =?UTF-8?q?=E2=80=94=20lazy=20track=20count=20loading?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove upfront master detail fetching (was 15+ API calls, 40+ seconds) - Discography loads from releases list only (~1 second, 2 API calls) - Track counts populate on-demand via get_album_tracks when clicking album - New get_album_tracks method: tries /masters first, falls back to /releases, returns Spotify-compatible format with proper disc/track numbering - Album type defaults to 'album' for masters without format metadata — Discogs limitation, singles only detectable from individual release format --- core/discogs_client.py | 122 +++++++++++++++++++++++++++++------------ 1 file changed, 87 insertions(+), 35 deletions(-) diff --git a/core/discogs_client.py b/core/discogs_client.py index 0282001f..5df20b2c 100644 --- a/core/discogs_client.py +++ b/core/discogs_client.py @@ -527,44 +527,17 @@ class DiscogsClient: for item in ordered: try: - # For master releases, fetch detail to get tracklist and proper metadata - if item.get('type') == 'master': - master_detail = self._api_get(f'/masters/{item["id"]}') - if master_detail: - # Merge master detail into the item for better parsing - tracklist = [t for t in master_detail.get('tracklist', []) - if t.get('type_', '') == 'track' or not t.get('type_')] - item['_track_count'] = len(tracklist) - # Carry over genres/styles from master - if master_detail.get('genres'): - item['genre'] = master_detail['genres'] - if master_detail.get('styles'): - item['style'] = master_detail['styles'] - if master_detail.get('images'): - primary = next((img for img in master_detail['images'] if img.get('type') == 'primary'), None) - item['cover_image'] = (primary or master_detail['images'][0]).get('uri') - album = Album.from_discogs_release(item) - # Override track count from master detail if available - if item.get('_track_count'): - album = Album( - id=album.id, name=album.name, artists=album.artists, - release_date=album.release_date, total_tracks=item['_track_count'], - album_type=album.album_type, image_url=album.image_url or item.get('cover_image'), - external_urls=album.external_urls, - ) - # Re-evaluate album type with actual track count - if album.total_tracks <= 3: - album = Album(id=album.id, name=album.name, artists=album.artists, - release_date=album.release_date, total_tracks=album.total_tracks, - album_type='single', image_url=album.image_url, external_urls=album.external_urls) - elif album.total_tracks <= 6: - album = Album(id=album.id, name=album.name, artists=album.artists, - release_date=album.release_date, total_tracks=album.total_tracks, - album_type='ep', image_url=album.image_url, external_urls=album.external_urls) + # Use thumb from release list as image + thumb = item.get('thumb') or item.get('cover_image') or '' + if thumb and 'spacer.gif' not in thumb and not album.image_url: + album = Album(id=album.id, name=album.name, artists=album.artists, + release_date=album.release_date, total_tracks=album.total_tracks, + album_type=album.album_type, image_url=thumb, + external_urls=album.external_urls) - # Deduplicate by normalized title + # Deduplicate by normalized title (but keep deluxe/special editions as separate) dedup_key = album.name.lower().strip() if dedup_key in seen_titles: continue @@ -581,6 +554,85 @@ class DiscogsClient: return albums + def get_album_tracks(self, release_id: str) -> Optional[Dict[str, Any]]: + """Get album tracks by Discogs release or master ID. Returns Spotify-compatible format.""" + # Try as master first (master IDs are used in artist discography) + data = self._api_get(f'/masters/{release_id}') + if not data or not data.get('tracklist'): + # Fall back to release + data = self._api_get(f'/releases/{release_id}') + if not data or not data.get('tracklist'): + return None + + # Get album image + image_url = None + images = data.get('images', []) + if images: + primary = next((img for img in images if img.get('type') == 'primary'), None) + image_url = (primary or images[0]).get('uri') + + album_info = { + 'id': str(data.get('id', release_id)), + 'name': data.get('title', ''), + 'images': [{'url': image_url, 'height': 600, 'width': 600}] if image_url else [], + 'release_date': str(data.get('year', '')) if data.get('year') else '', + } + + # Get artists + artists_list = [] + if data.get('artists'): + artists_list = [{'name': a.get('name', '')} for a in data['artists'] if a.get('name')] + if not artists_list: + artists_list = [{'name': 'Unknown Artist'}] + + tracks = [] + track_num = 0 + disc_num = 1 + for t in data['tracklist']: + if t.get('type_') == 'heading': + disc_num += 1 + continue + if t.get('type_', '') not in ('track', ''): + continue + + track_num += 1 + + # Parse duration + duration_ms = 0 + dur_str = t.get('duration', '') + if dur_str and ':' in dur_str: + parts = dur_str.split(':') + try: + duration_ms = (int(parts[0]) * 60 + int(parts[1])) * 1000 + except (ValueError, IndexError): + pass + + # Per-track artists or fall back to release artists + track_artists = artists_list + if t.get('artists'): + track_artists = [{'name': a.get('name', '')} for a in t['artists'] if a.get('name')] + + tracks.append({ + 'id': f"{release_id}_t{track_num}", + 'name': t.get('title', ''), + 'artists': track_artists, + 'album': album_info, + 'duration_ms': duration_ms, + 'track_number': track_num, + 'disc_number': disc_num if disc_num > 1 else 1, + 'explicit': False, + 'uri': f"discogs:track:{release_id}_{track_num}", + 'external_urls': {}, + '_source': 'discogs', + }) + + return { + 'items': tracks, + 'total': len(tracks), + 'limit': len(tracks), + 'next': None, + } + def _get_artist_image_from_albums(self, artist_id: str) -> Optional[str]: """Get artist image by fetching their first album's cover art. Used as fallback when artist has no direct image."""