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);
}