diff --git a/database/music_database.py b/database/music_database.py index 9cca9a8..4642fb0 100644 --- a/database/music_database.py +++ b/database/music_database.py @@ -2303,7 +2303,16 @@ class MusicDatabase: track_name = spotify_track_data.get('name', 'Unknown Track') artists = spotify_track_data.get('artists', []) - artist_name = artists[0].get('name', 'Unknown Artist') if artists else 'Unknown Artist' + if artists: + first_artist = artists[0] + if isinstance(first_artist, str): + artist_name = first_artist + elif isinstance(first_artist, dict): + artist_name = first_artist.get('name', 'Unknown Artist') + else: + artist_name = 'Unknown Artist' + else: + artist_name = 'Unknown Artist' # Check for duplicates by track name + artist (not just Spotify ID) # This prevents adding the same track multiple times with different IDs or edge cases @@ -2319,7 +2328,16 @@ class MusicDatabase: existing_data = json.loads(existing['spotify_data']) existing_name = existing_data.get('name', '') existing_artists = existing_data.get('artists', []) - existing_artist = existing_artists[0].get('name', '') if existing_artists else '' + if existing_artists: + existing_first = existing_artists[0] + if isinstance(existing_first, str): + existing_artist = existing_first + elif isinstance(existing_first, dict): + existing_artist = existing_first.get('name', '') + else: + existing_artist = '' + else: + existing_artist = '' # Case-insensitive comparison of track name and primary artist if (existing_name.lower() == track_name.lower() and diff --git a/services/sync_service.py b/services/sync_service.py index 37f6665..70279fd 100644 --- a/services/sync_service.py +++ b/services/sync_service.py @@ -167,7 +167,13 @@ class PlaylistSyncService: # Update progress for each track progress_percent = 20 + (40 * (i + 1) / total_tracks) # 20-60% for matching - current_track_name = f"{track.artists[0]} - {track.name}" if track.artists else track.name + # Extract artist name from both string and dict formats + if track.artists: + first_artist = track.artists[0] + artist_name = first_artist if isinstance(first_artist, str) else (first_artist.get('name', 'Unknown') if isinstance(first_artist, dict) else str(first_artist)) + current_track_name = f"{artist_name} - {track.name}" + else: + current_track_name = track.name self._update_progress(playlist.name, "Matching tracks", current_track_name, progress_percent, 5, 2, total_tracks=total_tracks, matched_tracks=len([r for r in match_results if r.is_match]), @@ -266,17 +272,25 @@ class PlaylistSyncService: for match_result in unmatched_tracks: spotify_track = match_result.spotify_track - # Convert SpotifyTrack to dict format expected by wishlist service - spotify_track_data = { - 'id': spotify_track.id, - 'name': spotify_track.name, - 'artists': [{'name': a} if isinstance(a, str) else a for a in spotify_track.artists], - 'album': {'name': spotify_track.album}, - 'duration_ms': spotify_track.duration_ms, - 'popularity': getattr(spotify_track, 'popularity', 0), - 'preview_url': getattr(spotify_track, 'preview_url', None), - 'external_urls': getattr(spotify_track, 'external_urls', {}) - } + # Check if we have original track data with full album objects + original_track_data = None + if hasattr(self, '_original_tracks_map') and self._original_tracks_map: + original_track_data = self._original_tracks_map.get(spotify_track.id) + + # Use original data if available (preserves album images), otherwise convert + if original_track_data: + spotify_track_data = original_track_data + else: + spotify_track_data = { + 'id': spotify_track.id, + 'name': spotify_track.name, + 'artists': [{'name': a} if isinstance(a, str) else a for a in spotify_track.artists], + 'album': {'name': spotify_track.album}, + 'duration_ms': spotify_track.duration_ms, + 'popularity': getattr(spotify_track, 'popularity', 0), + 'preview_url': getattr(spotify_track, 'preview_url', None), + 'external_urls': getattr(spotify_track, 'external_urls', {}) + } # Add to wishlist with source context success = wishlist_service.add_spotify_track_to_wishlist( @@ -344,8 +358,14 @@ class PlaylistSyncService: for artist in spotify_track.artists: if self._cancelled: return None, 0.0 - - artist_name = artist if isinstance(artist, str) else artist + + # Extract artist name from both string and dict formats + if isinstance(artist, str): + artist_name = artist + elif isinstance(artist, dict) and 'name' in artist: + artist_name = artist['name'] + else: + artist_name = str(artist) # Use the improved database check_track_exists method with server awareness try: diff --git a/web_server.py b/web_server.py index 1b669d4..c0c88c7 100644 --- a/web_server.py +++ b/web_server.py @@ -12547,7 +12547,7 @@ def get_playlist_tracks(playlist_id): tracks.append({ 'id': track_data['id'], 'name': track_data['name'], - 'artists': [artist['name'] for artist in track_data['artists']], + 'artists': track_data['artists'], # Full artist objects (matches Download Missing Tracks behavior) 'album': track_data['album'], # Full album object 'duration_ms': track_data['duration_ms'], 'popularity': track_data.get('popularity', 0), @@ -12589,7 +12589,7 @@ def get_playlist_tracks(playlist_id): tracks.append({ 'id': track_data['id'], 'name': track_data['name'], - 'artists': [artist['name'] for artist in track_data['artists']], + 'artists': track_data['artists'], # Full artist objects (matches Download Missing Tracks behavior) 'album': track_data['album'], # Full album object with album_type, total_tracks, etc. 'duration_ms': track_data['duration_ms'], 'popularity': track_data.get('popularity', 0), @@ -14502,6 +14502,14 @@ def _run_sync_task(playlist_id, playlist_name, tracks_json): # Recreate a Playlist object from the JSON data sent by the frontend # This avoids needing to re-fetch it from Spotify print(f"🔄 Converting JSON tracks to SpotifyTrack objects...") + + # Store original track data with full album objects (for wishlist with cover art) + original_tracks_map = {} + for t in tracks_json: + track_id = t.get('id', '') + if track_id: + original_tracks_map[track_id] = t + tracks = [] for i, t in enumerate(tracks_json): # Handle album field - extract name if it's a dictionary @@ -14666,7 +14674,10 @@ def _run_sync_task(playlist_id, playlist_name, tracks_json): setup_duration = (sync_start_time - task_start_time) * 1000 print(f"⏱️ [TIMING] Setup completed at {time.strftime('%H:%M:%S')} (took {setup_duration:.1f}ms)") print(f"🚀 Starting actual sync process with asyncio.run()...") - + + # Attach original tracks map to sync_service for wishlist with album images + sync_service._original_tracks_map = original_tracks_map + # Run the sync (this is a blocking call within this thread) result = asyncio.run(sync_service.sync_playlist(playlist, download_missing=False)) @@ -14704,6 +14715,9 @@ def _run_sync_task(playlist_id, playlist_name, tracks_json): # Clean up the callback if sync_service: sync_service.clear_progress_callback(playlist.name) + # Clean up original tracks map + if hasattr(sync_service, '_original_tracks_map'): + del sync_service._original_tracks_map print(f"✅ Cleanup completed for {playlist_id}") diff --git a/webui/static/script.js b/webui/static/script.js index 9e94778..659b870 100644 --- a/webui/static/script.js +++ b/webui/static/script.js @@ -3975,7 +3975,7 @@ function showPlaylistDetailsModal(playlist) { ${index + 1}