Improve artist handling and preserve album data in sync

Enhanced handling of artist data to support both string and object formats across the database, sync service, and web server. The sync process now preserves full album and artist objects for tracks, enabling wishlist additions with album cover art. The frontend and API were updated to use the full artist objects, and the UI now formats artist names correctly.
pull/97/head
Broque Thomas 2 months ago
parent 90732cc029
commit bab31218bb

@ -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

@ -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:

@ -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}")

@ -3975,7 +3975,7 @@ function showPlaylistDetailsModal(playlist) {
<span class="playlist-track-number">${index + 1}</span>
<div class="playlist-track-info">
<div class="playlist-track-name">${escapeHtml(track.name)}</div>
<div class="playlist-track-artists">${track.artists.join(', ')}</div>
<div class="playlist-track-artists">${formatArtists(track.artists)}</div>
</div>
<div class="playlist-track-duration">${formatDuration(track.duration_ms)}</div>
</div>

Loading…
Cancel
Save