diff --git a/allstarr/Controllers/JellyfinController.cs b/allstarr/Controllers/JellyfinController.cs index 21d92e1..904c901 100644 --- a/allstarr/Controllers/JellyfinController.cs +++ b/allstarr/Controllers/JellyfinController.cs @@ -294,14 +294,58 @@ public class JellyfinController : ControllerBase .Select(x => x.Item) .ToList(); - // Dedupe artists by name, keeping highest scored version - var artistScores = scoredLocalArtists.Concat(scoredExternalArtists) - .GroupBy(x => x.Item.Name, StringComparer.OrdinalIgnoreCase) - .Select(g => g.OrderByDescending(x => x.Score).First()) - .OrderByDescending(x => x.Score) - .Select(x => x.Item) + // Dedupe artists by name, but KEEP both local and external versions + // Group by name, then for each name keep ONE local and ONE external (if both exist) + var artistsByName = scoredLocalArtists.Concat(scoredExternalArtists) + .GroupBy(x => x.Item.Name, StringComparer.OrdinalIgnoreCase); + + var artistScores = new List(); + foreach (var group in artistsByName) + { + // Get best local artist (if any) + var bestLocal = group + .Where(x => x.Item.IsLocal) + .OrderByDescending(x => x.Score) + .FirstOrDefault(); + + // Get best external artist (if any) + var bestExternal = group + .Where(x => !x.Item.IsLocal) + .OrderByDescending(x => x.Score) + .FirstOrDefault(); + + // Add both (if they exist) + if (bestLocal.Item != null) + { + artistScores.Add(bestLocal.Item); + } + if (bestExternal.Item != null) + { + artistScores.Add(bestExternal.Item); + } + } + + // Sort by score + artistScores = artistScores + .OrderByDescending(a => { + // Find the score for this artist + var scored = scoredLocalArtists.Concat(scoredExternalArtists) + .FirstOrDefault(x => x.Item.Id == a.Id); + return scored.Score; + }) .ToList(); + // Log deduplication details for debugging + if (_logger.IsEnabled(LogLevel.Debug)) + { + var localArtistNames = scoredLocalArtists.Select(x => $"{x.Item.Name} (local, score: {x.Score:F2})").ToList(); + var externalArtistNames = scoredExternalArtists.Select(x => $"{x.Item.Name} ({x.Item.ExternalProvider}, score: {x.Score:F2})").ToList(); + _logger.LogDebug("🎤 Artist deduplication: Local={LocalArtists}, External={ExternalArtists}, Final={FinalCount}", + string.Join(", ", localArtistNames), + string.Join(", ", externalArtistNames), + artistScores.Count); + } + // Convert to Jellyfin format var mergedSongs = allSongs.Select(s => _responseBuilder.ConvertSongToJellyfinItem(s)).ToList(); var mergedAlbums = allAlbums.Select(a => _responseBuilder.ConvertAlbumToJellyfinItem(a)).ToList(); @@ -680,27 +724,30 @@ public class JellyfinController : ControllerBase } } - // Merge and deduplicate by name + // Merge and deduplicate by name, but KEEP both local and external versions + // This allows users to see both their local "Taylor Swift" and external "Taylor Swift [S]" var artistNames = new HashSet(StringComparer.OrdinalIgnoreCase); var mergedArtists = new List(); + // Add all local artists first foreach (var artist in localArtists) { - if (artistNames.Add(artist.Name)) + if (artistNames.Add(artist.Name + ":local")) { mergedArtists.Add(artist); } } + // Add all external artists (even if name matches local) foreach (var artist in externalArtists) { - if (artistNames.Add(artist.Name)) + if (artistNames.Add(artist.Name + ":external")) { mergedArtists.Add(artist); } } - _logger.LogInformation("Returning {Count} merged artists", mergedArtists.Count); + _logger.LogInformation("Returning {Count} merged artists (local + external)", mergedArtists.Count); // Convert to Jellyfin format var artistItems = mergedArtists.Select(a => _responseBuilder.ConvertArtistToJellyfinItem(a)).ToList(); diff --git a/allstarr/Services/SquidWTF/SquidWTFMetadataService.cs b/allstarr/Services/SquidWTF/SquidWTFMetadataService.cs index 7e99160..c8956bf 100644 --- a/allstarr/Services/SquidWTF/SquidWTFMetadataService.cs +++ b/allstarr/Services/SquidWTF/SquidWTFMetadataService.cs @@ -176,10 +176,13 @@ public class SquidWTFMetadataService : IMusicMetadataService return await TryWithFallbackAsync(async (baseUrl) => { var url = $"{baseUrl}/search/?a={Uri.EscapeDataString(query)}"; + _logger.LogInformation("🔍 SQUIDWTF: Searching artists with URL: {Url}", url); + var response = await _httpClient.GetAsync(url); if (!response.IsSuccessStatusCode) { + _logger.LogWarning("⚠️ SQUIDWTF: Artist search failed with status {StatusCode}", response.StatusCode); return new List(); } @@ -196,11 +199,14 @@ public class SquidWTFMetadataService : IMusicMetadataService { if (count >= limit) break; - artists.Add(ParseTidalArtist(artist)); + var parsedArtist = ParseTidalArtist(artist); + artists.Add(parsedArtist); + _logger.LogDebug("🎤 SQUIDWTF: Found artist: {Name} (ID: {Id})", parsedArtist.Name, parsedArtist.ExternalId); count++; } } + _logger.LogInformation("✓ SQUIDWTF: Artist search returned {Count} results", artists.Count); return artists; }, new List()); }