fix: artist, album, songs image problem fixed with new ID format

This commit is contained in:
V1ck3s
2025-12-12 00:25:17 +01:00
committed by Vickes
parent d183853cb2
commit 55727f206f
5 changed files with 114 additions and 41 deletions

View File

@@ -52,7 +52,7 @@ public class DeezerMetadataServiceTests
// Assert // Assert
Assert.NotNull(result); Assert.NotNull(result);
Assert.Single(result); Assert.Single(result);
Assert.Equal("ext-deezer-123456", result[0].Id); Assert.Equal("ext-deezer-song-123456", result[0].Id);
Assert.Equal("Test Song", result[0].Title); Assert.Equal("Test Song", result[0].Title);
Assert.Equal("Test Artist", result[0].Artist); Assert.Equal("Test Artist", result[0].Artist);
Assert.Equal("Test Album", result[0].Album); Assert.Equal("Test Album", result[0].Album);
@@ -89,7 +89,7 @@ public class DeezerMetadataServiceTests
// Assert // Assert
Assert.NotNull(result); Assert.NotNull(result);
Assert.Single(result); Assert.Single(result);
Assert.Equal("ext-deezer-456789", result[0].Id); Assert.Equal("ext-deezer-album-456789", result[0].Id);
Assert.Equal("Test Album", result[0].Title); Assert.Equal("Test Album", result[0].Title);
Assert.Equal("Test Artist", result[0].Artist); Assert.Equal("Test Artist", result[0].Artist);
Assert.Equal(12, result[0].SongCount); Assert.Equal(12, result[0].SongCount);
@@ -123,7 +123,7 @@ public class DeezerMetadataServiceTests
// Assert // Assert
Assert.NotNull(result); Assert.NotNull(result);
Assert.Single(result); Assert.Single(result);
Assert.Equal("ext-deezer-789012", result[0].Id); Assert.Equal("ext-deezer-artist-789012", result[0].Id);
Assert.Equal("Test Artist", result[0].Name); Assert.Equal("Test Artist", result[0].Name);
Assert.Equal(5, result[0].AlbumCount); Assert.Equal(5, result[0].AlbumCount);
Assert.False(result[0].IsLocal); Assert.False(result[0].IsLocal);
@@ -167,7 +167,7 @@ public class DeezerMetadataServiceTests
// Assert // Assert
Assert.NotNull(result); Assert.NotNull(result);
Assert.Equal("ext-deezer-123456", result.Id); Assert.Equal("ext-deezer-song-123456", result.Id);
Assert.Equal("Test Song", result.Title); Assert.Equal("Test Song", result.Title);
} }
@@ -254,7 +254,7 @@ public class DeezerMetadataServiceTests
// Assert // Assert
Assert.NotNull(result); Assert.NotNull(result);
Assert.Equal("ext-deezer-456789", result.Id); Assert.Equal("ext-deezer-album-456789", result.Id);
Assert.Equal("Test Album", result.Title); Assert.Equal("Test Album", result.Title);
Assert.Equal("Test Artist", result.Artist); Assert.Equal("Test Artist", result.Artist);
Assert.Equal(2, result.Songs.Count); Assert.Equal(2, result.Songs.Count);

View File

@@ -203,6 +203,7 @@ public class LocalLibraryServiceTests : IDisposable
[InlineData("ext-deezer-123", true, "deezer", "123")] [InlineData("ext-deezer-123", true, "deezer", "123")]
[InlineData("ext-spotify-abc123", true, "spotify", "abc123")] [InlineData("ext-spotify-abc123", true, "spotify", "abc123")]
[InlineData("ext-tidal-999-888", true, "tidal", "999-888")] [InlineData("ext-tidal-999-888", true, "tidal", "999-888")]
[InlineData("ext-deezer-song-123456", true, "deezer", "123456")] // New format - extracts numeric ID
[InlineData("123456", false, null, null)] [InlineData("123456", false, null, null)]
[InlineData("", false, null, null)] [InlineData("", false, null, null)]
[InlineData("ext-", false, null, null)] [InlineData("ext-", false, null, null)]
@@ -217,4 +218,27 @@ public class LocalLibraryServiceTests : IDisposable
Assert.Equal(expectedProvider, provider); Assert.Equal(expectedProvider, provider);
Assert.Equal(expectedExternalId, externalId); Assert.Equal(expectedExternalId, externalId);
} }
[Theory]
[InlineData("ext-deezer-song-123456", true, "deezer", "song", "123456")]
[InlineData("ext-deezer-album-789012", true, "deezer", "album", "789012")]
[InlineData("ext-deezer-artist-259", true, "deezer", "artist", "259")]
[InlineData("ext-spotify-song-abc123", true, "spotify", "song", "abc123")]
[InlineData("ext-deezer-123", true, "deezer", "song", "123")] // Legacy format defaults to song
[InlineData("ext-tidal-999", true, "tidal", "song", "999")] // Legacy format defaults to song
[InlineData("123456", false, null, null, null)]
[InlineData("", false, null, null, null)]
[InlineData("ext-", false, null, null, null)]
[InlineData("ext-deezer", false, null, null, null)]
public void ParseExternalId_VariousInputs_ReturnsExpected(string id, bool expectedIsExternal, string? expectedProvider, string? expectedType, string? expectedExternalId)
{
// Act
var (isExternal, provider, type, externalId) = _service.ParseExternalId(id);
// Assert
Assert.Equal(expectedIsExternal, isExternal);
Assert.Equal(expectedProvider, provider);
Assert.Equal(expectedType, type);
Assert.Equal(expectedExternalId, externalId);
}
} }

