mirror of
https://github.com/navidrome/navidrome.git
synced 2026-11-15 14:48:41 +01:00
feat(artwork): advertise album art for tracks whose embedded cover is the album's
Most albums embed the same picture in every track, so clients requesting per-track cover art (mf- ids) download and cache the same image once per song. This change hashes the embedded picture at scan time (XXH3 of the bytes, the same digest the artwork pipeline uses) and stores it on media_file.embed_art_hash. ToAlbum stores the hash of the track it already picks as the album's embedded cover (EmbedArtPath) on album.embed_art_hash. A new MediaFile.HasOwnCoverArt replaces the scattered "HasCoverArt && EnableMediaFileCoverArt" checks: a track whose hash equals its album's now returns the disc/album artwork id from CoverArtID, so the Subsonic, Jellyfin and native responses point every matching song at one cached image. Serving a stale mf- URL follows the same rule, so what is advertised and what is served agree. Tracks with a different embedded picture (compilations, per-track art) keep their own ids. Rows scanned before this change have an empty hash and keep the previous behavior until a full scan; no config option is added. The album hash is hydrated onto tracks with one batched PK lookup per page, only when the page has hashed tracks. BestImageIndex is exported from core/artwork so the scanner hashes the same picture the resolver serves.
This commit is contained in:
parent
1ceb25c6c1
commit
af984bc817
21 changed files with 280 additions and 82 deletions
|
|
@ -65,6 +65,7 @@ func (md Metadata) ToMediaFile(libID int, folderID string) model.MediaFile {
|
|||
|
||||
// General properties
|
||||
mf.HasCoverArt = md.HasPicture()
|
||||
mf.EmbedArtHash = md.PictureHash()
|
||||
mf.Duration = md.Length()
|
||||
mf.BitRate = md.AudioProperties().BitRate
|
||||
mf.SampleRate = md.AudioProperties().SampleRate
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ type Info struct {
|
|||
Tags model.RawTags
|
||||
AudioProperties AudioProperties
|
||||
HasPicture bool
|
||||
PictureHash string
|
||||
}
|
||||
|
||||
type FileInfo interface {
|
||||
|
|
@ -69,20 +70,22 @@ func NewPair(key, value string) string {
|
|||
|
||||
func New(filePath string, info Info) Metadata {
|
||||
return Metadata{
|
||||
filePath: filePath,
|
||||
fileInfo: info.FileInfo,
|
||||
tags: clean(filePath, info.Tags),
|
||||
audioProps: info.AudioProperties,
|
||||
hasPicture: info.HasPicture,
|
||||
filePath: filePath,
|
||||
fileInfo: info.FileInfo,
|
||||
tags: clean(filePath, info.Tags),
|
||||
audioProps: info.AudioProperties,
|
||||
hasPicture: info.HasPicture,
|
||||
pictureHash: info.PictureHash,
|
||||
}
|
||||
}
|
||||
|
||||
type Metadata struct {
|
||||
filePath string
|
||||
fileInfo FileInfo
|
||||
tags model.Tags
|
||||
audioProps AudioProperties
|
||||
hasPicture bool
|
||||
filePath string
|
||||
fileInfo FileInfo
|
||||
tags model.Tags
|
||||
audioProps AudioProperties
|
||||
hasPicture bool
|
||||
pictureHash string
|
||||
}
|
||||
|
||||
func (md Metadata) FilePath() string { return md.filePath }
|
||||
|
|
@ -95,6 +98,7 @@ func (md Metadata) Suffix() string {
|
|||
func (md Metadata) AudioProperties() AudioProperties { return md.audioProps }
|
||||
func (md Metadata) Length() float32 { return float32(md.audioProps.Duration.Milliseconds()) / 1000 }
|
||||
func (md Metadata) HasPicture() bool { return md.hasPicture }
|
||||
func (md Metadata) PictureHash() string { return md.pictureHash }
|
||||
func (md Metadata) All() model.Tags { return md.tags }
|
||||
func (md Metadata) Strings(key model.TagName) []string { return md.tags[key] }
|
||||
func (md Metadata) String(key model.TagName) string { return md.first(key) }
|
||||
|
|
|
|||
|
|
@ -36,8 +36,9 @@ var _ = Describe("Metadata", func() {
|
|||
Duration: time.Minute * 3,
|
||||
BitRate: 320,
|
||||
},
|
||||
HasPicture: true,
|
||||
FileInfo: testFileInfo{fileInfo},
|
||||
HasPicture: true,
|
||||
PictureHash: "0123456789abcdef",
|
||||
FileInfo: testFileInfo{fileInfo},
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -60,6 +61,7 @@ var _ = Describe("Metadata", func() {
|
|||
Expect(md.AudioProperties()).To(Equal(props.AudioProperties))
|
||||
Expect(md.Length()).To(Equal(float32(3 * 60)))
|
||||
Expect(md.HasPicture()).To(Equal(props.HasPicture))
|
||||
Expect(md.PictureHash()).To(Equal(props.PictureHash))
|
||||
Expect(md.Strings(model.TagTrackArtist)).To(Equal([]string{"First Artist", "Second Artist"}))
|
||||
Expect(md.String(model.TagTrackArtist)).To(Equal("First Artist"))
|
||||
Expect(md.Int(model.TagCatalogNumber)).To(Equal(int64(1234)))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue