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.NotNull(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 Artist", result[0].Artist);
Assert.Equal("Test Album", result[0].Album);
@@ -89,7 +89,7 @@ public class DeezerMetadataServiceTests
// Assert
Assert.NotNull(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 Artist", result[0].Artist);
Assert.Equal(12, result[0].SongCount);
@@ -123,7 +123,7 @@ public class DeezerMetadataServiceTests
// Assert
Assert.NotNull(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(5, result[0].AlbumCount);
Assert.False(result[0].IsLocal);
@@ -167,7 +167,7 @@ public class DeezerMetadataServiceTests
// Assert
Assert.NotNull(result);
Assert.Equal("ext-deezer-123456", result.Id);
Assert.Equal("ext-deezer-song-123456", result.Id);
Assert.Equal("Test Song", result.Title);
}
@@ -254,7 +254,7 @@ public class DeezerMetadataServiceTests
// Assert
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 Artist", result.Artist);
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-spotify-abc123", true, "spotify", "abc123")]
[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("", false, null, null)]
[InlineData("ext-", false, null, null)]
@@ -217,4 +218,27 @@ public class LocalLibraryServiceTests : IDisposable
Assert.Equal(expectedProvider, provider);
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>
/// 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>
[HttpGet, HttpPost]
[Route("rest/getCoverArt")]
@@ -489,7 +490,7 @@ public class SubsonicController : ControllerBase
return NotFound();
}
var (isExternal, provider, externalId) = _localLibraryService.ParseSongId(id);
var (isExternal, provider, type, externalId) = _localLibraryService.ParseExternalId(id);
if (!isExternal)
{
@@ -507,28 +508,43 @@ public class SubsonicController : ControllerBase
string? coverUrl = null;
var album = await _metadataService.GetAlbumAsync(provider!, externalId!);
if (album?.CoverArtUrl != null)
// Use type to determine which API to call first
switch (type)
{
coverUrl = album.CoverArtUrl;
}
if (coverUrl == null)
{
var song = await _metadataService.GetSongAsync(provider!, externalId!);
if (song?.CoverArtUrl != null)
{
coverUrl = song.CoverArtUrl;
}
}
if (coverUrl == null)
{
var artist = await _metadataService.GetArtistAsync(provider!, externalId!);
if (artist?.ImageUrl != null)
{
coverUrl = artist.ImageUrl;
}
case "artist":
var artist = await _metadataService.GetArtistAsync(provider!, externalId!);
if (artist?.ImageUrl != null)
{
coverUrl = artist.ImageUrl;
}
break;
case "album":
var album = await _metadataService.GetAlbumAsync(provider!, externalId!);
if (album?.CoverArtUrl != null)
{
coverUrl = album.CoverArtUrl;
}
break;
case "song":
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)

View File

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

View File

@@ -30,6 +30,13 @@ public interface ILocalLibraryService
/// </summary>
(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>
/// Déclenche un scan de la bibliothèque Subsonic
/// </summary>
@@ -129,16 +136,42 @@ public class LocalLibraryService : ILocalLibraryService
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);
if (parts.Length == 3)
{
return (true, parts[1], parts[2]);
}
return (false, null, null, null);
}
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()