View File

@@ -474,7 +474,8 @@ public class SubsonicController : ControllerBase
} }
/// <summary> /// <summary>
/// Proxies external covers. Tries album first since same ID could match a different track on Deezer. /// Proxies external covers. Uses type from ID to determine which API to call.
/// Format: ext-{provider}-{type}-{id} (e.g., ext-deezer-artist-259, ext-deezer-album-96126)
/// </summary> /// </summary>
[HttpGet, HttpPost] [HttpGet, HttpPost]
[Route("rest/getCoverArt")] [Route("rest/getCoverArt")]
@@ -489,7 +490,7 @@ public class SubsonicController : ControllerBase
return NotFound(); return NotFound();
} }
var (isExternal, provider, externalId) = _localLibraryService.ParseSongId(id); var (isExternal, provider, type, externalId) = _localLibraryService.ParseExternalId(id);
if (!isExternal) if (!isExternal)
{ {
@@ -507,28 +508,43 @@ public class SubsonicController : ControllerBase
string? coverUrl = null; string? coverUrl = null;
var album = await _metadataService.GetAlbumAsync(provider!, externalId!); // Use type to determine which API to call first
if (album?.CoverArtUrl != null) switch (type)
{ {
coverUrl = album.CoverArtUrl; case "artist":
} var artist = await _metadataService.GetArtistAsync(provider!, externalId!);
if (artist?.ImageUrl != null)
if (coverUrl == null) {
{ coverUrl = artist.ImageUrl;
var song = await _metadataService.GetSongAsync(provider!, externalId!); }
if (song?.CoverArtUrl != null) break;
{
coverUrl = song.CoverArtUrl; case "album":
} var album = await _metadataService.GetAlbumAsync(provider!, externalId!);
} if (album?.CoverArtUrl != null)
{
if (coverUrl == null) coverUrl = album.CoverArtUrl;
{ }
var artist = await _metadataService.GetArtistAsync(provider!, externalId!); break;
if (artist?.ImageUrl != null)
{ case "song":
coverUrl = artist.ImageUrl; default:
} // For songs, try to get from song first, then album
var song = await _metadataService.GetSongAsync(provider!, externalId!);
if (song?.CoverArtUrl != null)
{
coverUrl = song.CoverArtUrl;
}
else
{
// Fallback: try album with same ID (legacy behavior)
var albumFallback = await _metadataService.GetAlbumAsync(provider!, externalId!);
if (albumFallback?.CoverArtUrl != null)
{
coverUrl = albumFallback.CoverArtUrl;
}
}
break;
} }
if (coverUrl != null) if (coverUrl != null)

View File

@@ -221,19 +221,19 @@ public class DeezerMetadataService : IMusicMetadataService
return new Song return new Song
{ {
Id = $"ext-deezer-{externalId}", Id = $"ext-deezer-song-{externalId}",
Title = track.GetProperty("title").GetString() ?? "", Title = track.GetProperty("title").GetString() ?? "",
Artist = track.TryGetProperty("artist", out var artist) Artist = track.TryGetProperty("artist", out var artist)
? artist.GetProperty("name").GetString() ?? "" ? artist.GetProperty("name").GetString() ?? ""
: "", : "",
ArtistId = track.TryGetProperty("artist", out var artistForId) ArtistId = track.TryGetProperty("artist", out var artistForId)
? $"ext-deezer-{artistForId.GetProperty("id").GetInt64()}" ? $"ext-deezer-artist-{artistForId.GetProperty("id").GetInt64()}"
: null, : null,
Album = track.TryGetProperty("album", out var album) Album = track.TryGetProperty("album", out var album)
? album.GetProperty("title").GetString() ?? "" ? album.GetProperty("title").GetString() ?? ""
: "", : "",
AlbumId = track.TryGetProperty("album", out var albumForId) AlbumId = track.TryGetProperty("album", out var albumForId)
? $"ext-deezer-{albumForId.GetProperty("id").GetInt64()}" ? $"ext-deezer-album-{albumForId.GetProperty("id").GetInt64()}"
: null, : null,
Duration = track.TryGetProperty("duration", out var duration) Duration = track.TryGetProperty("duration", out var duration)
? duration.GetInt32() ? duration.GetInt32()
@@ -255,13 +255,13 @@ public class DeezerMetadataService : IMusicMetadataService
return new Album return new Album
{ {
Id = $"ext-deezer-{externalId}", Id = $"ext-deezer-album-{externalId}",
Title = album.GetProperty("title").GetString() ?? "", Title = album.GetProperty("title").GetString() ?? "",
Artist = album.TryGetProperty("artist", out var artist) Artist = album.TryGetProperty("artist", out var artist)
? artist.GetProperty("name").GetString() ?? "" ? artist.GetProperty("name").GetString() ?? ""
: "", : "",
ArtistId = album.TryGetProperty("artist", out var artistForId) ArtistId = album.TryGetProperty("artist", out var artistForId)
? $"ext-deezer-{artistForId.GetProperty("id").GetInt64()}" ? $"ext-deezer-artist-{artistForId.GetProperty("id").GetInt64()}"
: null, : null,
Year = album.TryGetProperty("release_date", out var releaseDate) Year = album.TryGetProperty("release_date", out var releaseDate)
? int.TryParse(releaseDate.GetString()?.Split('-')[0], out var year) ? year : null ? int.TryParse(releaseDate.GetString()?.Split('-')[0], out var year) ? year : null
@@ -289,7 +289,7 @@ public class DeezerMetadataService : IMusicMetadataService
return new Artist return new Artist
{ {
Id = $"ext-deezer-{externalId}", Id = $"ext-deezer-artist-{externalId}",
Name = artist.GetProperty("name").GetString() ?? "", Name = artist.GetProperty("name").GetString() ?? "",
ImageUrl = artist.TryGetProperty("picture_medium", out var picture) ImageUrl = artist.TryGetProperty("picture_medium", out var picture)
? picture.GetString() ? picture.GetString()

View File

@@ -30,6 +30,13 @@ public interface ILocalLibraryService
/// </summary> /// </summary>
(bool isExternal, string? provider, string? externalId) ParseSongId(string songId); (bool isExternal, string? provider, string? externalId) ParseSongId(string songId);
/// <summary>
/// Parse un ID externe pour extraire le provider, le type et l'ID
/// Format: ext-{provider}-{type}-{id} (ex: ext-deezer-artist-259, ext-deezer-album-96126, ext-deezer-song-12345)
/// Also supports legacy format: ext-{provider}-{id} (assumes song type)
/// </summary>
(bool isExternal, string? provider, string? type, string? externalId) ParseExternalId(string id);
/// <summary> /// <summary>
/// Déclenche un scan de la bibliothèque Subsonic /// Déclenche un scan de la bibliothèque Subsonic
/// </summary> /// </summary>
@@ -129,16 +136,42 @@ public class LocalLibraryService : ILocalLibraryService
public (bool isExternal, string? provider, string? externalId) ParseSongId(string songId) public (bool isExternal, string? provider, string? externalId) ParseSongId(string songId)
{ {
if (songId.StartsWith("ext-")) var (isExternal, provider, type, externalId) = ParseExternalId(songId);
return (isExternal, provider, externalId);
}
public (bool isExternal, string? provider, string? type, string? externalId) ParseExternalId(string id)
{
if (!id.StartsWith("ext-"))
{ {
var parts = songId.Split('-', 3); return (false, null, null, null);
if (parts.Length == 3)
{
return (true, parts[1], parts[2]);
}
} }
return (false, null, null); var parts = id.Split('-');
// Known types for the new format
var knownTypes = new HashSet<string> { "song", "album", "artist" };
// New format: ext-{provider}-{type}-{id} (e.g., ext-deezer-artist-259)
// Only use new format if parts[2] is a known type
if (parts.Length >= 4 && knownTypes.Contains(parts[2]))
{
var provider = parts[1];
var type = parts[2];
var externalId = string.Join("-", parts.Skip(3)); // Handle IDs with dashes
return (true, provider, type, externalId);
}
// Legacy format: ext-{provider}-{id} (assumes "song" type for backward compatibility)
// This handles both 3-part IDs and 4+ part IDs where parts[2] is NOT a known type
if (parts.Length >= 3)
{
var provider = parts[1];
var externalId = string.Join("-", parts.Skip(2)); // Everything after provider is the ID
return (true, provider, "song", externalId);
}
return (false, null, null, null);
} }
private async Task<Dictionary<string, LocalSongMapping>> LoadMappingsAsync() private async Task<Dictionary<string, LocalSongMapping>> LoadMappingsAsync()