mirror of
https://github.com/SoPat712/allstarr.git
synced 2026-02-09 23:55:10 -05:00
Skip matching tracks already in Jellyfin playlist
- Fetch existing tracks from Jellyfin playlist before matching - Extract Spotify IDs from ProviderIds to identify already-matched tracks - Only search for tracks not already in Jellyfin - Logs: 'Matching X/Y tracks (skipping Z already in Jellyfin)' - Significantly reduces matching time for playlists with local content - Example: If 20/50 tracks exist locally, only searches for 30 tracks
This commit is contained in:
@@ -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
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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<string> 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<JellyfinProxyService>();
|
||||
|
||||
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<List<MatchedTrack>>(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<MatchedTrack>();
|
||||
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
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user