mirror of
https://github.com/navidrome/navidrome.git
synced 2026-08-01 07:21:17 +00:00
feat(artwork): hydrate and expose thumbHash on every entity
ItemArtworkInfo.Image() is the single projection every hydration branch goes through, so adding the field there covers albums, artists, playlists and both the own-art and inherited media-file branches.
This commit is contained in:
parent
313e93c3d9
commit
82b9538bff
@ -40,10 +40,9 @@ func BenchmarkEncodeAtInputSize(b *testing.B) {
|
||||
})
|
||||
}
|
||||
}
|
||||
x, y := blurhash.Components(size, size)
|
||||
b.ReportAllocs()
|
||||
for range b.N {
|
||||
if _, err := blurhash.Encode(img, x, y); err != nil {
|
||||
if _, err := blurhash.Encode(img); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -21,6 +21,7 @@ type ItemImage struct {
|
||||
ImageHash string `structs:"-" json:"imageHash,omitempty"`
|
||||
ImageAbsent bool `structs:"-" json:"imageAbsent,omitempty"`
|
||||
BlurHash string `structs:"-" json:"blurHash,omitempty"`
|
||||
ThumbHash string `structs:"-" json:"thumbHash,omitempty"`
|
||||
// A blurhash carries no aspect ratio, so clients need these to shape the placeholder.
|
||||
ImageWidth int `structs:"-" json:"imageWidth,omitempty"`
|
||||
ImageHeight int `structs:"-" json:"imageHeight,omitempty"`
|
||||
@ -52,11 +53,12 @@ type ItemArtwork struct {
|
||||
|
||||
// ItemArtworkInfo is the list-hydration projection (item_artwork joined with artwork).
|
||||
type ItemArtworkInfo struct {
|
||||
ItemID string
|
||||
Hash string
|
||||
BlurHash string
|
||||
Width int
|
||||
Height int
|
||||
ItemID string
|
||||
Hash string
|
||||
BlurHash string
|
||||
ThumbHash string
|
||||
Width int
|
||||
Height int
|
||||
}
|
||||
|
||||
// Absent reports a known-absent artwork state (resolved, no image).
|
||||
@ -68,6 +70,7 @@ func (i ItemArtworkInfo) Image() ItemImage {
|
||||
ImageHash: i.Hash,
|
||||
ImageAbsent: i.Absent(),
|
||||
BlurHash: i.BlurHash,
|
||||
ThumbHash: i.ThumbHash,
|
||||
ImageWidth: i.Width,
|
||||
ImageHeight: i.Height,
|
||||
}
|
||||
|
||||
@ -23,6 +23,23 @@ var _ = Describe("ItemImage JSON", func() {
|
||||
Expect(out).To(HaveKeyWithValue("blurHash", "LEHV6nWB2yk8"))
|
||||
})
|
||||
|
||||
It("exposes the thumbhash, and omits it when the entity has none", func() {
|
||||
al := model.Album{ID: "al-4", Name: "Album"}
|
||||
al.ThumbHash = "1QcSHQRn"
|
||||
|
||||
var out map[string]any
|
||||
data, err := json.Marshal(al)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(json.Unmarshal(data, &out)).To(Succeed())
|
||||
Expect(out).To(HaveKeyWithValue("thumbHash", "1QcSHQRn"))
|
||||
|
||||
var bare map[string]any
|
||||
data, err = json.Marshal(model.Album{ID: "al-5", Name: "Album"})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(json.Unmarshal(data, &bare)).To(Succeed())
|
||||
Expect(bare).ToNot(HaveKey("thumbHash"))
|
||||
})
|
||||
|
||||
// Without the dimensions, clients cannot know the placeholder's shape and default to a square.
|
||||
It("exposes the image dimensions alongside the blurhash", func() {
|
||||
al := model.Album{ID: "al-3", Name: "Album"}
|
||||
|
||||
@ -289,12 +289,12 @@ var _ = Describe("Artwork hydration", func() {
|
||||
Expect(byID["2002"].AlbumImage.ImageAbsent).To(BeTrue())
|
||||
})
|
||||
|
||||
It("carries the blurhash and its dimensions alongside the hash in both the own-art and inherited branches", func() {
|
||||
It("carries both hashes and the dimensions alongside the hash in 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", Width: 640, Height: 480})).To(Succeed())
|
||||
Expect(aw.PutImage(&model.Artwork{Hash: "alh102blurxxxxxx", Mime: "image/jpeg", BlurHash: "LALBUMblur", Width: 1200, Height: 800})).To(Succeed())
|
||||
Expect(aw.PutImage(&model.Artwork{Hash: "mfh1001blurxxxxx", Mime: "image/jpeg", BlurHash: "LTRACKblur", ThumbHash: "THtrack", Width: 640, Height: 480})).To(Succeed())
|
||||
Expect(aw.PutImage(&model.Artwork{Hash: "alh102blurxxxxxx", Mime: "image/jpeg", BlurHash: "LALBUMblur", ThumbHash: "THalbum", Width: 1200, Height: 800})).To(Succeed())
|
||||
putInfo("mf", "1001", "mfh1001blurxxxxx")
|
||||
putInfo("al", "102", "alh102blurxxxxxx") // 1002's album: single-disc inheritance branch
|
||||
|
||||
@ -302,11 +302,13 @@ var _ = Describe("Artwork hydration", func() {
|
||||
|
||||
Expect(byID["1001"].ImageHash).To(Equal("mfh1001blurxxxxx"))
|
||||
Expect(byID["1001"].BlurHash).To(Equal("LTRACKblur"))
|
||||
Expect(byID["1001"].ThumbHash).To(Equal("THtrack"))
|
||||
Expect(byID["1001"].ImageWidth).To(Equal(640))
|
||||
Expect(byID["1001"].ImageHeight).To(Equal(480))
|
||||
|
||||
Expect(byID["1002"].ImageHash).To(Equal("alh102blurxxxxxx"))
|
||||
Expect(byID["1002"].BlurHash).To(Equal("LALBUMblur"))
|
||||
Expect(byID["1002"].ThumbHash).To(Equal("THalbum"))
|
||||
Expect(byID["1002"].ImageWidth).To(Equal(1200))
|
||||
Expect(byID["1002"].ImageHeight).To(Equal(800))
|
||||
})
|
||||
@ -739,13 +741,14 @@ var _ = Describe("Artwork hydration", func() {
|
||||
Describe("applyItemImage", func() {
|
||||
It("copies hash, absence, blurhash and dimensions onto the item", func() {
|
||||
infos := map[string]model.ItemArtworkInfo{
|
||||
"al-1": {ItemID: "al-1", Hash: "0123456789abcdef", BlurHash: "LEHV6nWB2yk8", Width: 1200, Height: 800},
|
||||
"al-1": {ItemID: "al-1", Hash: "0123456789abcdef", BlurHash: "LEHV6nWB2yk8", ThumbHash: "1QcSHQRn", Width: 1200, Height: 800},
|
||||
}
|
||||
var img model.ItemImage
|
||||
applyItemImage(infos, "al-1", &img)
|
||||
Expect(img.ImageHash).To(Equal("0123456789abcdef"))
|
||||
Expect(img.ImageAbsent).To(BeFalse())
|
||||
Expect(img.BlurHash).To(Equal("LEHV6nWB2yk8"))
|
||||
Expect(img.ThumbHash).To(Equal("1QcSHQRn"))
|
||||
Expect(img.ImageWidth).To(Equal(1200))
|
||||
Expect(img.ImageHeight).To(Equal(800))
|
||||
})
|
||||
@ -756,6 +759,7 @@ var _ = Describe("Artwork hydration", func() {
|
||||
applyItemImage(infos, "al-2", &img)
|
||||
Expect(img.ImageAbsent).To(BeTrue())
|
||||
Expect(img.BlurHash).To(BeEmpty())
|
||||
Expect(img.ThumbHash).To(BeEmpty())
|
||||
})
|
||||
|
||||
It("leaves an unresolved item zero-valued", func() {
|
||||
|
||||
@ -191,6 +191,7 @@ func (r *artworkRepository) GetInfoForItems(kind model.Kind, ids []string) (map[
|
||||
res := map[string]model.ItemArtworkInfo{}
|
||||
for chunk := range slices.Chunk(ids, artworkBatchSize) {
|
||||
sel := Select("ia.item_id", "ia.hash", "COALESCE(a.blur_hash, '') as blur_hash",
|
||||
"COALESCE(a.thumb_hash, '') as thumb_hash",
|
||||
"COALESCE(a.width, 0) as width", "COALESCE(a.height, 0) as height").
|
||||
From(itemArtworkTable + " ia").
|
||||
LeftJoin("artwork a ON a.hash = ia.hash").
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user