diff --git a/web_server.py b/web_server.py index 3517dfac..cc704562 100644 --- a/web_server.py +++ b/web_server.py @@ -8853,33 +8853,58 @@ def get_album_tracks(album_id): @app.route('/api/artist//download-discography', methods=['POST']) def download_discography(artist_id): - """Add selected albums from an artist's discography to the wishlist.""" + """Add selected albums from an artist's discography to the wishlist. + + Resolves each album through the same source-aware path that the + individual-album flow uses, so albums whose IDs come from a + fallback/provider-specific source (e.g. Deezer-formatted IDs surfaced + via Hydrabase) don't fail with "Album not found" when the primary + source can't look them up directly. + """ try: data = request.get_json() - if not data or 'album_ids' not in data: - return jsonify({"success": False, "error": "album_ids required"}), 400 + if not data: + return jsonify({"success": False, "error": "request body required"}), 400 + + # Preferred payload: per-album metadata so each album can be resolved + # through its own source. Falls back to the legacy album_ids list, + # in which case every album is looked up under the artist-level source. + albums_payload = data.get('albums') + legacy_album_ids = data.get('album_ids') + if not albums_payload and not legacy_album_ids: + return jsonify({"success": False, "error": "albums or album_ids required"}), 400 - album_ids = data['album_ids'] artist_name = data.get('artist_name', 'Unknown Artist') + artist_source = (data.get('source') or '').strip().lower() or None + + if albums_payload: + album_entries = [ + { + 'id': str(a.get('id', '')), + 'name': a.get('name') or a.get('title') or '', + 'source': (a.get('source') or '').strip().lower() or artist_source, + 'artist_name': a.get('artist_name') or artist_name, + } + for a in albums_payload if a.get('id') + ] + else: + album_entries = [ + { + 'id': str(aid), + 'name': '', + 'source': artist_source, + 'artist_name': artist_name, + } + for aid in legacy_album_ids if aid + ] + + if not album_entries: + return jsonify({"success": False, "error": "no valid albums in payload"}), 400 from database.music_database import MusicDatabase + from core.metadata.album_tracks import get_artist_album_tracks db = MusicDatabase() profile_id = get_current_profile_id() - active_server = config_manager.get_active_media_server() - - # Resolve metadata client - client = None - if spotify_client and spotify_client.is_authenticated(): - client = spotify_client - else: - fallback_src = _get_metadata_fallback_source() - if fallback_src == 'itunes': - client = _get_itunes_client() - elif fallback_src == 'deezer': - client = _get_deezer_client() - - if not client: - return jsonify({"success": False, "error": "No metadata source available"}), 500 total_added = 0 total_skipped = 0 @@ -8887,27 +8912,48 @@ def download_discography(artist_id): def generate_ndjson(): nonlocal total_added, total_skipped - for album_id in album_ids: + for entry in album_entries: + album_id = entry['id'] + hint_album_name = entry['name'] + hint_artist = entry['artist_name'] + source_override = entry['source'] try: - album_data = client.get_album(album_id) - if not album_data: - yield json.dumps({"album_id": album_id, "status": "error", "message": "Album not found"}) + '\n' - continue + result = get_artist_album_tracks( + album_id, + artist_name=hint_artist, + album_name=hint_album_name, + source_override=source_override, + ) - album_name = album_data.get('name', 'Unknown') - album_images = album_data.get('images', []) - album_type = album_data.get('album_type', 'album') - release_date = album_data.get('release_date', '') - album_artists = album_data.get('artists', []) + if not result.get('success'): + message = result.get('error') or 'Album not found' + yield json.dumps({ + "album_id": album_id, + "name": hint_album_name or album_id, + "status": "error", + "message": message, + }) + '\n' + continue - tracks = album_data.get('tracks', {}).get('items', []) - if not tracks: - tracks_data = client.get_album_tracks(album_id) - if tracks_data and 'items' in tracks_data: - tracks = tracks_data['items'] + album = result.get('album', {}) or {} + tracks = result.get('tracks', []) or [] + album_name = album.get('name') or hint_album_name or 'Unknown' + album_images = album.get('images') or ( + [{'url': album['image_url']}] if album.get('image_url') else [] + ) + album_type = album.get('album_type', 'album') + release_date = album.get('release_date', '') or '' + album_artists = album.get('artists') or [{'name': hint_artist}] + resolved_album_id = result.get('resolved_album_id') or album.get('id') or album_id + resolved_source = result.get('source') or source_override or 'unknown' if not tracks: - yield json.dumps({"album_id": album_id, "name": album_name, "status": "error", "message": "No tracks"}) + '\n' + yield json.dumps({ + "album_id": album_id, + "name": album_name, + "status": "error", + "message": "No tracks", + }) + '\n' continue added = 0 @@ -8915,24 +8961,23 @@ def download_discography(artist_id): for track in tracks: track_name = track.get('name', '') - track_artists = track.get('artists', []) - track_id = track.get('id', '') - if not track_name: continue + track_artists = track.get('artists', []) or album_artists + track_id = track.get('id', '') spotify_track_data = { 'id': track_id, 'name': track_name, 'artists': track_artists if isinstance(track_artists, list) else [{'name': str(track_artists)}], 'album': { - 'id': str(album_id), + 'id': str(resolved_album_id), 'name': album_name, 'artists': album_artists, 'images': album_images, 'album_type': album_type, 'release_date': release_date, - 'total_tracks': len(tracks) + 'total_tracks': len(tracks), }, 'duration_ms': track.get('duration_ms', 0), 'explicit': track.get('explicit', False), @@ -8941,7 +8986,8 @@ def download_discography(artist_id): 'uri': track.get('uri', ''), 'preview_url': track.get('preview_url'), 'external_urls': track.get('external_urls', {}), - 'is_local': False + 'is_local': False, + '_source': resolved_source, } try: @@ -8950,11 +8996,12 @@ def download_discography(artist_id): failure_reason="Added via Download Discography", source_type="discography", source_info=json.dumps({ - 'artist_name': artist_name, + 'artist_name': hint_artist, 'album_name': album_name, - 'album_type': album_type + 'album_type': album_type, + 'source': resolved_source, }), - profile_id=profile_id + profile_id=profile_id, ) if was_added: added += 1 @@ -8965,17 +9012,37 @@ def download_discography(artist_id): total_added += added total_skipped += skipped - logger.warning(f"[Discography] {album_name}: {added} added, {skipped} skipped") + logger.warning( + f"[Discography] {album_name} ({resolved_source}): {added} added, {skipped} skipped" + ) yield json.dumps({ - "album_id": album_id, "name": album_name, "status": "done", - "tracks_added": added, "tracks_skipped": skipped, "tracks_total": len(tracks) + "album_id": album_id, + "name": album_name, + "status": "done", + "tracks_added": added, + "tracks_skipped": skipped, + "tracks_total": len(tracks), + "source": resolved_source, }) + '\n' except Exception as album_err: - yield json.dumps({"album_id": album_id, "status": "error", "message": str(album_err)}) + '\n' + yield json.dumps({ + "album_id": album_id, + "name": hint_album_name or album_id, + "status": "error", + "message": str(album_err), + }) + '\n' - logger.warning(f"[Discography] Complete for {artist_name}: {total_added} tracks added, {total_skipped} skipped across {len(album_ids)} albums") - yield json.dumps({"status": "complete", "total_added": total_added, "total_skipped": total_skipped, "total_albums": len(album_ids)}) + '\n' + logger.warning( + f"[Discography] Complete for {artist_name}: {total_added} tracks added, " + f"{total_skipped} skipped across {len(album_entries)} albums" + ) + yield json.dumps({ + "status": "complete", + "total_added": total_added, + "total_skipped": total_skipped, + "total_albums": len(album_entries), + }) + '\n' return app.response_class(generate_ndjson(), mimetype='application/x-ndjson', headers={'X-Accel-Buffering': 'no'}) diff --git a/webui/static/library.js b/webui/static/library.js index 55b1892c..6c4e7a4c 100644 --- a/webui/static/library.js +++ b/webui/static/library.js @@ -2251,15 +2251,16 @@ function _renderDiscogCard(release, index, completionData) { const statusClass = isOwned ? 'owned' : isPartial ? 'partial' : ''; const statusIcon = isOwned ? 'โœ“' : isPartial ? 'โ—' : ''; + const albumName = release.name || release.title || ''; return `