fix: show both local and external artists with same name

- Artists with same name now appear separately (local + external [S])
- Fixed deduplication to keep one local AND one external per name
- Added logging to SquidWTF artist search to show actual URLs
- External artists get [S] suffix to distinguish from local
- Allows users to browse external artist albums not in local library
- TIDAL image URLs already correctly implemented with UUID splitting
This commit is contained in:
2026-02-06 19:41:06 -05:00
parent 99d701a355
commit 1d774111e7
2 changed files with 64 additions and 11 deletions

View File

@@ -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<Artist>();
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<string>(StringComparer.OrdinalIgnoreCase);
var mergedArtists = new List<Artist>();
// 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();

View File

@@ -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<Artist>();
}
@@ -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<Artist>());
}