mirror of
https://github.com/SoPat712/allstarr.git
synced 2026-02-09 23:55:10 -05:00
Add Spotify lyrics sidecar service and integrate with prefetch
- Add spotify-lyrics-api container to docker-compose - Update SpotifyLyricsService to use sidecar API - Prefetch now tries Spotify lyrics first (using track ID), then LRCLib - Add SPOTIFY_LYRICS_API_URL setting - Sidecar handles sp_dc cookie authentication automatically
This commit is contained in:
@@ -29,6 +29,7 @@ public class SpotifyLyricsService
|
||||
private readonly HttpClient _httpClient;
|
||||
|
||||
private const string LyricsApiBase = "https://spclient.wg.spotify.com/color-lyrics/v2/track";
|
||||
private bool _useSidecarApi = false;
|
||||
|
||||
public SpotifyLyricsService(
|
||||
ILogger<SpotifyLyricsService> logger,
|
||||
@@ -45,6 +46,11 @@ public class SpotifyLyricsService
|
||||
_httpClient = httpClientFactory.CreateClient();
|
||||
_httpClient.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0");
|
||||
_httpClient.DefaultRequestHeaders.Add("App-Platform", "WebPlayer");
|
||||
|
||||
// Check if sidecar API is configured and available
|
||||
_useSidecarApi = !string.IsNullOrEmpty(_settings.LyricsApiUrl) &&
|
||||
_settings.LyricsApiUrl != "http://spotify-lyrics:8080" ||
|
||||
_settings.Enabled;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -72,6 +78,64 @@ public class SpotifyLyricsService
|
||||
return cached;
|
||||
}
|
||||
|
||||
// Try sidecar API first if available
|
||||
if (_useSidecarApi && !string.IsNullOrEmpty(_settings.LyricsApiUrl))
|
||||
{
|
||||
var sidecarResult = await GetLyricsFromSidecarAsync(spotifyTrackId);
|
||||
if (sidecarResult != null)
|
||||
{
|
||||
await _cache.SetAsync(cacheKey, sidecarResult, TimeSpan.FromDays(30));
|
||||
return sidecarResult;
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to direct API call
|
||||
return await GetLyricsDirectAsync(spotifyTrackId, cacheKey);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets lyrics from the sidecar spotify-lyrics-api service.
|
||||
/// </summary>
|
||||
private async Task<SpotifyLyricsResult?> GetLyricsFromSidecarAsync(string spotifyTrackId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var url = $"{_settings.LyricsApiUrl}/?trackid={spotifyTrackId}&format=id3";
|
||||
|
||||
_logger.LogDebug("Fetching lyrics from sidecar API: {Url}", url);
|
||||
|
||||
var response = await _httpClient.GetAsync(url);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
_logger.LogDebug("Sidecar API returned {StatusCode} for track {TrackId}",
|
||||
response.StatusCode, spotifyTrackId);
|
||||
return null;
|
||||
}
|
||||
|
||||
var json = await response.Content.ReadAsStringAsync();
|
||||
var result = ParseSidecarResponse(json, spotifyTrackId);
|
||||
|
||||
if (result != null)
|
||||
{
|
||||
_logger.LogInformation("Got Spotify lyrics from sidecar for track {TrackId} ({LineCount} lines)",
|
||||
spotifyTrackId, result.Lines.Count);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Error fetching lyrics from sidecar API for track {TrackId}", spotifyTrackId);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets lyrics directly from Spotify's color-lyrics API.
|
||||
/// </summary>
|
||||
private async Task<SpotifyLyricsResult?> GetLyricsDirectAsync(string spotifyTrackId, string cacheKey)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Get access token
|
||||
@@ -356,6 +420,63 @@ public class SpotifyLyricsService
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses the response from the sidecar spotify-lyrics-api service.
|
||||
/// Format: {"error": false, "syncType": "LINE_SYNCED", "lines": [...]}
|
||||
/// </summary>
|
||||
private SpotifyLyricsResult? ParseSidecarResponse(string json, string trackId)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var doc = JsonDocument.Parse(json);
|
||||
var root = doc.RootElement;
|
||||
|
||||
// Check for error
|
||||
if (root.TryGetProperty("error", out var error) && error.GetBoolean())
|
||||
{
|
||||
_logger.LogDebug("Sidecar API returned error for track {TrackId}", trackId);
|
||||
return null;
|
||||
}
|
||||
|
||||
var result = new SpotifyLyricsResult
|
||||
{
|
||||
SpotifyTrackId = trackId
|
||||
};
|
||||
|
||||
// Get sync type
|
||||
if (root.TryGetProperty("syncType", out var syncType))
|
||||
{
|
||||
result.SyncType = syncType.GetString() ?? "LINE_SYNCED";
|
||||
}
|
||||
|
||||
// Parse lines
|
||||
if (root.TryGetProperty("lines", out var lines))
|
||||
{
|
||||
foreach (var line in lines.EnumerateArray())
|
||||
{
|
||||
var lyricsLine = new SpotifyLyricsLine
|
||||
{
|
||||
StartTimeMs = line.TryGetProperty("startTimeMs", out var start)
|
||||
? long.Parse(start.GetString() ?? "0") : 0,
|
||||
Words = line.TryGetProperty("words", out var words)
|
||||
? words.GetString() ?? "" : "",
|
||||
EndTimeMs = line.TryGetProperty("endTimeMs", out var end)
|
||||
? long.Parse(end.GetString() ?? "0") : 0
|
||||
};
|
||||
|
||||
result.Lines.Add(lyricsLine);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error parsing sidecar API response");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static int? ParseColorValue(JsonElement element)
|
||||
{
|
||||
if (element.ValueKind == JsonValueKind.Number)
|
||||
|
||||
Reference in New Issue
Block a user