From 0781c4a9b2dad51a243ed800e7e5c970998e8b6d Mon Sep 17 00:00:00 2001 From: Deluan Date: Thu, 23 Jul 2026 00:26:47 -0400 Subject: [PATCH] fix(artwork): keep an eligible track's cover requestable when its album is absent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An embedded-eligible track with no resolved item_artwork row inherited the album's ImageAbsent, so when the album resolved absent (e.g. CoverArtPriority without 'embedded') the track's coverArt was omitted permanently — the client never requested it, so the lazy mediafile path never resolved it — even though the serving path would extract and serve the track's own embedded art. Hydration now never copies the album's absence onto an eligible-but-unresolved track. --- persistence/artwork_hydration_test.go | 26 ++++++++++++++++++++++++++ persistence/mediafile_repository.go | 16 +++++++++++++--- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/persistence/artwork_hydration_test.go b/persistence/artwork_hydration_test.go index 6001221cb..5dab6f2de 100644 --- a/persistence/artwork_hydration_test.go +++ b/persistence/artwork_hydration_test.go @@ -233,6 +233,32 @@ var _ = Describe("Artwork hydration", func() { Expect(byID["2002"].ImageAbsent).To(BeFalse()) }) + It("keeps an eligible file optimistic when its own art is unresolved, even if the album is absent", func() { + // 1004 is eligible (has embedded cover) with no mf state row yet; its album (103) is absent. + setCover("1004", true) + DeferCleanup(func() { setCover("1004", false) }) + putInfo("al", "103", "") // album known-absent + + byID := getByID() + + // The track's own embedded art is still unresolved, so it must NOT inherit the album's + // absence: coverArt stays requestable so serving can extract the embedded art. + Expect(byID["1004"].ImageAbsent).To(BeFalse()) + Expect(byID["1004"].ImageHash).To(BeEmpty()) + // A non-eligible sibling on the same absent album still inherits the absence. + Expect(byID["1003"].ImageAbsent).To(BeTrue()) + }) + + It("uses the album hash for an eligible file whose own art is unresolved but album is found", func() { + setCover("1004", true) + DeferCleanup(func() { setCover("1004", false) }) + putInfo("al", "103", "alh103found11111") + + byID := getByID() + Expect(byID["1004"].ImageAbsent).To(BeFalse()) + Expect(byID["1004"].ImageHash).To(Equal("alh103found11111")) + }) + It("uses album info for an eligible file when EnableMediaFileCoverArt is off", func() { conf.Server.EnableMediaFileCoverArt = false setCover("1001", true) diff --git a/persistence/mediafile_repository.go b/persistence/mediafile_repository.go index 9ba9d3825..72c1907d5 100644 --- a/persistence/mediafile_repository.go +++ b/persistence/mediafile_repository.go @@ -241,11 +241,21 @@ func (r *mediaFileRepository) hydrateArtwork(mfs model.MediaFiles) { mfInfos := hydrateItemImages(r.ctx, r.db, model.KindMediaFileArtwork.Prefix(), eligibleIDs) for i := range mfs { if mfs[i].HasCoverArt && conf.Server.EnableMediaFileCoverArt { - if info, ok := mfInfos[mfs[i].ID]; ok && !info.Absent() { - mfs[i].ImageHash = info.Hash - mfs[i].ImageAbsent = false + if info, ok := mfInfos[mfs[i].ID]; ok { + if !info.Absent() { + mfs[i].ImageHash = info.Hash + continue + } + // Own art resolved absent → serving delegates to the album, so mirror that. + applyItemImage(albumInfos, mfs[i].AlbumID, &mfs[i].ItemImage) continue } + // Eligible but unresolved: take a found album hash for caching, but never the + // album's absence — serving still extracts the track's own embedded art on request. + if albumInfo, ok := albumInfos[mfs[i].AlbumID]; ok && !albumInfo.Absent() { + mfs[i].ImageHash = albumInfo.Hash + } + continue } applyItemImage(albumInfos, mfs[i].AlbumID, &mfs[i].ItemImage) }