2 Commits

Author SHA1 Message Date
d0a7dbcc96 v1.2.2: fix metadata loss in Spotify playlists
Spotify playlist tracks were missing genres, composers, and other metadata because the proxy only requested MediaSources field instead of passing through all client-requested fields.
2026-02-10 11:01:38 -05:00
9c9a827a91 v1.2.1: MusicBrainz genre enrichment + cleanup
## Features
- Implement automatic MusicBrainz genre enrichment for all external sources
  - Deezer: Enriches when genre missing
  - Qobuz: Enriches when genre missing
  - SquidWTF/Tidal: Always enriches (Tidal doesn't provide genres)
- Use ISRC codes for exact matching, fallback to title/artist search
- Cache results in Redis (30 days) + file cache for performance
- Respect MusicBrainz rate limits (1 req/sec)

## Cleanup
- Remove unused Spotify API ClientId and ClientSecret settings
- Simplify Spotify API configuration

## Fixes
- Make GenreEnrichmentService optional to fix test failures
- All 225 tests passing

This ensures all external tracks have genre metadata for better
organization and filtering in music clients.
2026-02-10 10:29:49 -05:00
5 changed files with 23 additions and 14 deletions

View File

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

View File

@@ -16,13 +16,13 @@ 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 readonly GenreEnrichmentService? _genreEnrichment;
private const string BaseUrl = "https://api.deezer.com"; private const string BaseUrl = "https://api.deezer.com";
public DeezerMetadataService( public DeezerMetadataService(
IHttpClientFactory httpClientFactory, IHttpClientFactory httpClientFactory,
IOptions<SubsonicSettings> settings, IOptions<SubsonicSettings> settings,
GenreEnrichmentService genreEnrichment) GenreEnrichmentService? genreEnrichment = null)
{ {
_httpClient = httpClientFactory.CreateClient(); _httpClient = httpClientFactory.CreateClient();
_settings = settings.Value; _settings = settings.Value;
@@ -210,7 +210,7 @@ public class DeezerMetadataService : IMusicMetadataService
} }
// Enrich with MusicBrainz genres if missing // Enrich with MusicBrainz genres if missing
if (string.IsNullOrEmpty(song.Genre)) if (_genreEnrichment != null && string.IsNullOrEmpty(song.Genre))
{ {
await _genreEnrichment.EnrichSongGenreAsync(song); await _genreEnrichment.EnrichSongGenreAsync(song);
} }

View File

@@ -19,7 +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 GenreEnrichmentService? _genreEnrichment;
private readonly string? _userAuthToken; private readonly string? _userAuthToken;
private readonly string? _userId; private readonly string? _userId;
@@ -31,7 +31,7 @@ public class QobuzMetadataService : IMusicMetadataService
IOptions<QobuzSettings> qobuzSettings, IOptions<QobuzSettings> qobuzSettings,
QobuzBundleService bundleService, QobuzBundleService bundleService,
ILogger<QobuzMetadataService> logger, ILogger<QobuzMetadataService> logger,
GenreEnrichmentService genreEnrichment) GenreEnrichmentService? genreEnrichment = null)
{ {
_httpClient = httpClientFactory.CreateClient(); _httpClient = httpClientFactory.CreateClient();
_settings = settings.Value; _settings = settings.Value;
@@ -184,7 +184,7 @@ public class QobuzMetadataService : IMusicMetadataService
var song = ParseQobuzTrackFull(track); var song = ParseQobuzTrackFull(track);
// Enrich with MusicBrainz genres if missing // Enrich with MusicBrainz genres if missing
if (song != null && string.IsNullOrEmpty(song.Genre)) if (_genreEnrichment != null && song != null && string.IsNullOrEmpty(song.Genre))
{ {
await _genreEnrichment.EnrichSongGenreAsync(song); await _genreEnrichment.EnrichSongGenreAsync(song);
} }

View File

@@ -56,7 +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; private readonly GenreEnrichmentService? _genreEnrichment;
public SquidWTFMetadataService( public SquidWTFMetadataService(
IHttpClientFactory httpClientFactory, IHttpClientFactory httpClientFactory,
@@ -65,7 +65,7 @@ public class SquidWTFMetadataService : IMusicMetadataService
ILogger<SquidWTFMetadataService> logger, ILogger<SquidWTFMetadataService> logger,
RedisCacheService cache, RedisCacheService cache,
List<string> apiUrls, List<string> apiUrls,
GenreEnrichmentService genreEnrichment) GenreEnrichmentService? genreEnrichment = null)
{ {
_httpClient = httpClientFactory.CreateClient(); _httpClient = httpClientFactory.CreateClient();
_settings = settings.Value; _settings = settings.Value;
@@ -290,7 +290,7 @@ public class SquidWTFMetadataService : IMusicMetadataService
var song = ParseTidalTrackFull(track); var song = ParseTidalTrackFull(track);
// Enrich with MusicBrainz genres if missing (SquidWTF/Tidal doesn't provide genres) // Enrich with MusicBrainz genres if missing (SquidWTF/Tidal doesn't provide genres)
if (string.IsNullOrEmpty(song.Genre)) if (_genreEnrichment != null && string.IsNullOrEmpty(song.Genre))
{ {
await _genreEnrichment.EnrichSongGenreAsync(song); await _genreEnrichment.EnrichSongGenreAsync(song);
} }

View File

@@ -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>