Add LRCLIB lyrics integration for Jellyfin

- Create LrclibService to fetch lyrics from lrclib.net API
- Add LyricsInfo model for lyrics data
- Add /Audio/{itemId}/Lyrics and /Items/{itemId}/Lyrics endpoints
- Support both local and external songs
- Cache lyrics for 30 days in Redis
- Return lyrics in Jellyfin format with synced/plain lyrics
This commit is contained in:
2026-01-30 02:09:27 -05:00
parent ceaa17f018
commit f8969bea8d
4 changed files with 328 additions and 0 deletions

View File

@@ -9,6 +9,7 @@ using allstarr.Services.Common;
using allstarr.Services.Local;
using allstarr.Services.Jellyfin;
using allstarr.Services.Subsonic;
using allstarr.Services.Lyrics;
namespace allstarr.Controllers;
@@ -938,6 +939,94 @@ public class JellyfinController : ControllerBase
#endregion
#region Lyrics
/// <summary>
/// Gets lyrics for an item.
/// </summary>
[HttpGet("Audio/{itemId}/Lyrics")]
[HttpGet("Items/{itemId}/Lyrics")]
public async Task<IActionResult> GetLyrics(string itemId)
{
if (string.IsNullOrWhiteSpace(itemId))
{
return NotFound();
}
var (isExternal, provider, externalId) = _localLibraryService.ParseSongId(itemId);
Song? song = null;
if (isExternal)
{
song = await _metadataService.GetSongAsync(provider!, externalId!);
}
else
{
// For local songs, get metadata from Jellyfin
var item = await _proxyService.GetItemAsync(itemId, Request.Headers);
if (item != null && item.RootElement.TryGetProperty("Type", out var typeEl) &&
typeEl.GetString() == "Audio")
{
song = new Song
{
Title = item.RootElement.TryGetProperty("Name", out var name) ? name.GetString() ?? "" : "",
Artist = item.RootElement.TryGetProperty("AlbumArtist", out var artist) ? artist.GetString() ?? "" : "",
Album = item.RootElement.TryGetProperty("Album", out var album) ? album.GetString() ?? "" : "",
Duration = item.RootElement.TryGetProperty("RunTimeTicks", out var ticks) ? (int)(ticks.GetInt64() / 10000000) : 0
};
}
}
if (song == null)
{
return NotFound(new { error = "Song not found" });
}
// Try to get lyrics from LRCLIB
var lyricsService = HttpContext.RequestServices.GetService<LrclibService>();
if (lyricsService == null)
{
return NotFound(new { error = "Lyrics service not available" });
}
var lyrics = await lyricsService.GetLyricsAsync(
song.Title,
song.Artist ?? "",
song.Album ?? "",
song.Duration);
if (lyrics == null)
{
return NotFound(new { error = "Lyrics not found" });
}
// Return in Jellyfin lyrics format
var response = new
{
Metadata = new
{
Artist = lyrics.ArtistName,
Album = lyrics.AlbumName,
Title = lyrics.TrackName,
Length = lyrics.Duration,
IsSynced = !string.IsNullOrEmpty(lyrics.SyncedLyrics)
},
Lyrics = new[]
{
new
{
Start = (long?)null,
Text = lyrics.SyncedLyrics ?? lyrics.PlainLyrics ?? ""
}
}
};
return Ok(response);
}
#endregion
#region Favorites
/// <summary>