mirror of
https://github.com/SoPat712/allstarr.git
synced 2026-02-09 23:55:10 -05:00
feat: convert Tidal tracks to Spotify ID immediately for lyrics
- Added SpotifyId field to Song model - SquidWTFMetadataService now calls Odesli API when fetching track metadata - Spotify ID is populated immediately when track is loaded, not during lyrics fetch - GetLyrics now checks song.SpotifyId first before falling back to cache/Odesli - Enables Spotify lyrics for all SquidWTF (Tidal) tracks automatically - Reduces latency - conversion happens once during track load, not every lyrics request
This commit is contained in:
@@ -1165,9 +1165,15 @@ public class JellyfinController : ControllerBase
|
|||||||
{
|
{
|
||||||
song = await _metadataService.GetSongAsync(provider!, externalId!);
|
song = await _metadataService.GetSongAsync(provider!, externalId!);
|
||||||
|
|
||||||
// Try to find Spotify ID from matched tracks cache
|
// Use Spotify ID from song metadata if available (populated during GetSongAsync)
|
||||||
// External tracks from playlists should have been matched and cached
|
if (song != null && !string.IsNullOrEmpty(song.SpotifyId))
|
||||||
if (song != null)
|
{
|
||||||
|
spotifyTrackId = song.SpotifyId;
|
||||||
|
_logger.LogInformation("Using Spotify ID {SpotifyId} from song metadata for {Provider}/{ExternalId}",
|
||||||
|
spotifyTrackId, provider, externalId);
|
||||||
|
}
|
||||||
|
// Fallback: Try to find Spotify ID from matched tracks cache
|
||||||
|
else if (song != null)
|
||||||
{
|
{
|
||||||
spotifyTrackId = await FindSpotifyIdForExternalTrackAsync(song);
|
spotifyTrackId = await FindSpotifyIdForExternalTrackAsync(song);
|
||||||
if (!string.IsNullOrEmpty(spotifyTrackId))
|
if (!string.IsNullOrEmpty(spotifyTrackId))
|
||||||
@@ -1177,8 +1183,7 @@ public class JellyfinController : ControllerBase
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// If no cached Spotify ID, try to convert via Odesli/song.link
|
// Last resort: Try to convert via Odesli/song.link
|
||||||
// This works for SquidWTF (Tidal), Deezer, Qobuz, etc.
|
|
||||||
spotifyTrackId = await ConvertToSpotifyIdViaOdesliAsync(song, provider!, externalId!);
|
spotifyTrackId = await ConvertToSpotifyIdViaOdesliAsync(song, provider!, externalId!);
|
||||||
if (!string.IsNullOrEmpty(spotifyTrackId))
|
if (!string.IsNullOrEmpty(spotifyTrackId))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -44,6 +44,11 @@ public class Song
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string? Isrc { get; set; }
|
public string? Isrc { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Spotify track ID (for lyrics and matching)
|
||||||
|
/// </summary>
|
||||||
|
public string? SpotifyId { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Full release date (format: YYYY-MM-DD)
|
/// Full release date (format: YYYY-MM-DD)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -280,7 +280,40 @@ public class SquidWTFMetadataService : IMusicMetadataService
|
|||||||
if (!result.RootElement.TryGetProperty("data", out var track))
|
if (!result.RootElement.TryGetProperty("data", out var track))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return ParseTidalTrackFull(track);
|
var song = ParseTidalTrackFull(track);
|
||||||
|
|
||||||
|
// Convert to Spotify ID via Odesli for lyrics support
|
||||||
|
if (song != null && !string.IsNullOrEmpty(externalId))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var tidalUrl = $"https://tidal.com/browse/track/{externalId}";
|
||||||
|
var odesliUrl = $"https://api.song.link/v1-alpha.1/links?url={Uri.EscapeDataString(tidalUrl)}&userCountry=US";
|
||||||
|
|
||||||
|
_logger.LogDebug("🔗 Converting Tidal track {ExternalId} to Spotify ID via Odesli", externalId);
|
||||||
|
|
||||||
|
var odesliResponse = await _httpClient.GetAsync(odesliUrl);
|
||||||
|
if (odesliResponse.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var odesliJson = await odesliResponse.Content.ReadAsStringAsync();
|
||||||
|
var odesliDoc = JsonDocument.Parse(odesliJson);
|
||||||
|
|
||||||
|
if (odesliDoc.RootElement.TryGetProperty("linksByPlatform", out var platforms) &&
|
||||||
|
platforms.TryGetProperty("spotify", out var spotifyPlatform) &&
|
||||||
|
spotifyPlatform.TryGetProperty("entityUniqueId", out var spotifyIdEl))
|
||||||
|
{
|
||||||
|
song.SpotifyId = spotifyIdEl.GetString();
|
||||||
|
_logger.LogInformation("✓ Converted squidwtf/{ExternalId} → Spotify ID {SpotifyId}", externalId, song.SpotifyId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogDebug(ex, "Failed to convert Tidal track to Spotify ID via Odesli");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return song;
|
||||||
}, (Song?)null);
|
}, (Song?)null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user