From f9aaff7d7f2f6342aaa8296f806c4cf6badb51b1 Mon Sep 17 00:00:00 2001 From: Deluan Date: Thu, 23 Jul 2026 20:01:04 -0400 Subject: [PATCH] feat(artwork): hydrate the parent album's artwork state onto tracks --- model/mediafile.go | 4 ++++ persistence/mediafile_repository.go | 1 + persistence/mediafile_repository_test.go | 25 ++++++++++++++++++++++++ 3 files changed, 30 insertions(+) diff --git a/model/mediafile.go b/model/mediafile.go index ac0a7f655..1e2dff782 100644 --- a/model/mediafile.go +++ b/model/mediafile.go @@ -26,6 +26,10 @@ type MediaFile struct { Bookmarkable `structs:"-" hash:"ignore"` ItemImage `structs:"-" hash:"ignore"` + // AlbumImage is the parent album's artwork state, hydrated alongside the track's own so a + // song's Jellyfin album-art tag can be pixel-versioned without a second query. + AlbumImage ItemImage `structs:"-" json:"-" hash:"ignore"` + ID string `structs:"id" json:"id" hash:"ignore"` PID string `structs:"pid" json:"-" hash:"ignore"` LibraryID int `structs:"library_id" json:"libraryId" hash:"ignore"` diff --git a/persistence/mediafile_repository.go b/persistence/mediafile_repository.go index 4cc93f4dd..c90a28566 100644 --- a/persistence/mediafile_repository.go +++ b/persistence/mediafile_repository.go @@ -241,6 +241,7 @@ func (r *mediaFileRepository) hydrateArtwork(mfs model.MediaFiles) { mfInfos := hydrateItemImages(r.ctx, r.db, model.KindMediaFileArtwork.Prefix(), eligibleIDs) for i := range mfs { mf := &mfs[i] + applyItemImage(albumInfos, mf.AlbumID, &mf.AlbumImage) eligible := mf.HasCoverArt && conf.Server.EnableMediaFileCoverArt ownInfo, ownResolved := mfInfos[mf.ID] if eligible && ownResolved && !ownInfo.Absent() { diff --git a/persistence/mediafile_repository_test.go b/persistence/mediafile_repository_test.go index f6a744d8d..6e892c8f5 100644 --- a/persistence/mediafile_repository_test.go +++ b/persistence/mediafile_repository_test.go @@ -1093,4 +1093,29 @@ var _ = Describe("MediaRepository", func() { _ = mr.Delete(newID) }) }) + + Describe("AlbumImage hydration", func() { + It("carries the parent album's artwork state onto each track", func() { + mfs := model.MediaFiles{{ID: "mf-1", AlbumID: "al-1"}} + infos := map[string]model.ItemArtworkInfo{ + "al-1": {ItemID: "al-1", Hash: "0123456789abcdef", BlurHash: "LEHV6nWB2yk8"}, + } + applyItemImage(infos, mfs[0].AlbumID, &mfs[0].AlbumImage) + + Expect(mfs[0].AlbumImage.ImageHash).To(Equal("0123456789abcdef")) + Expect(mfs[0].AlbumImage.BlurHash).To(Equal("LEHV6nWB2yk8")) + Expect(mfs[0].AlbumImage.ImageAbsent).To(BeFalse()) + }) + + It("keeps the album state independent of the track's own art", func() { + mf := model.MediaFile{ID: "mf-2", AlbumID: "al-2"} + mf.ImageHash = "aaaaaaaaaaaaaaaa" // the track's own art + applyItemImage( + map[string]model.ItemArtworkInfo{"al-2": {ItemID: "al-2", Hash: "bbbbbbbbbbbbbbbb"}}, + mf.AlbumID, &mf.AlbumImage, + ) + Expect(mf.ImageHash).To(Equal("aaaaaaaaaaaaaaaa")) + Expect(mf.AlbumImage.ImageHash).To(Equal("bbbbbbbbbbbbbbbb")) + }) + }) })