mirror of
https://github.com/SoPat712/allstarr.git
synced 2026-02-09 23:55:10 -05:00
Prioritize local Jellyfin lyrics over LRCLib in prefetch
Some checks failed
CI / build-and-test (push) Has been cancelled
Some checks failed
CI / build-and-test (push) Has been cancelled
- Check for embedded lyrics in local Jellyfin tracks before fetching from LRCLib - Remove previously cached LRCLib lyrics when local lyrics are found - Prevents unnecessary API calls and respects user's embedded lyrics - Tracks with local lyrics are counted as 'cached' in prefetch stats
This commit is contained in:
@@ -732,7 +732,9 @@ public class AdminController : ControllerBase
|
||||
}
|
||||
}
|
||||
|
||||
// Check lyrics status
|
||||
// Check lyrics status (only from our cache - lrclib/Spotify)
|
||||
// Note: For local tracks, Jellyfin may have embedded lyrics that we don't check here
|
||||
// Those will be served directly by Jellyfin when requested
|
||||
var cacheKey = $"lyrics:{track.PrimaryArtist}:{track.Title}:{track.Album}:{track.DurationMs / 1000}";
|
||||
var existingLyrics = await _cache.GetStringAsync(cacheKey);
|
||||
var hasLyrics = !string.IsNullOrEmpty(existingLyrics);
|
||||
|
||||
@@ -2,6 +2,7 @@ using System.Text.Json;
|
||||
using allstarr.Models.Lyrics;
|
||||
using allstarr.Models.Settings;
|
||||
using allstarr.Services.Common;
|
||||
using allstarr.Services.Jellyfin;
|
||||
using allstarr.Services.Spotify;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
@@ -17,6 +18,7 @@ public class LyricsPrefetchService : BackgroundService
|
||||
private readonly LrclibService _lrclibService;
|
||||
private readonly SpotifyPlaylistFetcher _playlistFetcher;
|
||||
private readonly RedisCacheService _cache;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly ILogger<LyricsPrefetchService> _logger;
|
||||
private readonly string _lyricsCacheDir = "/app/cache/lyrics";
|
||||
private const int DelayBetweenRequestsMs = 500; // 500ms = 2 requests/second to be respectful
|
||||
@@ -26,12 +28,14 @@ public class LyricsPrefetchService : BackgroundService
|
||||
LrclibService lrclibService,
|
||||
SpotifyPlaylistFetcher playlistFetcher,
|
||||
RedisCacheService cache,
|
||||
IServiceProvider serviceProvider,
|
||||
ILogger<LyricsPrefetchService> logger)
|
||||
{
|
||||
_spotifySettings = spotifySettings.Value;
|
||||
_lrclibService = lrclibService;
|
||||
_playlistFetcher = playlistFetcher;
|
||||
_cache = cache;
|
||||
_serviceProvider = serviceProvider;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
@@ -141,7 +145,20 @@ public class LyricsPrefetchService : BackgroundService
|
||||
continue;
|
||||
}
|
||||
|
||||
// Fetch lyrics
|
||||
// Check if this track has local Jellyfin lyrics (embedded in file)
|
||||
var hasLocalLyrics = await CheckForLocalJellyfinLyricsAsync(track.SpotifyId);
|
||||
if (hasLocalLyrics)
|
||||
{
|
||||
cached++;
|
||||
_logger.LogInformation("✓ Local Jellyfin lyrics found for {Artist} - {Track}, skipping LRCLib fetch",
|
||||
track.PrimaryArtist, track.Title);
|
||||
|
||||
// Remove any previously cached LRCLib lyrics for this track
|
||||
await RemoveCachedLyricsAsync(track.PrimaryArtist, track.Title, track.Album, track.DurationMs / 1000);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Fetch lyrics from LRCLib
|
||||
var lyrics = await _lrclibService.GetLyricsAsync(
|
||||
track.Title,
|
||||
track.Artists.ToArray(),
|
||||
@@ -255,4 +272,108 @@ public class LyricsPrefetchService : BackgroundService
|
||||
.Replace(" ", "_")
|
||||
.ToLowerInvariant();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes cached LRCLib lyrics from both Redis and file cache.
|
||||
/// Used when a track has local Jellyfin lyrics, making the LRCLib cache obsolete.
|
||||
/// </summary>
|
||||
private async Task RemoveCachedLyricsAsync(string artist, string title, string album, int duration)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Remove from Redis cache
|
||||
var cacheKey = $"lyrics:{artist}:{title}:{album}:{duration}";
|
||||
await _cache.DeleteAsync(cacheKey);
|
||||
|
||||
// Remove from file cache
|
||||
var fileName = $"{SanitizeFileName(artist)}_{SanitizeFileName(title)}_{duration}.json";
|
||||
var filePath = Path.Combine(_lyricsCacheDir, fileName);
|
||||
|
||||
if (File.Exists(filePath))
|
||||
{
|
||||
File.Delete(filePath);
|
||||
_logger.LogDebug("🗑️ Removed cached LRCLib lyrics file: {FileName}", fileName);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Failed to remove cached lyrics for {Artist} - {Track}", artist, title);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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.
|
||||
/// </summary>
|
||||
private async Task<bool> CheckForLocalJellyfinLyricsAsync(string spotifyTrackId)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var scope = _serviceProvider.CreateScope();
|
||||
var proxyService = scope.ServiceProvider.GetService<JellyfinProxyService>();
|
||||
|
||||
if (proxyService == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Search for the track in Jellyfin by Spotify provider ID
|
||||
var searchParams = new Dictionary<string, string>
|
||||
{
|
||||
["anyProviderIdEquals"] = $"Spotify.{spotifyTrackId}",
|
||||
["includeItemTypes"] = "Audio",
|
||||
["recursive"] = "true",
|
||||
["limit"] = "1"
|
||||
};
|
||||
|
||||
var (searchResult, statusCode) = await proxyService.GetJsonAsync("Items", searchParams, null);
|
||||
|
||||
if (searchResult == null || statusCode != 200)
|
||||
{
|
||||
// Track not found in local library
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if we found any items
|
||||
if (!searchResult.RootElement.TryGetProperty("Items", out var items) ||
|
||||
items.GetArrayLength() == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the first matching track's ID
|
||||
var firstItem = items[0];
|
||||
if (!firstItem.TryGetProperty("Id", out var idElement))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var jellyfinTrackId = idElement.GetString();
|
||||
if (string.IsNullOrEmpty(jellyfinTrackId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if this track has lyrics
|
||||
var (lyricsResult, lyricsStatusCode) = await proxyService.GetJsonAsync(
|
||||
$"Audio/{jellyfinTrackId}/Lyrics",
|
||||
null,
|
||||
null);
|
||||
|
||||
if (lyricsResult != null && lyricsStatusCode == 200)
|
||||
{
|
||||
// Track has embedded lyrics in Jellyfin
|
||||
_logger.LogDebug("Found embedded lyrics in Jellyfin for Spotify track {SpotifyId} (Jellyfin ID: {JellyfinId})",
|
||||
spotifyTrackId, jellyfinTrackId);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogDebug(ex, "Error checking for local Jellyfin lyrics for Spotify track {SpotifyId}", spotifyTrackId);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user