diff --git a/allstarr/Services/Spotify/SpotifyTrackMatchingService.cs b/allstarr/Services/Spotify/SpotifyTrackMatchingService.cs
index 399c0f4..c130734 100644
--- a/allstarr/Services/Spotify/SpotifyTrackMatchingService.cs
+++ b/allstarr/Services/Spotify/SpotifyTrackMatchingService.cs
@@ -2,6 +2,7 @@ using allstarr.Models.Domain;
using allstarr.Models.Settings;
using allstarr.Models.Spotify;
using allstarr.Services.Common;
+using allstarr.Services.Jellyfin;
using Microsoft.Extensions.Options;
using System.Text.Json;
@@ -146,6 +147,7 @@ public class SpotifyTrackMatchingService : BackgroundService
///
/// New matching mode that uses ISRC when available for exact matches.
/// Preserves track position for correct playlist ordering.
+ /// Only matches tracks that aren't already in the Jellyfin playlist.
///
private async Task MatchPlaylistTracksWithIsrcAsync(
string playlistName,
@@ -163,25 +165,84 @@ public class SpotifyTrackMatchingService : BackgroundService
return;
}
+ // Get the Jellyfin playlist ID to check which tracks already exist
+ var playlistConfig = _spotifySettings.Playlists
+ .FirstOrDefault(p => p.Name.Equals(playlistName, StringComparison.OrdinalIgnoreCase));
+
+ HashSet existingSpotifyIds = new();
+
+ if (!string.IsNullOrEmpty(playlistConfig?.JellyfinId))
+ {
+ // Get existing tracks from Jellyfin playlist to avoid re-matching
+ using var scope = _serviceProvider.CreateScope();
+ var proxyService = scope.ServiceProvider.GetService();
+
+ if (proxyService != null)
+ {
+ try
+ {
+ var (existingTracksResponse, _) = await proxyService.GetJsonAsync(
+ $"Playlists/{playlistConfig.JellyfinId}/Items",
+ null,
+ null);
+
+ if (existingTracksResponse != null &&
+ existingTracksResponse.RootElement.TryGetProperty("Items", out var items))
+ {
+ foreach (var item in items.EnumerateArray())
+ {
+ if (item.TryGetProperty("ProviderIds", out var providerIds) &&
+ providerIds.TryGetProperty("Spotify", out var spotifyId))
+ {
+ var id = spotifyId.GetString();
+ if (!string.IsNullOrEmpty(id))
+ {
+ existingSpotifyIds.Add(id);
+ }
+ }
+ }
+ _logger.LogInformation("Found {Count} tracks already in Jellyfin playlist {Playlist}, will skip matching these",
+ existingSpotifyIds.Count, playlistName);
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger.LogWarning(ex, "Could not fetch existing Jellyfin tracks for {Playlist}, will match all tracks", playlistName);
+ }
+ }
+ }
+
+ // Filter to only tracks not already in Jellyfin
+ var tracksToMatch = spotifyTracks
+ .Where(t => !existingSpotifyIds.Contains(t.SpotifyId))
+ .ToList();
+
+ if (tracksToMatch.Count == 0)
+ {
+ _logger.LogInformation("All {Count} tracks for {Playlist} already exist in Jellyfin, skipping matching",
+ spotifyTracks.Count, playlistName);
+ return;
+ }
+
+ _logger.LogInformation("Matching {ToMatch}/{Total} tracks for {Playlist} (skipping {Existing} already in Jellyfin, ISRC: {IsrcEnabled})",
+ tracksToMatch.Count, spotifyTracks.Count, playlistName, existingSpotifyIds.Count, _spotifyApiSettings.PreferIsrcMatching);
+
// Check cache - use snapshot/timestamp to detect changes
var existingMatched = await _cache.GetAsync>(matchedTracksKey);
- if (existingMatched != null && existingMatched.Count == spotifyTracks.Count)
+ if (existingMatched != null && existingMatched.Count >= tracksToMatch.Count)
{
_logger.LogInformation("Playlist {Playlist} already has {Count} matched tracks cached, skipping",
playlistName, existingMatched.Count);
return;
}
- _logger.LogInformation("Matching {Count} tracks for {Playlist} (ISRC: {IsrcEnabled})",
- spotifyTracks.Count, playlistName, _spotifyApiSettings.PreferIsrcMatching);
-
var matchedTracks = new List();
var isrcMatches = 0;
var fuzzyMatches = 0;
var noMatch = 0;
// Process tracks in batches for parallel searching
- var orderedTracks = spotifyTracks.OrderBy(t => t.Position).ToList();
+ var orderedTracks = tracksToMatch.OrderBy(t => t.Position).ToList();
for (int i = 0; i < orderedTracks.Count; i += BatchSize)
{
if (cancellationToken.IsCancellationRequested) break;
@@ -293,7 +354,7 @@ public class SpotifyTrackMatchingService : BackgroundService
_logger.LogInformation(
"✓ Cached {Matched}/{Total} tracks for {Playlist} (ISRC: {Isrc}, Fuzzy: {Fuzzy}, No match: {NoMatch})",
- matchedTracks.Count, spotifyTracks.Count, playlistName, isrcMatches, fuzzyMatches, noMatch);
+ matchedTracks.Count, tracksToMatch.Count, playlistName, isrcMatches, fuzzyMatches, noMatch);
}
else
{