mirror of
https://github.com/navidrome/navidrome.git
synced 2026-08-01 07:21:17 +00:00
feat(jellyfin): emit a song's own cover art when it differs from the album's
Real Jellyfin fills ImageTags from each item's own images before falling back to the parent album, and Finamp checks imageTags.Primary before AlbumId. Our mapper read only the album's image, so a track with distinct embedded art silently showed the album cover. Emit exactly one entry under ImageBlurHashes.Primary: Go marshals map[string]string in sorted key order rather than insertion order, so a second entry could pair the wrong blurhash with the image imageId resolves to, and Finamp pins that pairing in its cache for 365 days.
This commit is contained in:
parent
8545cc4762
commit
77e6f7fdc3
@ -286,6 +286,24 @@ var _ = Describe("Artwork hydration", func() {
|
||||
Expect(byID["2002"].AlbumImage.ImageAbsent).To(BeTrue())
|
||||
})
|
||||
|
||||
It("carries the blurhash alongside the hash in both the own-art and inherited branches", func() {
|
||||
setCover("1001", true) // eligible, resolves its own art -> own-art-wins branch
|
||||
DeferCleanup(func() { setCover("1001", false) })
|
||||
|
||||
Expect(aw.PutImage(&model.Artwork{Hash: "mfh1001blurxxxxx", Mime: "image/jpeg", BlurHash: "LTRACKblur"})).To(Succeed())
|
||||
Expect(aw.PutImage(&model.Artwork{Hash: "alh102blurxxxxxx", Mime: "image/jpeg", BlurHash: "LALBUMblur"})).To(Succeed())
|
||||
putInfo("mf", "1001", "mfh1001blurxxxxx")
|
||||
putInfo("al", "102", "alh102blurxxxxxx") // 1002's album: single-disc inheritance branch
|
||||
|
||||
byID := getByID()
|
||||
|
||||
Expect(byID["1001"].ImageHash).To(Equal("mfh1001blurxxxxx"))
|
||||
Expect(byID["1001"].BlurHash).To(Equal("LTRACKblur"))
|
||||
|
||||
Expect(byID["1002"].ImageHash).To(Equal("alh102blurxxxxxx"))
|
||||
Expect(byID["1002"].BlurHash).To(Equal("LALBUMblur"))
|
||||
})
|
||||
|
||||
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)
|
||||
|
||||
@ -246,6 +246,7 @@ func (r *mediaFileRepository) hydrateArtwork(mfs model.MediaFiles) {
|
||||
ownInfo, ownResolved := mfInfos[mf.ID]
|
||||
if eligible && ownResolved && !ownInfo.Absent() {
|
||||
mf.ImageHash = ownInfo.Hash // own resolved art wins
|
||||
mf.BlurHash = ownInfo.BlurHash
|
||||
continue
|
||||
}
|
||||
// Fallback (see MediaFile.CoverArtID): inherit a found album hash for optimistic caching,
|
||||
@ -255,6 +256,7 @@ func (r *mediaFileRepository) hydrateArtwork(mfs model.MediaFiles) {
|
||||
if album, ok := albumInfos[mf.AlbumID]; ok && !album.Absent() {
|
||||
if mf.DiscNumber == 0 {
|
||||
mf.ImageHash = album.Hash
|
||||
mf.BlurHash = album.BlurHash
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
@ -192,9 +192,14 @@ func SongToBaseItem(mf model.MediaFile, fields Fields) BaseItemDto {
|
||||
} else if mf.Genre != "" {
|
||||
item.Genres = []string{mf.Genre}
|
||||
}
|
||||
// Finamp resolves song art via AlbumId + a non-empty AlbumPrimaryImageTag, so this tag
|
||||
// must version the ALBUM's image, not the track's own art.
|
||||
if mf.AlbumID != "" {
|
||||
// A track's own cover wins in Finamp's precedence (ImageTags.Primary before AlbumId); emit only
|
||||
// the matching blurhash, since Go sorts map keys and a second entry could pair the wrong one.
|
||||
if mf.ImageHash != "" && mf.ImageHash != mf.AlbumImage.ImageHash {
|
||||
item.ImageTags = map[string]string{"Primary": mf.ImageHash}
|
||||
if mf.BlurHash != "" {
|
||||
item.ImageBlurHashes = map[string]map[string]string{"Primary": {mf.ImageHash: mf.BlurHash}}
|
||||
}
|
||||
} else if mf.AlbumID != "" {
|
||||
if tag, blurs := primaryImageTag(mf.AlbumImage, mf.AlbumID); tag != "" {
|
||||
item.AlbumPrimaryImageTag = tag
|
||||
item.ImageBlurHashes = blurs
|
||||
|
||||
@ -471,6 +471,47 @@ var _ = Describe("mappers", func() {
|
||||
Expect(item.ImageTags).To(HaveKeyWithValue("Primary", "abcdef0123456789"))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("per-song artwork", func() {
|
||||
It("emits the track's own Primary tag when it has distinct art", func() {
|
||||
mf := model.MediaFile{ID: "song-own", Title: "Song", AlbumID: "alb-1"}
|
||||
mf.ImageHash = "aaaaaaaaaaaaaaaa"
|
||||
mf.BlurHash = "LTRACKblur"
|
||||
mf.AlbumImage.ImageHash = "bbbbbbbbbbbbbbbb"
|
||||
mf.AlbumImage.BlurHash = "LALBUMblur"
|
||||
|
||||
item := SongToBaseItem(mf, nil)
|
||||
Expect(item.ImageTags).To(HaveKeyWithValue("Primary", "aaaaaaaaaaaaaaaa"))
|
||||
Expect(item.ImageBlurHashes["Primary"]).To(HaveLen(1),
|
||||
"exactly one Primary entry: Go sorts map keys, so a second entry could pair the wrong blurhash with imageId")
|
||||
Expect(item.ImageBlurHashes["Primary"]).To(HaveKeyWithValue("aaaaaaaaaaaaaaaa", "LTRACKblur"))
|
||||
})
|
||||
|
||||
It("falls back to the album tag when the track has no distinct art", func() {
|
||||
mf := model.MediaFile{ID: "song-inherit", Title: "Song", AlbumID: "alb-1"}
|
||||
mf.ImageHash = "bbbbbbbbbbbbbbbb"
|
||||
mf.BlurHash = "LALBUMblur"
|
||||
mf.AlbumImage.ImageHash = "bbbbbbbbbbbbbbbb"
|
||||
mf.AlbumImage.BlurHash = "LALBUMblur"
|
||||
|
||||
item := SongToBaseItem(mf, nil)
|
||||
Expect(item.ImageTags).To(BeEmpty(), "an inherited cover is the album's image, not the track's")
|
||||
Expect(item.AlbumPrimaryImageTag).To(Equal("bbbbbbbbbbbbbbbb"))
|
||||
Expect(item.ImageBlurHashes["Primary"]).To(HaveLen(1))
|
||||
Expect(item.ImageBlurHashes["Primary"]).To(HaveKeyWithValue("bbbbbbbbbbbbbbbb", "LALBUMblur"))
|
||||
})
|
||||
|
||||
It("omits the track tag when its own art is known absent", func() {
|
||||
mf := model.MediaFile{ID: "song-absent", Title: "Song", AlbumID: "alb-1"}
|
||||
mf.ImageAbsent = true
|
||||
mf.AlbumImage.ImageAbsent = true
|
||||
|
||||
item := SongToBaseItem(mf, nil)
|
||||
Expect(item.ImageTags).To(BeEmpty())
|
||||
Expect(item.AlbumPrimaryImageTag).To(BeEmpty())
|
||||
Expect(item.ImageBlurHashes).To(BeNil())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
var _ = Describe("LyricDtoFromLyrics", func() {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user