mirror of
https://github.com/SoPat712/allstarr.git
synced 2026-02-09 23:55:10 -05:00
Artists, not artist but now for lyrics
This commit is contained in:
@@ -1022,7 +1022,9 @@ public class JellyfinController : ControllerBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Try to get lyrics from LRCLIB
|
// Try to get lyrics from LRCLIB
|
||||||
_logger.LogInformation("Searching LRCLIB for lyrics: {Artist} - {Title}", song.Artist, song.Title);
|
_logger.LogInformation("Searching LRCLIB for lyrics: {Artists} - {Title}",
|
||||||
|
song.Artists.Count > 0 ? string.Join(", ", song.Artists) : song.Artist,
|
||||||
|
song.Title);
|
||||||
var lyricsService = HttpContext.RequestServices.GetService<LrclibService>();
|
var lyricsService = HttpContext.RequestServices.GetService<LrclibService>();
|
||||||
if (lyricsService == null)
|
if (lyricsService == null)
|
||||||
{
|
{
|
||||||
@@ -1031,7 +1033,7 @@ public class JellyfinController : ControllerBase
|
|||||||
|
|
||||||
var lyrics = await lyricsService.GetLyricsAsync(
|
var lyrics = await lyricsService.GetLyricsAsync(
|
||||||
song.Title,
|
song.Title,
|
||||||
song.Artist ?? "",
|
song.Artists.Count > 0 ? song.Artists.ToArray() : new[] { song.Artist ?? "" },
|
||||||
song.Album ?? "",
|
song.Album ?? "",
|
||||||
song.Duration ?? 0);
|
song.Duration ?? 0);
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,12 @@ public class LrclibService
|
|||||||
|
|
||||||
public async Task<LyricsInfo?> GetLyricsAsync(string trackName, string artistName, string albumName, int durationSeconds)
|
public async Task<LyricsInfo?> GetLyricsAsync(string trackName, string artistName, string albumName, int durationSeconds)
|
||||||
{
|
{
|
||||||
|
return await GetLyricsAsync(trackName, new[] { artistName }, albumName, durationSeconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<LyricsInfo?> GetLyricsAsync(string trackName, string[] artistNames, string albumName, int durationSeconds)
|
||||||
|
{
|
||||||
|
var artistName = string.Join(", ", artistNames);
|
||||||
var cacheKey = $"lyrics:{artistName}:{trackName}:{albumName}:{durationSeconds}";
|
var cacheKey = $"lyrics:{artistName}:{trackName}:{albumName}:{durationSeconds}";
|
||||||
|
|
||||||
var cached = await _cache.GetStringAsync(cacheKey);
|
var cached = await _cache.GetStringAsync(cacheKey);
|
||||||
@@ -42,12 +48,15 @@ public class LrclibService
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
// Try searching with all artists joined (space-separated for better matching)
|
||||||
|
var searchArtistName = string.Join(" ", artistNames);
|
||||||
|
|
||||||
// First try search API for fuzzy matching (more forgiving)
|
// First try search API for fuzzy matching (more forgiving)
|
||||||
var searchUrl = $"{BaseUrl}/search?" +
|
var searchUrl = $"{BaseUrl}/search?" +
|
||||||
$"track_name={Uri.EscapeDataString(trackName)}&" +
|
$"track_name={Uri.EscapeDataString(trackName)}&" +
|
||||||
$"artist_name={Uri.EscapeDataString(artistName)}";
|
$"artist_name={Uri.EscapeDataString(searchArtistName)}";
|
||||||
|
|
||||||
_logger.LogInformation("Searching LRCLIB: {Url}", searchUrl);
|
_logger.LogInformation("Searching LRCLIB: {Url} (expecting {ArtistCount} artists)", searchUrl, artistNames.Length);
|
||||||
|
|
||||||
var searchResponse = await _httpClient.GetAsync(searchUrl);
|
var searchResponse = await _httpClient.GetAsync(searchUrl);
|
||||||
|
|
||||||
@@ -66,20 +75,29 @@ public class LrclibService
|
|||||||
{
|
{
|
||||||
// Calculate similarity scores
|
// Calculate similarity scores
|
||||||
var trackScore = CalculateSimilarity(trackName, result.TrackName ?? "");
|
var trackScore = CalculateSimilarity(trackName, result.TrackName ?? "");
|
||||||
var artistScore = CalculateSimilarity(artistName, result.ArtistName ?? "");
|
|
||||||
|
// Count artists in the result
|
||||||
|
var resultArtistCount = CountArtists(result.ArtistName ?? "");
|
||||||
|
var expectedArtistCount = artistNames.Length;
|
||||||
|
|
||||||
|
// Artist matching - check if all our artists are present
|
||||||
|
var artistScore = CalculateArtistSimilarity(artistNames, result.ArtistName ?? "");
|
||||||
|
|
||||||
|
// STRONG bonus for matching artist count (this is critical!)
|
||||||
|
var artistCountBonus = resultArtistCount == expectedArtistCount ? 50.0 : 0.0;
|
||||||
|
|
||||||
// Duration match (within 5 seconds is good)
|
// Duration match (within 5 seconds is good)
|
||||||
var durationDiff = Math.Abs(result.Duration - durationSeconds);
|
var durationDiff = Math.Abs(result.Duration - durationSeconds);
|
||||||
var durationScore = durationDiff <= 5 ? 100.0 : Math.Max(0, 100 - (durationDiff * 2));
|
var durationScore = durationDiff <= 5 ? 100.0 : Math.Max(0, 100 - (durationDiff * 2));
|
||||||
|
|
||||||
// Bonus for having synced lyrics (prefer synced over plain)
|
// Bonus for having synced lyrics (prefer synced over plain)
|
||||||
var syncedBonus = !string.IsNullOrEmpty(result.SyncedLyrics) ? 20.0 : 0.0;
|
var syncedBonus = !string.IsNullOrEmpty(result.SyncedLyrics) ? 15.0 : 0.0;
|
||||||
|
|
||||||
// Weighted score: track name most important, then artist, then duration, plus synced bonus
|
// Weighted score: track name important, artist match critical, artist count VERY important
|
||||||
var totalScore = (trackScore * 0.5) + (artistScore * 0.3) + (durationScore * 0.2) + syncedBonus;
|
var totalScore = (trackScore * 0.3) + (artistScore * 0.3) + (durationScore * 0.15) + artistCountBonus + syncedBonus;
|
||||||
|
|
||||||
_logger.LogDebug("Candidate: {Track} by {Artist} - Score: {Score:F1} (track:{TrackScore:F1}, artist:{ArtistScore:F1}, duration:{DurationScore:F1}, synced:{Synced})",
|
_logger.LogDebug("Candidate: {Track} by {Artist} ({ArtistCount} artists) - Score: {Score:F1} (track:{TrackScore:F1}, artist:{ArtistScore:F1}, duration:{DurationScore:F1}, countBonus:{CountBonus:F1}, synced:{Synced})",
|
||||||
result.TrackName, result.ArtistName, totalScore, trackScore, artistScore, durationScore, !string.IsNullOrEmpty(result.SyncedLyrics));
|
result.TrackName, result.ArtistName, resultArtistCount, totalScore, trackScore, artistScore, durationScore, artistCountBonus, !string.IsNullOrEmpty(result.SyncedLyrics));
|
||||||
|
|
||||||
if (totalScore > bestScore)
|
if (totalScore > bestScore)
|
||||||
{
|
{
|
||||||
@@ -173,6 +191,69 @@ public class LrclibService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Counts the number of artists in an artist string (separated by comma, ampersand, or 'e')
|
||||||
|
/// </summary>
|
||||||
|
private static int CountArtists(string artistString)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(artistString))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
// Split by common separators: comma, ampersand, " e " (Portuguese/Spanish "and")
|
||||||
|
var separators = new[] { ',', '&' };
|
||||||
|
var parts = artistString.Split(separators, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
|
||||||
|
// Also check for " e " pattern (like "Julia Michaels e Alessia Cara")
|
||||||
|
var count = parts.Length;
|
||||||
|
foreach (var part in parts)
|
||||||
|
{
|
||||||
|
if (part.Contains(" e ", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
count += part.Split(new[] { " e " }, StringSplitOptions.RemoveEmptyEntries).Length - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.Max(1, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculates how well the expected artists match the result's artist string
|
||||||
|
/// </summary>
|
||||||
|
private static double CalculateArtistSimilarity(string[] expectedArtists, string resultArtistString)
|
||||||
|
{
|
||||||
|
if (expectedArtists.Length == 0 || string.IsNullOrWhiteSpace(resultArtistString))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
var resultLower = resultArtistString.ToLowerInvariant();
|
||||||
|
var matchedCount = 0;
|
||||||
|
|
||||||
|
foreach (var artist in expectedArtists)
|
||||||
|
{
|
||||||
|
var artistLower = artist.ToLowerInvariant();
|
||||||
|
|
||||||
|
// Check if this artist appears in the result string
|
||||||
|
if (resultLower.Contains(artistLower))
|
||||||
|
{
|
||||||
|
matchedCount++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Try token-based matching for partial matches
|
||||||
|
var artistTokens = artistLower.Split(new[] { ' ', '-', '_' }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
var matchedTokens = artistTokens.Count(token => resultLower.Contains(token));
|
||||||
|
|
||||||
|
// If most tokens match, count it as a partial match
|
||||||
|
if (matchedTokens >= artistTokens.Length * 0.7)
|
||||||
|
{
|
||||||
|
matchedCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return percentage of artists matched
|
||||||
|
return (matchedCount * 100.0) / expectedArtists.Length;
|
||||||
|
}
|
||||||
|
|
||||||
private static double CalculateSimilarity(string str1, string str2)
|
private static double CalculateSimilarity(string str1, string str2)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(str1) || string.IsNullOrEmpty(str2))
|
if (string.IsNullOrEmpty(str1) || string.IsNullOrEmpty(str2))
|
||||||
|
|||||||
Reference in New Issue
Block a user