mirror of
https://github.com/SoPat712/allstarr.git
synced 2026-02-11 00:18:38 -05:00
Compare commits
9 Commits
6ea03b8005
...
v1.2.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
d0a7dbcc96
|
|||
|
9c9a827a91
|
|||
|
96889738df
|
|||
|
f3c791496e
|
|||
|
f68706f300
|
|||
|
9f362b4920
|
|||
|
2b09484c0b
|
|||
|
fa9739bfaa
|
|||
|
0ba51e2b30
|
@@ -143,13 +143,6 @@ SPOTIFY_IMPORT_PLAYLISTS=[]
|
|||||||
# Enable direct Spotify API access (default: false)
|
# Enable direct Spotify API access (default: false)
|
||||||
SPOTIFY_API_ENABLED=false
|
SPOTIFY_API_ENABLED=false
|
||||||
|
|
||||||
# Spotify Client ID from https://developer.spotify.com/dashboard
|
|
||||||
# Create an app in the Spotify Developer Dashboard to get this
|
|
||||||
SPOTIFY_API_CLIENT_ID=
|
|
||||||
|
|
||||||
# Spotify Client Secret (optional - only needed for certain OAuth flows)
|
|
||||||
SPOTIFY_API_CLIENT_SECRET=
|
|
||||||
|
|
||||||
# Spotify session cookie (sp_dc) - REQUIRED for editorial playlists
|
# Spotify session cookie (sp_dc) - REQUIRED for editorial playlists
|
||||||
# Editorial playlists (Release Radar, Discover Weekly, etc.) require authentication
|
# Editorial playlists (Release Radar, Discover Weekly, etc.) require authentication
|
||||||
# via session cookie because they're not accessible through the official API.
|
# via session cookie because they're not accessible through the official API.
|
||||||
|
|||||||
@@ -3529,8 +3529,17 @@ public class JellyfinController : ControllerBase
|
|||||||
return null; // Fall back to legacy mode
|
return null; // Fall back to legacy mode
|
||||||
}
|
}
|
||||||
|
|
||||||
// Request MediaSources field to get bitrate info
|
// Pass through all requested fields from the original request
|
||||||
var playlistItemsUrl = $"Playlists/{playlistId}/Items?UserId={userId}&Fields=MediaSources";
|
var queryString = Request.QueryString.Value ?? "";
|
||||||
|
var playlistItemsUrl = $"Playlists/{playlistId}/Items?UserId={userId}";
|
||||||
|
|
||||||
|
// Append the original query string (which includes Fields parameter)
|
||||||
|
if (!string.IsNullOrEmpty(queryString))
|
||||||
|
{
|
||||||
|
// Remove the leading ? if present
|
||||||
|
queryString = queryString.TrimStart('?');
|
||||||
|
playlistItemsUrl = $"{playlistItemsUrl}&{queryString}";
|
||||||
|
}
|
||||||
|
|
||||||
_logger.LogInformation("🔍 Fetching existing tracks from Jellyfin playlist {PlaylistId} with UserId {UserId}",
|
_logger.LogInformation("🔍 Fetching existing tracks from Jellyfin playlist {PlaylistId} with UserId {UserId}",
|
||||||
playlistId, userId);
|
playlistId, userId);
|
||||||
|
|||||||
@@ -18,18 +18,6 @@ public class SpotifyApiSettings
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool Enabled { get; set; }
|
public bool Enabled { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Spotify Client ID from https://developer.spotify.com/dashboard
|
|
||||||
/// Used for OAuth token refresh and API access.
|
|
||||||
/// </summary>
|
|
||||||
public string ClientId { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Spotify Client Secret from https://developer.spotify.com/dashboard
|
|
||||||
/// Optional - only needed for certain OAuth flows.
|
|
||||||
/// </summary>
|
|
||||||
public string ClientSecret { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Spotify session cookie (sp_dc).
|
/// Spotify session cookie (sp_dc).
|
||||||
/// Required for accessing editorial/personalized playlists like Release Radar and Discover Weekly.
|
/// Required for accessing editorial/personalized playlists like Release Radar and Discover Weekly.
|
||||||
|
|||||||
@@ -473,7 +473,8 @@ else if (musicService == MusicService.SquidWTF)
|
|||||||
sp.GetRequiredService<Microsoft.Extensions.Options.IOptions<SquidWTFSettings>>(),
|
sp.GetRequiredService<Microsoft.Extensions.Options.IOptions<SquidWTFSettings>>(),
|
||||||
sp.GetRequiredService<ILogger<SquidWTFMetadataService>>(),
|
sp.GetRequiredService<ILogger<SquidWTFMetadataService>>(),
|
||||||
sp.GetRequiredService<RedisCacheService>(),
|
sp.GetRequiredService<RedisCacheService>(),
|
||||||
squidWtfApiUrls));
|
squidWtfApiUrls,
|
||||||
|
sp.GetRequiredService<GenreEnrichmentService>()));
|
||||||
builder.Services.AddSingleton<IDownloadService>(sp =>
|
builder.Services.AddSingleton<IDownloadService>(sp =>
|
||||||
new SquidWTFDownloadService(
|
new SquidWTFDownloadService(
|
||||||
sp.GetRequiredService<IHttpClientFactory>(),
|
sp.GetRequiredService<IHttpClientFactory>(),
|
||||||
@@ -537,18 +538,6 @@ builder.Services.Configure<allstarr.Models.Settings.SpotifyApiSettings>(options
|
|||||||
options.Enabled = enabled.Equals("true", StringComparison.OrdinalIgnoreCase);
|
options.Enabled = enabled.Equals("true", StringComparison.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
var clientId = builder.Configuration.GetValue<string>("SpotifyApi:ClientId");
|
|
||||||
if (!string.IsNullOrEmpty(clientId))
|
|
||||||
{
|
|
||||||
options.ClientId = clientId;
|
|
||||||
}
|
|
||||||
|
|
||||||
var clientSecret = builder.Configuration.GetValue<string>("SpotifyApi:ClientSecret");
|
|
||||||
if (!string.IsNullOrEmpty(clientSecret))
|
|
||||||
{
|
|
||||||
options.ClientSecret = clientSecret;
|
|
||||||
}
|
|
||||||
|
|
||||||
var sessionCookie = builder.Configuration.GetValue<string>("SpotifyApi:SessionCookie");
|
var sessionCookie = builder.Configuration.GetValue<string>("SpotifyApi:SessionCookie");
|
||||||
if (!string.IsNullOrEmpty(sessionCookie))
|
if (!string.IsNullOrEmpty(sessionCookie))
|
||||||
{
|
{
|
||||||
@@ -576,7 +565,6 @@ builder.Services.Configure<allstarr.Models.Settings.SpotifyApiSettings>(options
|
|||||||
// Log configuration (mask sensitive values)
|
// Log configuration (mask sensitive values)
|
||||||
Console.WriteLine($"SpotifyApi Configuration:");
|
Console.WriteLine($"SpotifyApi Configuration:");
|
||||||
Console.WriteLine($" Enabled: {options.Enabled}");
|
Console.WriteLine($" Enabled: {options.Enabled}");
|
||||||
Console.WriteLine($" ClientId: {(string.IsNullOrEmpty(options.ClientId) ? "(not set)" : options.ClientId[..8] + "...")}");
|
|
||||||
Console.WriteLine($" SessionCookie: {(string.IsNullOrEmpty(options.SessionCookie) ? "(not set)" : "***" + options.SessionCookie[^8..])}");
|
Console.WriteLine($" SessionCookie: {(string.IsNullOrEmpty(options.SessionCookie) ? "(not set)" : "***" + options.SessionCookie[^8..])}");
|
||||||
Console.WriteLine($" SessionCookieSetDate: {options.SessionCookieSetDate ?? "(not set)"}");
|
Console.WriteLine($" SessionCookieSetDate: {options.SessionCookieSetDate ?? "(not set)"}");
|
||||||
Console.WriteLine($" CacheDurationMinutes: {options.CacheDurationMinutes}");
|
Console.WriteLine($" CacheDurationMinutes: {options.CacheDurationMinutes}");
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using allstarr.Models.Settings;
|
|||||||
using allstarr.Models.Download;
|
using allstarr.Models.Download;
|
||||||
using allstarr.Models.Search;
|
using allstarr.Models.Search;
|
||||||
using allstarr.Models.Subsonic;
|
using allstarr.Models.Subsonic;
|
||||||
|
using allstarr.Services.Common;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
@@ -15,12 +16,17 @@ public class DeezerMetadataService : IMusicMetadataService
|
|||||||
{
|
{
|
||||||
private readonly HttpClient _httpClient;
|
private readonly HttpClient _httpClient;
|
||||||
private readonly SubsonicSettings _settings;
|
private readonly SubsonicSettings _settings;
|
||||||
|
private readonly GenreEnrichmentService? _genreEnrichment;
|
||||||
private const string BaseUrl = "https://api.deezer.com";
|
private const string BaseUrl = "https://api.deezer.com";
|
||||||
|
|
||||||
public DeezerMetadataService(IHttpClientFactory httpClientFactory, IOptions<SubsonicSettings> settings)
|
public DeezerMetadataService(
|
||||||
|
IHttpClientFactory httpClientFactory,
|
||||||
|
IOptions<SubsonicSettings> settings,
|
||||||
|
GenreEnrichmentService? genreEnrichment = null)
|
||||||
{
|
{
|
||||||
_httpClient = httpClientFactory.CreateClient();
|
_httpClient = httpClientFactory.CreateClient();
|
||||||
_settings = settings.Value;
|
_settings = settings.Value;
|
||||||
|
_genreEnrichment = genreEnrichment;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<Song>> SearchSongsAsync(string query, int limit = 20)
|
public async Task<List<Song>> SearchSongsAsync(string query, int limit = 20)
|
||||||
@@ -203,6 +209,12 @@ public class DeezerMetadataService : IMusicMetadataService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Enrich with MusicBrainz genres if missing
|
||||||
|
if (_genreEnrichment != null && string.IsNullOrEmpty(song.Genre))
|
||||||
|
{
|
||||||
|
await _genreEnrichment.EnrichSongGenreAsync(song);
|
||||||
|
}
|
||||||
|
|
||||||
return song;
|
return song;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -167,16 +167,14 @@ public class LyricsStartupValidator : BaseStartupValidator
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(_spotifySettings.ClientId))
|
if (!_spotifySettings.Enabled)
|
||||||
{
|
{
|
||||||
WriteStatus("Spotify API", "NOT CONFIGURED", ConsoleColor.Yellow);
|
WriteStatus("Spotify API", "DISABLED", ConsoleColor.Gray);
|
||||||
WriteDetail("Set SpotifyApi__ClientId to enable");
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
WriteStatus("Spotify API", "CONFIGURED", ConsoleColor.Green);
|
WriteStatus("Spotify API", "CONFIGURED", ConsoleColor.Green);
|
||||||
WriteDetail($"Client ID: {_spotifySettings.ClientId.Substring(0, Math.Min(8, _spotifySettings.ClientId.Length))}...");
|
WriteDetail("Note: Spotify API is used for track matching and lyrics");
|
||||||
WriteDetail("Note: Spotify API is used for track matching, not lyrics");
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using allstarr.Models.Settings;
|
|||||||
using allstarr.Models.Download;
|
using allstarr.Models.Download;
|
||||||
using allstarr.Models.Search;
|
using allstarr.Models.Search;
|
||||||
using allstarr.Models.Subsonic;
|
using allstarr.Models.Subsonic;
|
||||||
|
using allstarr.Services.Common;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
@@ -18,6 +19,7 @@ public class QobuzMetadataService : IMusicMetadataService
|
|||||||
private readonly SubsonicSettings _settings;
|
private readonly SubsonicSettings _settings;
|
||||||
private readonly QobuzBundleService _bundleService;
|
private readonly QobuzBundleService _bundleService;
|
||||||
private readonly ILogger<QobuzMetadataService> _logger;
|
private readonly ILogger<QobuzMetadataService> _logger;
|
||||||
|
private readonly GenreEnrichmentService? _genreEnrichment;
|
||||||
private readonly string? _userAuthToken;
|
private readonly string? _userAuthToken;
|
||||||
private readonly string? _userId;
|
private readonly string? _userId;
|
||||||
|
|
||||||
@@ -28,12 +30,14 @@ public class QobuzMetadataService : IMusicMetadataService
|
|||||||
IOptions<SubsonicSettings> settings,
|
IOptions<SubsonicSettings> settings,
|
||||||
IOptions<QobuzSettings> qobuzSettings,
|
IOptions<QobuzSettings> qobuzSettings,
|
||||||
QobuzBundleService bundleService,
|
QobuzBundleService bundleService,
|
||||||
ILogger<QobuzMetadataService> logger)
|
ILogger<QobuzMetadataService> logger,
|
||||||
|
GenreEnrichmentService? genreEnrichment = null)
|
||||||
{
|
{
|
||||||
_httpClient = httpClientFactory.CreateClient();
|
_httpClient = httpClientFactory.CreateClient();
|
||||||
_settings = settings.Value;
|
_settings = settings.Value;
|
||||||
_bundleService = bundleService;
|
_bundleService = bundleService;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
_genreEnrichment = genreEnrichment;
|
||||||
|
|
||||||
var qobuzConfig = qobuzSettings.Value;
|
var qobuzConfig = qobuzSettings.Value;
|
||||||
_userAuthToken = qobuzConfig.UserAuthToken;
|
_userAuthToken = qobuzConfig.UserAuthToken;
|
||||||
@@ -177,7 +181,15 @@ public class QobuzMetadataService : IMusicMetadataService
|
|||||||
|
|
||||||
if (track.TryGetProperty("error", out _)) return null;
|
if (track.TryGetProperty("error", out _)) return null;
|
||||||
|
|
||||||
return ParseQobuzTrackFull(track);
|
var song = ParseQobuzTrackFull(track);
|
||||||
|
|
||||||
|
// Enrich with MusicBrainz genres if missing
|
||||||
|
if (_genreEnrichment != null && song != null && string.IsNullOrEmpty(song.Genre))
|
||||||
|
{
|
||||||
|
await _genreEnrichment.EnrichSongGenreAsync(song);
|
||||||
|
}
|
||||||
|
|
||||||
|
return song;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ public class SquidWTFMetadataService : IMusicMetadataService
|
|||||||
private readonly ILogger<SquidWTFMetadataService> _logger;
|
private readonly ILogger<SquidWTFMetadataService> _logger;
|
||||||
private readonly RedisCacheService _cache;
|
private readonly RedisCacheService _cache;
|
||||||
private readonly RoundRobinFallbackHelper _fallbackHelper;
|
private readonly RoundRobinFallbackHelper _fallbackHelper;
|
||||||
|
private readonly GenreEnrichmentService? _genreEnrichment;
|
||||||
|
|
||||||
public SquidWTFMetadataService(
|
public SquidWTFMetadataService(
|
||||||
IHttpClientFactory httpClientFactory,
|
IHttpClientFactory httpClientFactory,
|
||||||
@@ -63,13 +64,15 @@ public class SquidWTFMetadataService : IMusicMetadataService
|
|||||||
IOptions<SquidWTFSettings> squidwtfSettings,
|
IOptions<SquidWTFSettings> squidwtfSettings,
|
||||||
ILogger<SquidWTFMetadataService> logger,
|
ILogger<SquidWTFMetadataService> logger,
|
||||||
RedisCacheService cache,
|
RedisCacheService cache,
|
||||||
List<string> apiUrls)
|
List<string> apiUrls,
|
||||||
|
GenreEnrichmentService? genreEnrichment = null)
|
||||||
{
|
{
|
||||||
_httpClient = httpClientFactory.CreateClient();
|
_httpClient = httpClientFactory.CreateClient();
|
||||||
_settings = settings.Value;
|
_settings = settings.Value;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_cache = cache;
|
_cache = cache;
|
||||||
_fallbackHelper = new RoundRobinFallbackHelper(apiUrls, logger, "SquidWTF");
|
_fallbackHelper = new RoundRobinFallbackHelper(apiUrls, logger, "SquidWTF");
|
||||||
|
_genreEnrichment = genreEnrichment;
|
||||||
|
|
||||||
// Set up default headers
|
// Set up default headers
|
||||||
_httpClient.DefaultRequestHeaders.Add("User-Agent",
|
_httpClient.DefaultRequestHeaders.Add("User-Agent",
|
||||||
@@ -286,6 +289,12 @@ public class SquidWTFMetadataService : IMusicMetadataService
|
|||||||
|
|
||||||
var song = ParseTidalTrackFull(track);
|
var song = ParseTidalTrackFull(track);
|
||||||
|
|
||||||
|
// Enrich with MusicBrainz genres if missing (SquidWTF/Tidal doesn't provide genres)
|
||||||
|
if (_genreEnrichment != null && string.IsNullOrEmpty(song.Genre))
|
||||||
|
{
|
||||||
|
await _genreEnrichment.EnrichSongGenreAsync(song);
|
||||||
|
}
|
||||||
|
|
||||||
// NOTE: Spotify ID conversion happens during download (in SquidWTFDownloadService)
|
// NOTE: Spotify ID conversion happens during download (in SquidWTFDownloadService)
|
||||||
// This avoids redundant conversions and ensures it's done in parallel with the download
|
// This avoids redundant conversions and ensures it's done in parallel with the download
|
||||||
|
|
||||||
|
|||||||
@@ -5,9 +5,9 @@
|
|||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<RootNamespace>allstarr</RootNamespace>
|
<RootNamespace>allstarr</RootNamespace>
|
||||||
<Version>1.0.0</Version>
|
<Version>1.2.2</Version>
|
||||||
<AssemblyVersion>1.0.0.0</AssemblyVersion>
|
<AssemblyVersion>1.2.2.0</AssemblyVersion>
|
||||||
<FileVersion>1.0.0.0</FileVersion>
|
<FileVersion>1.2.2.0</FileVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -61,8 +61,6 @@
|
|||||||
},
|
},
|
||||||
"SpotifyApi": {
|
"SpotifyApi": {
|
||||||
"Enabled": false,
|
"Enabled": false,
|
||||||
"ClientId": "",
|
|
||||||
"ClientSecret": "",
|
|
||||||
"SessionCookie": "",
|
"SessionCookie": "",
|
||||||
"CacheDurationMinutes": 60,
|
"CacheDurationMinutes": 60,
|
||||||
"RateLimitDelayMs": 100,
|
"RateLimitDelayMs": 100,
|
||||||
|
|||||||
@@ -107,8 +107,6 @@ services:
|
|||||||
|
|
||||||
# ===== SPOTIFY DIRECT API (for lyrics, ISRC matching, track ordering) =====
|
# ===== SPOTIFY DIRECT API (for lyrics, ISRC matching, track ordering) =====
|
||||||
- SpotifyApi__Enabled=${SPOTIFY_API_ENABLED:-false}
|
- SpotifyApi__Enabled=${SPOTIFY_API_ENABLED:-false}
|
||||||
- SpotifyApi__ClientId=${SPOTIFY_API_CLIENT_ID:-}
|
|
||||||
- SpotifyApi__ClientSecret=${SPOTIFY_API_CLIENT_SECRET:-}
|
|
||||||
- SpotifyApi__SessionCookie=${SPOTIFY_API_SESSION_COOKIE:-}
|
- SpotifyApi__SessionCookie=${SPOTIFY_API_SESSION_COOKIE:-}
|
||||||
- SpotifyApi__SessionCookieSetDate=${SPOTIFY_API_SESSION_COOKIE_SET_DATE:-}
|
- SpotifyApi__SessionCookieSetDate=${SPOTIFY_API_SESSION_COOKIE_SET_DATE:-}
|
||||||
- SpotifyApi__CacheDurationMinutes=${SPOTIFY_API_CACHE_DURATION_MINUTES:-60}
|
- SpotifyApi__CacheDurationMinutes=${SPOTIFY_API_CACHE_DURATION_MINUTES:-60}
|
||||||
|
|||||||
Reference in New Issue
Block a user