mirror of
https://github.com/SoPat712/allstarr.git
synced 2026-02-09 23:55:10 -05:00
Use Jellyfin item IDs for lyrics check instead of searching
- Lyrics prefetch now uses playlist items cache which has Jellyfin item IDs
- Directly queries /Audio/{itemId}/Lyrics endpoint (no search needed)
- Eliminates all 401 errors and 'no client headers' warnings
- Priority order: 1) Local Jellyfin lyrics, 2) Spotify lyrics API, 3) LRCLib
- Much more efficient - no fuzzy searching required
- Only searches by artist/title as fallback if item ID not available
- All 225 tests passing
This commit is contained in:
@@ -124,6 +124,42 @@ public class LyricsPrefetchService : BackgroundService
|
||||
return (0, 0, 0);
|
||||
}
|
||||
|
||||
// Get the pre-built playlist items cache which includes Jellyfin item IDs for local tracks
|
||||
var playlistItemsKey = $"spotify:playlist:items:{playlistName}";
|
||||
var playlistItems = await _cache.GetAsync<List<Dictionary<string, object?>>>(playlistItemsKey);
|
||||
|
||||
// Build a map of Spotify ID -> Jellyfin Item ID for quick lookup
|
||||
var spotifyToJellyfinId = new Dictionary<string, string>();
|
||||
if (playlistItems != null)
|
||||
{
|
||||
foreach (var item in playlistItems)
|
||||
{
|
||||
// Check if this is a local Jellyfin track (has Id field, no ProviderIds for external)
|
||||
if (item.TryGetValue("Id", out var idObj) && idObj != null)
|
||||
{
|
||||
var jellyfinId = idObj.ToString();
|
||||
|
||||
// Try to get Spotify provider ID
|
||||
if (item.TryGetValue("ProviderIds", out var providerIdsObj) && providerIdsObj != null)
|
||||
{
|
||||
var providerIdsJson = JsonSerializer.Serialize(providerIdsObj);
|
||||
using var doc = JsonDocument.Parse(providerIdsJson);
|
||||
if (doc.RootElement.TryGetProperty("Spotify", out var spotifyIdEl))
|
||||
{
|
||||
var spotifyId = spotifyIdEl.GetString();
|
||||
if (!string.IsNullOrEmpty(spotifyId) && !string.IsNullOrEmpty(jellyfinId))
|
||||
{
|
||||
spotifyToJellyfinId[spotifyId] = jellyfinId;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogDebug("Found {Count} local Jellyfin tracks with Spotify IDs in playlist {Playlist}",
|
||||
spotifyToJellyfinId.Count, playlistName);
|
||||
}
|
||||
|
||||
var fetched = 0;
|
||||
var cached = 0;
|
||||
var missing = 0;
|
||||
@@ -147,12 +183,15 @@ public class LyricsPrefetchService : BackgroundService
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if this track has local Jellyfin lyrics (embedded in file)
|
||||
var hasLocalLyrics = await CheckForLocalJellyfinLyricsAsync(track.SpotifyId, track.PrimaryArtist, track.Title);
|
||||
// Priority 1: Check if this track has local Jellyfin lyrics (embedded in file)
|
||||
// Use the Jellyfin item ID from the playlist cache if available
|
||||
if (spotifyToJellyfinId.TryGetValue(track.SpotifyId, out var jellyfinItemId))
|
||||
{
|
||||
var hasLocalLyrics = await CheckForLocalJellyfinLyricsByIdAsync(jellyfinItemId, track.PrimaryArtist, track.Title);
|
||||
if (hasLocalLyrics)
|
||||
{
|
||||
cached++;
|
||||
_logger.LogInformation("✓ Local Jellyfin lyrics found for {Artist} - {Track}, skipping LRCLib fetch",
|
||||
_logger.LogInformation("✓ Local Jellyfin lyrics found for {Artist} - {Track}, skipping external fetch",
|
||||
track.PrimaryArtist, track.Title);
|
||||
|
||||
// Remove any previously cached LRCLib lyrics for this track
|
||||
@@ -160,15 +199,16 @@ public class LyricsPrefetchService : BackgroundService
|
||||
await RemoveCachedLyricsAsync(artistNameForRemoval, track.Title, track.Album, track.DurationMs / 1000);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Try Spotify lyrics first if we have a Spotify ID
|
||||
// Priority 2: Try Spotify lyrics if we have a Spotify ID
|
||||
LyricsInfo? lyrics = null;
|
||||
if (!string.IsNullOrEmpty(track.SpotifyId))
|
||||
{
|
||||
lyrics = await TryGetSpotifyLyricsAsync(track.SpotifyId, track.Title, track.PrimaryArtist);
|
||||
}
|
||||
|
||||
// Fall back to LRCLib if no Spotify lyrics
|
||||
// Priority 3: Fall back to LRCLib if no Spotify lyrics
|
||||
if (lyrics == null)
|
||||
{
|
||||
lyrics = await _lrclibService.GetLyricsAsync(
|
||||
@@ -349,6 +389,45 @@ public class LyricsPrefetchService : BackgroundService
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a track has embedded lyrics in Jellyfin using the Jellyfin item ID.
|
||||
/// This is the most efficient method as it directly queries the lyrics endpoint.
|
||||
/// </summary>
|
||||
private async Task<bool> CheckForLocalJellyfinLyricsByIdAsync(string jellyfinItemId, string artistName, string trackTitle)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var scope = _serviceProvider.CreateScope();
|
||||
var proxyService = scope.ServiceProvider.GetService<JellyfinProxyService>();
|
||||
|
||||
if (proxyService == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Directly check if this track has lyrics using the item ID
|
||||
var (lyricsResult, lyricsStatusCode) = await proxyService.GetJsonAsync(
|
||||
$"Audio/{jellyfinItemId}/Lyrics",
|
||||
null,
|
||||
null);
|
||||
|
||||
if (lyricsResult != null && lyricsStatusCode == 200)
|
||||
{
|
||||
// Track has embedded lyrics in Jellyfin
|
||||
_logger.LogDebug("Found embedded lyrics in Jellyfin for {Artist} - {Track} (ID: {JellyfinId})",
|
||||
artistName, trackTitle, jellyfinItemId);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogDebug(ex, "Error checking Jellyfin lyrics for item {ItemId}", jellyfinItemId);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a track has embedded lyrics in Jellyfin by querying the Jellyfin API.
|
||||
/// This prevents downloading lyrics from LRCLib when the local file already has them.
|
||||
|
||||
Reference in New Issue
Block a user