mirror of
https://github.com/SoPat712/allstarr.git
synced 2026-02-09 23:55:10 -05:00
feat: preserve source search ordering instead of re-scoring
- Respect SquidWTF/Tidal's native search ranking (better than fuzzy matching) - Interleave local and external results based on average match quality - Put better-matching source first, preserve original order within each source - Remove unnecessary re-scoring that was disrupting optimal search results - Simplifies search logic and improves result relevance
This commit is contained in:
@@ -279,63 +279,71 @@ public class JellyfinController : ControllerBase
|
|||||||
// Parse Jellyfin results into domain models
|
// Parse Jellyfin results into domain models
|
||||||
var (localSongs, localAlbums, localArtists) = _modelMapper.ParseItemsResponse(jellyfinResult);
|
var (localSongs, localAlbums, localArtists) = _modelMapper.ParseItemsResponse(jellyfinResult);
|
||||||
|
|
||||||
// Score and filter Jellyfin results by relevance
|
// Respect source ordering (SquidWTF/Tidal has better search ranking than our fuzzy matching)
|
||||||
var scoredLocalSongs = ScoreSearchResults(cleanQuery, localSongs, s => s.Title, s => s.Artist, s => s.Album, isExternal: false);
|
// Just interleave local and external results based on which source has better overall match
|
||||||
var scoredLocalAlbums = ScoreSearchResults(cleanQuery, localAlbums, a => a.Title, a => a.Artist, _ => null, isExternal: false);
|
|
||||||
var scoredLocalArtists = ScoreSearchResults(cleanQuery, localArtists, a => a.Name, _ => null, _ => null, isExternal: false);
|
// Calculate average match score for each source to determine which should come first
|
||||||
|
var localSongsAvgScore = localSongs.Any()
|
||||||
|
? localSongs.Average(s => FuzzyMatcher.CalculateSimilarity(cleanQuery, s.Title))
|
||||||
|
: 0.0;
|
||||||
|
var externalSongsAvgScore = externalResult.Songs.Any()
|
||||||
|
? externalResult.Songs.Average(s => FuzzyMatcher.CalculateSimilarity(cleanQuery, s.Title))
|
||||||
|
: 0.0;
|
||||||
|
|
||||||
// Score external results with a small boost
|
var localAlbumsAvgScore = localAlbums.Any()
|
||||||
var scoredExternalSongs = ScoreSearchResults(cleanQuery, externalResult.Songs, s => s.Title, s => s.Artist, s => s.Album, isExternal: true);
|
? localAlbums.Average(a => FuzzyMatcher.CalculateSimilarity(cleanQuery, a.Title))
|
||||||
var scoredExternalAlbums = ScoreSearchResults(cleanQuery, externalResult.Albums, a => a.Title, a => a.Artist, _ => null, isExternal: true);
|
: 0.0;
|
||||||
var scoredExternalArtists = ScoreSearchResults(cleanQuery, externalResult.Artists, a => a.Name, _ => null, _ => null, isExternal: true);
|
var externalAlbumsAvgScore = externalResult.Albums.Any()
|
||||||
|
? externalResult.Albums.Average(a => FuzzyMatcher.CalculateSimilarity(cleanQuery, a.Title))
|
||||||
|
: 0.0;
|
||||||
|
|
||||||
// Merge and sort by score (no filtering - just reorder by relevance)
|
var localArtistsAvgScore = localArtists.Any()
|
||||||
var allSongs = scoredLocalSongs.Concat(scoredExternalSongs)
|
? localArtists.Average(a => FuzzyMatcher.CalculateSimilarity(cleanQuery, a.Name))
|
||||||
.OrderByDescending(x => x.Score)
|
: 0.0;
|
||||||
.Select(x => x.Item)
|
var externalArtistsAvgScore = externalResult.Artists.Any()
|
||||||
.ToList();
|
? externalResult.Artists.Average(a => FuzzyMatcher.CalculateSimilarity(cleanQuery, a.Name))
|
||||||
|
: 0.0;
|
||||||
|
|
||||||
var allAlbums = scoredLocalAlbums.Concat(scoredExternalAlbums)
|
// Interleave results: put better-matching source first, preserve original ordering within each source
|
||||||
.OrderByDescending(x => x.Score)
|
var allSongs = localSongsAvgScore >= externalSongsAvgScore
|
||||||
.Select(x => x.Item)
|
? localSongs.Concat(externalResult.Songs).ToList()
|
||||||
.ToList();
|
: externalResult.Songs.Concat(localSongs).ToList();
|
||||||
|
|
||||||
// NO deduplication - just merge and sort by relevance score
|
var allAlbums = localAlbumsAvgScore >= externalAlbumsAvgScore
|
||||||
// Show ALL matches (local + external) sorted by best match first
|
? localAlbums.Concat(externalResult.Albums).ToList()
|
||||||
var artistScores = scoredLocalArtists.Concat(scoredExternalArtists)
|
: externalResult.Albums.Concat(localAlbums).ToList();
|
||||||
.OrderByDescending(x => x.Score)
|
|
||||||
.Select(x => x.Item)
|
var allArtists = localArtistsAvgScore >= externalArtistsAvgScore
|
||||||
.ToList();
|
? localArtists.Concat(externalResult.Artists).ToList()
|
||||||
|
: externalResult.Artists.Concat(localArtists).ToList();
|
||||||
|
|
||||||
// Log results for debugging
|
// Log results for debugging
|
||||||
if (_logger.IsEnabled(LogLevel.Debug))
|
if (_logger.IsEnabled(LogLevel.Debug))
|
||||||
{
|
{
|
||||||
var localArtistNames = scoredLocalArtists.Select(x => $"{x.Item.Name} (local, score: {x.Score:F2})").ToList();
|
_logger.LogDebug("🎵 Songs: Local avg score={LocalScore:F2}, External avg score={ExtScore:F2}, Local first={LocalFirst}",
|
||||||
var externalArtistNames = scoredExternalArtists.Select(x => $"{x.Item.Name} ({x.Item.ExternalProvider}, score: {x.Score:F2})").ToList();
|
localSongsAvgScore, externalSongsAvgScore, localSongsAvgScore >= externalSongsAvgScore);
|
||||||
_logger.LogDebug("🎤 Artist results: Local={LocalArtists}, External={ExternalArtists}, Total={TotalCount}",
|
_logger.LogDebug("💿 Albums: Local avg score={LocalScore:F2}, External avg score={ExtScore:F2}, Local first={LocalFirst}",
|
||||||
string.Join(", ", localArtistNames),
|
localAlbumsAvgScore, externalAlbumsAvgScore, localAlbumsAvgScore >= externalAlbumsAvgScore);
|
||||||
string.Join(", ", externalArtistNames),
|
_logger.LogDebug("🎤 Artists: Local avg score={LocalScore:F2}, External avg score={ExtScore:F2}, Local first={LocalFirst}",
|
||||||
artistScores.Count);
|
localArtistsAvgScore, externalArtistsAvgScore, localArtistsAvgScore >= externalArtistsAvgScore);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert to Jellyfin format
|
// Convert to Jellyfin format
|
||||||
var mergedSongs = allSongs.Select(s => _responseBuilder.ConvertSongToJellyfinItem(s)).ToList();
|
var mergedSongs = allSongs.Select(s => _responseBuilder.ConvertSongToJellyfinItem(s)).ToList();
|
||||||
var mergedAlbums = allAlbums.Select(a => _responseBuilder.ConvertAlbumToJellyfinItem(a)).ToList();
|
var mergedAlbums = allAlbums.Select(a => _responseBuilder.ConvertAlbumToJellyfinItem(a)).ToList();
|
||||||
var mergedArtists = artistScores.Select(a => _responseBuilder.ConvertArtistToJellyfinItem(a)).ToList();
|
var mergedArtists = allArtists.Select(a => _responseBuilder.ConvertArtistToJellyfinItem(a)).ToList();
|
||||||
|
|
||||||
// Add playlists (score them too)
|
// Add playlists (preserve their order too)
|
||||||
if (playlistResult.Count > 0)
|
if (playlistResult.Count > 0)
|
||||||
{
|
{
|
||||||
var scoredPlaylists = playlistResult
|
var playlistItems = playlistResult
|
||||||
.Select(p => new { Playlist = p, Score = FuzzyMatcher.CalculateSimilarity(cleanQuery, p.Name) })
|
.Select(p => _responseBuilder.ConvertPlaylistToJellyfinItem(p))
|
||||||
.OrderByDescending(x => x.Score)
|
|
||||||
.Select(x => _responseBuilder.ConvertPlaylistToJellyfinItem(x.Playlist))
|
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
mergedAlbums.AddRange(scoredPlaylists);
|
mergedAlbums.AddRange(playlistItems);
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogInformation("Scored and filtered results: Songs={Songs}, Albums={Albums}, Artists={Artists}",
|
_logger.LogInformation("Merged results (preserving source order): Songs={Songs}, Albums={Albums}, Artists={Artists}",
|
||||||
mergedSongs.Count, mergedAlbums.Count, mergedArtists.Count);
|
mergedSongs.Count, mergedAlbums.Count, mergedArtists.Count);
|
||||||
|
|
||||||
// Pre-fetch lyrics for top 3 songs in background (don't await)
|
// Pre-fetch lyrics for top 3 songs in background (don't await)
|
||||||
|
|||||||
Reference in New Issue
Block a user