diff --git a/allstarr/Controllers/JellyfinController.cs b/allstarr/Controllers/JellyfinController.cs index 4400dba..2f90e6f 100644 --- a/allstarr/Controllers/JellyfinController.cs +++ b/allstarr/Controllers/JellyfinController.cs @@ -2862,29 +2862,36 @@ public class JellyfinController : ControllerBase Request.Headers); var existingTracks = new List(); - var existingSpotifyIds = new HashSet(); - var existingPositions = new Dictionary(); // SpotifyId -> position from Jellyfin + var existingBySpotifyId = new Dictionary(); // SpotifyId -> Song + var existingByIsrc = new Dictionary(); // ISRC -> Song if (existingTracksResponse != null && existingTracksResponse.RootElement.TryGetProperty("Items", out var items)) { - var position = 0; foreach (var item in items.EnumerateArray()) { var song = _modelMapper.ParseSong(item); existingTracks.Add(song); - // Track Spotify IDs and their positions + // Index by Spotify ID if available (from Jellyfin Spotify Import plugin) if (item.TryGetProperty("ProviderIds", out var providerIds) && - providerIds.TryGetProperty("Spotify", out var spotifyId)) + providerIds.TryGetProperty("Spotify", out var spotifyIdElement)) { - var id = spotifyId.GetString() ?? ""; - existingSpotifyIds.Add(id); - existingPositions[id] = position; + var spotifyId = spotifyIdElement.GetString(); + if (!string.IsNullOrEmpty(spotifyId)) + { + existingBySpotifyId[spotifyId] = song; + } + } + + // Index by ISRC for matching (most reliable) + if (!string.IsNullOrEmpty(song.Isrc)) + { + existingByIsrc[song.Isrc] = song; } - position++; } - _logger.LogInformation("Found {Count} existing local tracks in Jellyfin playlist", existingTracks.Count); + _logger.LogInformation("Found {Count} existing tracks in Jellyfin playlist ({SpotifyIds} with Spotify IDs, {Isrcs} with ISRCs)", + existingTracks.Count, existingBySpotifyId.Count, existingByIsrc.Count); } // Get the full playlist from Spotify to know the correct order @@ -2897,28 +2904,39 @@ public class JellyfinController : ControllerBase // Build the final track list in correct Spotify order var finalTracks = new List(); - var localUsed = new HashSet(); // Track which local tracks we've placed + var localUsedCount = 0; + var externalUsedCount = 0; foreach (var spotifyTrack in spotifyTracks.OrderBy(t => t.Position)) { - // Check if this track exists locally - if (existingSpotifyIds.Contains(spotifyTrack.SpotifyId)) + Song? localTrack = null; + + // Try to find local track by Spotify ID first (fastest) + if (existingBySpotifyId.TryGetValue(spotifyTrack.SpotifyId, out var trackBySpotifyId)) { - // Use the local version - if (existingPositions.TryGetValue(spotifyTrack.SpotifyId, out var localIndex) && - localIndex < existingTracks.Count) - { - finalTracks.Add(existingTracks[localIndex]); - localUsed.Add(localIndex); - continue; - } + localTrack = trackBySpotifyId; + } + // Try to find by ISRC (most reliable for matching) + else if (!string.IsNullOrEmpty(spotifyTrack.Isrc) && + existingByIsrc.TryGetValue(spotifyTrack.Isrc, out var trackByIsrc)) + { + localTrack = trackByIsrc; } - // Check if we have a matched external track + // If we found a local track, use it + if (localTrack != null) + { + finalTracks.Add(localTrack); + localUsedCount++; + continue; + } + + // No local track - check if we have a matched external track var matched = orderedTracks.FirstOrDefault(t => t.SpotifyId == spotifyTrack.SpotifyId); if (matched != null) { finalTracks.Add(matched.MatchedSong); + externalUsedCount++; } // If no match, the track is simply omitted (not available from any source) } @@ -2931,8 +2949,8 @@ public class JellyfinController : ControllerBase _logger.LogInformation( "Final ordered playlist: {Total} tracks ({Local} local + {External} external) for {Playlist}", finalTracks.Count, - localUsed.Count, - finalTracks.Count - localUsed.Count, + localUsedCount, + externalUsedCount, spotifyPlaylistName); return _responseBuilder.CreateItemsResponse(finalTracks);