diff --git a/allstarr/Controllers/JellyfinController.cs b/allstarr/Controllers/JellyfinController.cs index 02f71eb..50dd6cc 100644 --- a/allstarr/Controllers/JellyfinController.cs +++ b/allstarr/Controllers/JellyfinController.cs @@ -1165,9 +1165,15 @@ public class JellyfinController : ControllerBase { song = await _metadataService.GetSongAsync(provider!, externalId!); - // Try to find Spotify ID from matched tracks cache - // External tracks from playlists should have been matched and cached - if (song != null) + // Use Spotify ID from song metadata if available (populated during GetSongAsync) + if (song != null && !string.IsNullOrEmpty(song.SpotifyId)) + { + 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); if (!string.IsNullOrEmpty(spotifyTrackId)) @@ -1177,8 +1183,7 @@ public class JellyfinController : ControllerBase } else { - // If no cached Spotify ID, try to convert via Odesli/song.link - // This works for SquidWTF (Tidal), Deezer, Qobuz, etc. + // Last resort: Try to convert via Odesli/song.link spotifyTrackId = await ConvertToSpotifyIdViaOdesliAsync(song, provider!, externalId!); if (!string.IsNullOrEmpty(spotifyTrackId)) { diff --git a/allstarr/Models/Domain/Song.cs b/allstarr/Models/Domain/Song.cs index 55397cb..c61202e 100644 --- a/allstarr/Models/Domain/Song.cs +++ b/allstarr/Models/Domain/Song.cs @@ -44,6 +44,11 @@ public class Song /// public string? Isrc { get; set; } + /// + /// Spotify track ID (for lyrics and matching) + /// + public string? SpotifyId { get; set; } + /// /// Full release date (format: YYYY-MM-DD) /// diff --git a/allstarr/Services/SquidWTF/SquidWTFMetadataService.cs b/allstarr/Services/SquidWTF/SquidWTFMetadataService.cs index c8956bf..2b101ea 100644 --- a/allstarr/Services/SquidWTF/SquidWTFMetadataService.cs +++ b/allstarr/Services/SquidWTF/SquidWTFMetadataService.cs @@ -280,7 +280,40 @@ public class SquidWTFMetadataService : IMusicMetadataService if (!result.RootElement.TryGetProperty("data", out var track)) 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); }