Fix Spotify playlist injection: add dedicated route, startup fetch, and clarify config

This commit is contained in:
2026-01-31 16:57:52 -05:00
parent 4ba2245876
commit 2c3ef5c360
4 changed files with 58 additions and 7 deletions

View File

@@ -98,9 +98,9 @@ CACHE_DURATION_HOURS=1
# ===== SPOTIFY PLAYLIST INJECTION (JELLYFIN ONLY) ===== # ===== SPOTIFY PLAYLIST INJECTION (JELLYFIN ONLY) =====
# REQUIRES: Jellyfin Spotify Import Plugin (https://github.com/Viperinius/jellyfin-plugin-spotify-import) # REQUIRES: Jellyfin Spotify Import Plugin (https://github.com/Viperinius/jellyfin-plugin-spotify-import)
# This feature injects virtual Spotify playlists (Release Radar, Discover Weekly) into Jellyfin # This feature intercepts Spotify Import plugin playlists (Release Radar, Discover Weekly) and fills them
# with tracks auto-matched from external providers (SquidWTF, Deezer, Qobuz) # with tracks auto-matched from external providers (SquidWTF, Deezer, Qobuz)
# Uses JELLYFIN_URL and JELLYFIN_API_KEY for API access # Uses JELLYFIN_URL and JELLYFIN_API_KEY configured above (no separate credentials needed)
# Enable Spotify playlist injection (optional, default: false) # Enable Spotify playlist injection (optional, default: false)
SPOTIFY_IMPORT_ENABLED=false SPOTIFY_IMPORT_ENABLED=false

View File

@@ -90,7 +90,7 @@ This project brings together all the music streaming providers into one unified
- **Artist Deduplication**: Merges local and streaming artists to avoid duplicates - **Artist Deduplication**: Merges local and streaming artists to avoid duplicates
- **Album Enrichment**: Adds missing tracks to local albums from streaming providers - **Album Enrichment**: Adds missing tracks to local albums from streaming providers
- **Cover Art Proxy**: Serves cover art for external content - **Cover Art Proxy**: Serves cover art for external content
- **Spotify Playlist Injection** (Jellyfin only): Injects virtual Spotify playlists (Release Radar, Discover Weekly) with tracks auto-matched from streaming providers - **Spotify Playlist Injection** (Jellyfin only): Intercepts Spotify Import plugin playlists (Release Radar, Discover Weekly) and fills them with tracks auto-matched from streaming providers
## Supported Backends ## Supported Backends
@@ -290,7 +290,7 @@ Subsonic__EnableExternalPlaylists=false
### Spotify Playlist Injection (Jellyfin Only) ### Spotify Playlist Injection (Jellyfin Only)
Allstarr can inject virtual Spotify playlists (Release Radar, Discover Weekly) into Jellyfin with tracks automatically matched from your configured streaming provider. Allstarr can intercept Spotify Import plugin playlists (Release Radar, Discover Weekly) and fill them with tracks automatically matched from your configured streaming provider (SquidWTF, Deezer, or Qobuz).
**Requirements:** **Requirements:**
- [Jellyfin Spotify Import Plugin](https://github.com/Viperinius/jellyfin-plugin-spotify-import) installed and configured - [Jellyfin Spotify Import Plugin](https://github.com/Viperinius/jellyfin-plugin-spotify-import) installed and configured
@@ -308,11 +308,12 @@ Allstarr can inject virtual Spotify playlists (Release Radar, Discover Weekly) i
| `SpotifyImport:Playlists` | Array of playlists to inject (Name, SpotifyName, Enabled) | | `SpotifyImport:Playlists` | Array of playlists to inject (Name, SpotifyName, Enabled) |
**How it works:** **How it works:**
1. Jellyfin Spotify Import plugin runs daily and creates missing tracks files for playlists 1. Jellyfin Spotify Import plugin runs daily and creates playlists + missing tracks files
2. Allstarr fetches these files within the configured time window 2. Allstarr fetches these missing tracks files within the configured time window
3. For each missing track, Allstarr searches your streaming provider (SquidWTF, Deezer, or Qobuz) 3. For each missing track, Allstarr searches your streaming provider (SquidWTF, Deezer, or Qobuz)
4. Virtual playlists appear in Jellyfin with matched tracks ready to stream 4. When you open the playlist in Jellyfin, Allstarr intercepts the request and returns matched tracks
5. Tracks are downloaded on-demand when played 5. Tracks are downloaded on-demand when played
6. On startup, Allstarr will fetch missing tracks if it hasn't run in the last 24 hours
**Environment variables:** **Environment variables:**
```bash ```bash

View File

@@ -1210,6 +1210,17 @@ public class JellyfinController : ControllerBase
#region Playlists #region Playlists
/// <summary>
/// Intercepts playlist items requests to inject Spotify playlists.
/// This route must have lower Order than the catch-all ProxyRequest route.
/// </summary>
[HttpGet("Playlists/{playlistId}/Items", Order = 5)]
public async Task<IActionResult> GetPlaylistItems(string playlistId)
{
_logger.LogInformation("GetPlaylistItems called for playlist {PlaylistId}", playlistId);
return await GetPlaylistTracks(playlistId);
}
/// <summary> /// <summary>
/// Gets playlist tracks displayed as an album. /// Gets playlist tracks displayed as an album.
/// </summary> /// </summary>

View File

@@ -13,6 +13,7 @@ public class SpotifyMissingTracksFetcher : BackgroundService
private readonly IHttpClientFactory _httpClientFactory; private readonly IHttpClientFactory _httpClientFactory;
private readonly RedisCacheService _cache; private readonly RedisCacheService _cache;
private readonly ILogger<SpotifyMissingTracksFetcher> _logger; private readonly ILogger<SpotifyMissingTracksFetcher> _logger;
private bool _hasRunOnce = false;
public SpotifyMissingTracksFetcher( public SpotifyMissingTracksFetcher(
IOptions<SpotifyImportSettings> spotifySettings, IOptions<SpotifyImportSettings> spotifySettings,
@@ -47,6 +48,30 @@ public class SpotifyMissingTracksFetcher : BackgroundService
_logger.LogInformation("Spotify missing tracks fetcher started"); _logger.LogInformation("Spotify missing tracks fetcher started");
// Run once on startup if we haven't run in the last 24 hours
if (!_hasRunOnce)
{
var shouldRunOnStartup = await ShouldRunOnStartupAsync();
if (shouldRunOnStartup)
{
_logger.LogInformation("Running initial fetch on startup");
try
{
await FetchMissingTracksAsync(stoppingToken);
_hasRunOnce = true;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error during startup fetch");
}
}
else
{
_logger.LogInformation("Skipping startup fetch - already ran within last 24 hours");
_hasRunOnce = true;
}
}
while (!stoppingToken.IsCancellationRequested) while (!stoppingToken.IsCancellationRequested)
{ {
try try
@@ -62,6 +87,20 @@ public class SpotifyMissingTracksFetcher : BackgroundService
} }
} }
private async Task<bool> ShouldRunOnStartupAsync()
{
// Check if any playlist has cached data from the last 24 hours
foreach (var playlist in _spotifySettings.Value.Playlists.Where(p => p.Enabled))
{
var cacheKey = $"spotify:missing:{playlist.SpotifyName}";
if (await _cache.ExistsAsync(cacheKey))
{
return false; // Already have recent data
}
}
return true; // No recent data, should fetch
}
private async Task FetchMissingTracksAsync(CancellationToken cancellationToken) private async Task FetchMissingTracksAsync(CancellationToken cancellationToken)
{ {
var settings = _spotifySettings.Value; var settings = _spotifySettings.Value;