From 862037a9d5b0ecd63ac9790d9e2ef931ac476e2f Mon Sep 17 00:00:00 2001 From: zarzet Date: Thu, 27 Aug 2026 22:04:45 +0700 Subject: [PATCH] fix(metadata): preserve selected release artwork and title --- go_backend/exports_download.go | 8 +-- go_backend/exports_test.go | 58 +++++++++++++++++-- go_backend/extension_fallback.go | 32 +--------- go_backend/extension_fallback_helpers.go | 43 ++++++++++++++ .../extension_fallback_metadata_test.go | 48 +++++++++++++++ 5 files changed, 150 insertions(+), 39 deletions(-) diff --git a/go_backend/exports_download.go b/go_backend/exports_download.go index d6442257..9c6d2418 100644 --- a/go_backend/exports_download.go +++ b/go_backend/exports_download.go @@ -150,9 +150,9 @@ func buildDownloadSuccessResponse( filePath string, alreadyExists bool, ) DownloadResponse { - title := result.Title + title := strings.TrimSpace(req.TrackName) if title == "" { - title = req.TrackName + title = strings.TrimSpace(result.Title) } artist := result.Artist @@ -207,9 +207,9 @@ func buildDownloadSuccessResponse( upc = req.UPC } - coverURL := strings.TrimSpace(result.CoverURL) + coverURL := strings.TrimSpace(req.CoverURL) if coverURL == "" { - coverURL = strings.TrimSpace(req.CoverURL) + coverURL = strings.TrimSpace(result.CoverURL) } return DownloadResponse{ diff --git a/go_backend/exports_test.go b/go_backend/exports_test.go index 715dcaf5..13424769 100644 --- a/go_backend/exports_test.go +++ b/go_backend/exports_test.go @@ -105,9 +105,54 @@ func TestPreferredReleaseMetadataPrefersRequestValues(t *testing.T) { } } -func TestBuildDownloadSuccessResponsePrefersProviderCoverURL(t *testing.T) { +func TestBuildDownloadSuccessResponsePreservesRequestedTrackAndCover(t *testing.T) { + req := DownloadRequest{ + TrackName: "Album Track Title", + ArtistName: "Artist", + AlbumName: "Original Album", + AlbumArtist: "Artist", + CoverURL: "https://cdn.source.test/original-album.jpg", + ISRC: "USAAA2600001", + } + + result := DownloadResult{ + Title: "Single Version Title", + Artist: "Artist", + Album: "Single Version Title", + CoverURL: "https://cdn.provider.test/single.jpg", + ISRC: "USAAA2600001", + } + + resp := buildDownloadSuccessResponse( + req, + result, + "generic-provider", + "ok", + "/tmp/test.flac", + false, + ) + overlayExtensionDownloadMetadata(&resp, &ExtDownloadResult{ + Title: result.Title, + Artist: result.Artist, + Album: result.Album, + CoverURL: result.CoverURL, + ISRC: result.ISRC, + }) + applyExtensionRequestFallbacks(&resp, req) + + if resp.Title != req.TrackName { + t.Fatalf("title = %q, want requested title %q", resp.Title, req.TrackName) + } + if resp.Album != req.AlbumName { + t.Fatalf("album = %q, want requested album %q", resp.Album, req.AlbumName) + } + if resp.CoverURL != req.CoverURL { + t.Fatalf("cover url = %q, want requested album cover %q", resp.CoverURL, req.CoverURL) + } +} + +func TestBuildDownloadSuccessResponseFallsBackToProviderTrackAndCover(t *testing.T) { req := DownloadRequest{ - TrackName: "Track", ArtistName: "Artist", AlbumName: "Album", AlbumArtist: "Artist", @@ -117,20 +162,23 @@ func TestBuildDownloadSuccessResponsePrefersProviderCoverURL(t *testing.T) { Title: "Track", Artist: "Artist", Album: "Album", - CoverURL: "https://cdn.qobuz.test/cover.jpg", + CoverURL: "https://cdn.provider.test/cover.jpg", } resp := buildDownloadSuccessResponse( req, result, - "qobuz", + "generic-provider", "ok", "/tmp/test.flac", false, ) + if resp.Title != result.Title { + t.Fatalf("title = %q, want provider fallback %q", resp.Title, result.Title) + } if resp.CoverURL != result.CoverURL { - t.Fatalf("cover url = %q, want %q", resp.CoverURL, result.CoverURL) + t.Fatalf("cover url = %q, want provider fallback %q", resp.CoverURL, result.CoverURL) } } diff --git a/go_backend/extension_fallback.go b/go_backend/extension_fallback.go index d42b3790..3aa696c3 100644 --- a/go_backend/extension_fallback.go +++ b/go_backend/extension_fallback.go @@ -430,30 +430,7 @@ func DownloadWithExtensionFallback(req DownloadRequest) (*DownloadResponse, erro GoLog("[DownloadWithExtensionFallback] Enriching track from extension '%s'...\n", req.Source) provider := newExtensionProviderWrapper(ext) - trackMeta := &ExtTrackMetadata{ - ID: req.SpotifyID, - Name: req.TrackName, - Artists: req.ArtistName, - AlbumName: req.AlbumName, - AlbumArtist: req.AlbumArtist, - DurationMS: req.DurationMS, - CoverURL: req.CoverURL, - ISRC: req.ISRC, - ReleaseDate: req.ReleaseDate, - TrackNumber: req.TrackNumber, - TotalTracks: req.TotalTracks, - DiscNumber: req.DiscNumber, - TotalDiscs: req.TotalDiscs, - ProviderID: req.Source, - AlbumType: req.AlbumType, - Explicit: req.Explicit, - UPC: req.UPC, - Label: req.Label, - Copyright: req.Copyright, - Genre: req.Genre, - Composer: req.Composer, - Comment: req.Comment, - } + trackMeta := buildSourceExtensionTrackMetadata(req) enrichedTrack, err := provider.EnrichTrackForItemID(trackMeta, req.ItemID) if shouldAbortCancelledFallback(req.ItemID, err) { @@ -476,12 +453,7 @@ func DownloadWithExtensionFallback(req DownloadRequest) (*DownloadResponse, erro GoLog("[DownloadWithExtensionFallback] Deezer ID from Odesli: %s\n", enrichedTrack.DeezerID) req.DeezerID = enrichedTrack.DeezerID } - if enrichedTrack.Name != "" { - req.TrackName = enrichedTrack.Name - } - if enrichedTrack.Artists != "" { - req.ArtistName = enrichedTrack.Artists - } + overlaySourceExtensionTrackIdentity(&req, *enrichedTrack) overlayStr(&req.AlbumName, enrichedTrack.AlbumName, "AlbumName") overlayStr(&req.AlbumArtist, enrichedTrack.AlbumArtist, "") overlayInt(&req.DurationMS, enrichedTrack.DurationMS, "DurationMS") diff --git a/go_backend/extension_fallback_helpers.go b/go_backend/extension_fallback_helpers.go index 9e6f8086..5a854c95 100644 --- a/go_backend/extension_fallback_helpers.go +++ b/go_backend/extension_fallback_helpers.go @@ -72,6 +72,49 @@ func trimKnownProviderPrefix(trackID, providerID string) string { return trimmedID } +func buildSourceExtensionTrackMetadata(req DownloadRequest) *ExtTrackMetadata { + return &ExtTrackMetadata{ + ID: req.SpotifyID, + Name: req.TrackName, + Artists: req.ArtistName, + AlbumName: req.AlbumName, + AlbumArtist: req.AlbumArtist, + DurationMS: req.DurationMS, + CoverURL: req.CoverURL, + ISRC: req.ISRC, + ReleaseDate: req.ReleaseDate, + TrackNumber: req.TrackNumber, + TotalTracks: req.TotalTracks, + DiscNumber: req.DiscNumber, + TotalDiscs: req.TotalDiscs, + ProviderID: req.Source, + AlbumType: req.AlbumType, + Explicit: req.Explicit, + UPC: req.UPC, + TidalID: req.TidalID, + QobuzID: req.QobuzID, + DeezerID: req.DeezerID, + SpotifyID: req.SpotifyID, + Label: req.Label, + Copyright: req.Copyright, + Genre: req.Genre, + Composer: req.Composer, + Comment: req.Comment, + } +} + +func overlaySourceExtensionTrackIdentity(req *DownloadRequest, enrichedTrack ExtTrackMetadata) { + if req == nil { + return + } + + // The queued track describes the release selected by the user. Enrichment + // may fill missing identity fields, but must not rename it to another + // release's display title or artist spelling. + overlayStr(&req.TrackName, enrichedTrack.Name, "TrackName") + overlayStr(&req.ArtistName, enrichedTrack.Artists, "ArtistName") +} + func resolvePreferredTrackIDForExtension(ext *loadedExtension, req DownloadRequest, explicitTrackID string) string { candidates := make([]string, 0, 8) appendCandidate := func(value string) { diff --git a/go_backend/extension_fallback_metadata_test.go b/go_backend/extension_fallback_metadata_test.go index 7b978c25..a238f1ba 100644 --- a/go_backend/extension_fallback_metadata_test.go +++ b/go_backend/extension_fallback_metadata_test.go @@ -55,3 +55,51 @@ func TestOverlayExtensionReleaseMetadataDoesNotEraseExistingValues(t *testing.T) t.Fatalf("existing release metadata changed: %#v", req) } } + +func TestBuildSourceExtensionTrackMetadataCarriesKnownIdentifiers(t *testing.T) { + req := DownloadRequest{ + SpotifyID: "source-id", + TidalID: "alternate-id-a", + QobuzID: "alternate-id-b", + DeezerID: "alternate-id-c", + } + + track := buildSourceExtensionTrackMetadata(req) + if track.ID != req.SpotifyID || track.SpotifyID != req.SpotifyID { + t.Fatalf("primary identifier was not propagated: %#v", track) + } + if track.TidalID != req.TidalID || track.QobuzID != req.QobuzID || track.DeezerID != req.DeezerID { + t.Fatalf("alternate identifiers were not propagated: %#v", track) + } +} + +func TestOverlaySourceExtensionTrackIdentityPreservesRequestedValues(t *testing.T) { + req := DownloadRequest{ + TrackName: "Album Display Title", + ArtistName: "Album Artist Credit", + } + + overlaySourceExtensionTrackIdentity(&req, ExtTrackMetadata{ + Name: "Single Display Title", + Artists: "Provider Artist Credit", + }) + + if req.TrackName != "Album Display Title" { + t.Fatalf("track name = %q, want requested title", req.TrackName) + } + if req.ArtistName != "Album Artist Credit" { + t.Fatalf("artist name = %q, want requested credit", req.ArtistName) + } +} + +func TestOverlaySourceExtensionTrackIdentityFillsMissingValues(t *testing.T) { + req := DownloadRequest{} + overlaySourceExtensionTrackIdentity(&req, ExtTrackMetadata{ + Name: "Resolved Title", + Artists: "Resolved Artist", + }) + + if req.TrackName != "Resolved Title" || req.ArtistName != "Resolved Artist" { + t.Fatalf("missing identity fields were not enriched: %#v", req) + } +}