Fix: Prioritize LOCAL tracks in Spotify playlist injection - match by name only

- Remove Spotify ID/ISRC matching (Jellyfin plugin doesn't add these)
- Use ONLY fuzzy name matching (title + artist, 70% threshold)
- LOCAL tracks ALWAYS used first before external providers
- Include ALL tracks from Jellyfin playlist (even if not in Spotify)
- Prevent duplicate track usage with HashSet tracking
- AdminController also updated to match by name for Local/External badges
- Better logging with emojis for debugging
This commit is contained in:
2026-02-03 18:36:33 -05:00
parent 1492778b14
commit f240423822
2 changed files with 123 additions and 67 deletions

View File

@@ -312,26 +312,65 @@ public class AdminController : ControllerBase
var json = await response.Content.ReadAsStringAsync();
using var doc = JsonDocument.Parse(json);
var localSpotifyIds = new HashSet<string>();
// Build list of local tracks (match by name only - no Spotify IDs!)
var localTracks = new List<(string Title, string Artist)>();
if (doc.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 title = item.TryGetProperty("Name", out var nameEl) ? nameEl.GetString() ?? "" : "";
var artist = "";
if (item.TryGetProperty("Artists", out var artistsEl) && artistsEl.GetArrayLength() > 0)
{
var id = spotifyId.GetString();
if (!string.IsNullOrEmpty(id))
{
localSpotifyIds.Add(id);
}
artist = artistsEl[0].GetString() ?? "";
}
else if (item.TryGetProperty("AlbumArtist", out var albumArtistEl))
{
artist = albumArtistEl.GetString() ?? "";
}
if (!string.IsNullOrEmpty(title))
{
localTracks.Add((title, artist));
}
}
}
// Mark tracks as local or external
_logger.LogInformation("Found {Count} local tracks in Jellyfin playlist {Playlist}",
localTracks.Count, decodedName);
// Match Spotify tracks to local tracks by name (fuzzy matching)
foreach (var track in spotifyTracks)
{
var isLocal = false;
if (localTracks.Count > 0)
{
var bestMatch = localTracks
.Select(local => new
{
Local = local,
TitleScore = FuzzyMatcher.CalculateSimilarity(track.Title, local.Title),
ArtistScore = FuzzyMatcher.CalculateSimilarity(track.PrimaryArtist, local.Artist)
})
.Select(x => new
{
x.Local,
x.TitleScore,
x.ArtistScore,
TotalScore = (x.TitleScore * 0.7) + (x.ArtistScore * 0.3)
})
.OrderByDescending(x => x.TotalScore)
.FirstOrDefault();
// Use 70% threshold (same as playback matching)
if (bestMatch != null && bestMatch.TotalScore >= 70)
{
isLocal = true;
}
}
tracksWithStatus.Add(new
{
position = track.Position,
@@ -342,7 +381,7 @@ public class AdminController : ControllerBase
spotifyId = track.SpotifyId,
durationMs = track.DurationMs,
albumArtUrl = track.AlbumArtUrl,
isLocal = localSpotifyIds.Contains(track.SpotifyId)
isLocal = isLocal
});
}