Compare commits

...

6 Commits

Author SHA1 Message Date
Deluan
b7af074cb8 fix(Makefile): append EXTRA_BUILD_TAGS to GO_BUILD_TAGS
Signed-off-by: Deluan <deluan@navidrome.org>
2026-04-04 15:06:39 -04:00
Deluan Quintão
18aec5793c
Merge branch 'master' into rename-enable-webp-encoding 2026-04-04 12:55:44 -04:00
Deluan
39de90b080 fix(configuration): update DefaultUICoverArtSize to 300
Signed-off-by: Deluan <deluan@navidrome.org>
2026-04-04 12:50:54 -04:00
Deluan
75d0c839c3 fix: default EnableWebPEncoding to false and reduce artwork parallelism
Changed EnableWebPEncoding default to false so that upgrading users get
the same JPEG/PNG encoding behavior as v0.60.3 out of the box, avoiding
the WebP WASM overhead until native libwebp is available. Users can
opt in to WebP by setting EnableWebPEncoding=true. Also reduced the
default DevArtworkMaxRequests to half the CPU count (min 2) to lower
resource pressure during artwork processing.
2026-04-04 12:37:26 -04:00
Deluan
a31ce7ffa3 fix(artwork): preserve album cache key compatibility with v0.60.3
Restored the v0.60.3 hash input order for album artwork cache keys
(Agents + CoverArtPriority) so that existing caches remain valid on
upgrade when EnableExternalServices is true. Also ensures
CoverArtPriority is always part of the hash even when external services
are disabled, fixing a v0.60.3 bug where changing CoverArtPriority had
no effect on cache invalidation.

Signed-off-by: Deluan <deluan@navidrome.org>
2026-04-04 12:33:09 -04:00
Deluan
80c1e60259 feat(playlists): add sampleRate, codec, and missing fields for smart playlists
Closes #5302
2026-04-04 10:37:28 -04:00
6 changed files with 45 additions and 13 deletions

View File

@ -1,6 +1,8 @@
GO_VERSION=$(shell grep "^go " go.mod | cut -f 2 -d ' ') GO_VERSION=$(shell grep "^go " go.mod | cut -f 2 -d ' ')
NODE_VERSION=$(shell cat .nvmrc) NODE_VERSION=$(shell cat .nvmrc)
GO_BUILD_TAGS=netgo,sqlite_fts5
comma:=,
GO_BUILD_TAGS=netgo,sqlite_fts5$(if $(EXTRA_BUILD_TAGS),$(comma)$(EXTRA_BUILD_TAGS))
# Set global environment variables, required for most targets # Set global environment variables, required for most targets
export CGO_CFLAGS_ALLOW=--define-prefix export CGO_CFLAGS_ALLOW=--define-prefix

View File

@ -724,7 +724,7 @@ func setViperDefaults() {
viper.SetDefault("mpvcmdtemplate", "mpv --audio-device=%d --no-audio-display %f --input-ipc-server=%s") viper.SetDefault("mpvcmdtemplate", "mpv --audio-device=%d --no-audio-display %f --input-ipc-server=%s")
viper.SetDefault("coverartpriority", "cover.*, folder.*, front.*, embedded, external") viper.SetDefault("coverartpriority", "cover.*, folder.*, front.*, embedded, external")
viper.SetDefault("coverartquality", 75) viper.SetDefault("coverartquality", 75)
viper.SetDefault("enablewebpencoding", true) viper.SetDefault("enablewebpencoding", false)
viper.SetDefault("artistartpriority", "artist.*, album/artist.*, external") viper.SetDefault("artistartpriority", "artist.*, album/artist.*, external")
viper.SetDefault("artistimagefolder", "") viper.SetDefault("artistimagefolder", "")
viper.SetDefault("discartpriority", "disc*.*, cd*.*, cover.*, folder.*, front.*, discsubtitle, embedded") viper.SetDefault("discartpriority", "disc*.*, cd*.*, cover.*, folder.*, front.*, discsubtitle, embedded")
@ -820,7 +820,7 @@ func setViperDefaults() {
viper.SetDefault("devuishowconfig", true) viper.SetDefault("devuishowconfig", true)
viper.SetDefault("devneweventstream", true) viper.SetDefault("devneweventstream", true)
viper.SetDefault("devoffsetoptimize", 50000) viper.SetDefault("devoffsetoptimize", 50000)
viper.SetDefault("devartworkmaxrequests", max(4, runtime.NumCPU())) viper.SetDefault("devartworkmaxrequests", max(2, runtime.NumCPU()/2))
viper.SetDefault("devartworkthrottlebackloglimit", consts.RequestThrottleBacklogLimit) viper.SetDefault("devartworkthrottlebackloglimit", consts.RequestThrottleBacklogLimit)
viper.SetDefault("devartworkthrottlebacklogtimeout", consts.RequestThrottleBacklogTimeout) viper.SetDefault("devartworkthrottlebacklogtimeout", consts.RequestThrottleBacklogTimeout)
viper.SetDefault("devartistinfotimetolive", consts.ArtistInfoTimeToLive) viper.SetDefault("devartistinfotimetolive", consts.ArtistInfoTimeToLive)

View File

@ -85,7 +85,7 @@ const (
) )
const ( const (
DefaultUICoverArtSize = 600 DefaultUICoverArtSize = 300
) )
// Prometheus options // Prometheus options

View File

@ -380,24 +380,24 @@ var _ = Describe("Artwork", func() {
}) })
}) })
When("Square is false", func() { When("Square is false", func() {
It("returns WebP even if original image is a PNG", func() { It("returns PNG if original image is a PNG", func() {
conf.Server.CoverArtPriority = "front.png" conf.Server.CoverArtPriority = "front.png"
r, _, err := aw.Get(context.Background(), alMultipleCovers.CoverArtID(), 15, false) r, _, err := aw.Get(context.Background(), alMultipleCovers.CoverArtID(), 15, false)
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
img, format, err := image.Decode(r) img, format, err := image.Decode(r)
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
Expect(format).To(Equal("webp")) Expect(format).To(Equal("png"))
Expect(img.Bounds().Size().X).To(Equal(15)) Expect(img.Bounds().Size().X).To(Equal(15))
Expect(img.Bounds().Size().Y).To(Equal(15)) Expect(img.Bounds().Size().Y).To(Equal(15))
}) })
It("returns WebP if original image is not a PNG", func() { It("returns JPEG if original image is not a PNG", func() {
conf.Server.CoverArtPriority = "cover.jpg" conf.Server.CoverArtPriority = "cover.jpg"
r, _, err := aw.Get(context.Background(), alMultipleCovers.CoverArtID(), 200, false) r, _, err := aw.Get(context.Background(), alMultipleCovers.CoverArtID(), 200, false)
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
img, format, err := image.Decode(r) img, format, err := image.Decode(r)
Expect(format).To(Equal("webp")) Expect(format).To(Equal("jpeg"))
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
Expect(img.Bounds().Size().X).To(Equal(200)) Expect(img.Bounds().Size().X).To(Equal(200))
Expect(img.Bounds().Size().Y).To(Equal(200)) Expect(img.Bounds().Size().Y).To(Equal(200))
@ -430,12 +430,39 @@ var _ = Describe("Artwork", func() {
Expect(img.Bounds().Size().X).To(Equal(size)) Expect(img.Bounds().Size().X).To(Equal(size))
Expect(img.Bounds().Size().Y).To(Equal(size)) Expect(img.Bounds().Size().Y).To(Equal(size))
}, },
Entry("portrait png image", "png", "webp", false, 200), Entry("portrait png image", "png", "png", false, 200),
Entry("landscape png image", "png", "webp", true, 200), Entry("landscape png image", "png", "png", true, 200),
Entry("portrait jpg image", "jpg", "webp", false, 200), Entry("portrait jpg image", "jpg", "png", false, 200),
Entry("landscape jpg image", "jpg", "webp", true, 200), Entry("landscape jpg image", "jpg", "png", true, 200),
) )
}) })
When("EnableWebPEncoding is true and square is false", func() {
BeforeEach(func() {
conf.Server.EnableWebPEncoding = true
})
It("returns WebP even if original image is a PNG", func() {
conf.Server.CoverArtPriority = "front.png"
r, _, err := aw.Get(context.Background(), alMultipleCovers.CoverArtID(), 15, false)
Expect(err).ToNot(HaveOccurred())
img, format, err := image.Decode(r)
Expect(err).ToNot(HaveOccurred())
Expect(format).To(Equal("webp"))
Expect(img.Bounds().Size().X).To(Equal(15))
Expect(img.Bounds().Size().Y).To(Equal(15))
})
It("returns WebP if original image is not a PNG", func() {
conf.Server.CoverArtPriority = "cover.jpg"
r, _, err := aw.Get(context.Background(), alMultipleCovers.CoverArtID(), 200, false)
Expect(err).ToNot(HaveOccurred())
img, format, err := image.Decode(r)
Expect(format).To(Equal("webp"))
Expect(err).ToNot(HaveOccurred())
Expect(img.Bounds().Size().X).To(Equal(200))
Expect(img.Bounds().Size().Y).To(Equal(200))
})
})
When("EnableWebPEncoding is false and square is false", func() { When("EnableWebPEncoding is false and square is false", func() {
BeforeEach(func() { BeforeEach(func() {
conf.Server.EnableWebPEncoding = false conf.Server.EnableWebPEncoding = false

View File

@ -61,7 +61,7 @@ func newAlbumArtworkReader(ctx context.Context, artwork *artwork, artID model.Ar
func (a *albumArtworkReader) Key() string { func (a *albumArtworkReader) Key() string {
hashInput := conf.Server.CoverArtPriority hashInput := conf.Server.CoverArtPriority
if conf.Server.EnableExternalServices { if conf.Server.EnableExternalServices {
hashInput += conf.Server.Agents hashInput = conf.Server.Agents + hashInput
} }
hash := md5.Sum([]byte(hashInput)) hash := md5.Sum([]byte(hashInput))
return fmt.Sprintf( return fmt.Sprintf(

View File

@ -35,6 +35,7 @@ var fieldMap = map[string]*mappedField{
"releasedate": {field: "media_file.release_date"}, "releasedate": {field: "media_file.release_date"},
"size": {field: "media_file.size"}, "size": {field: "media_file.size"},
"compilation": {field: "media_file.compilation"}, "compilation": {field: "media_file.compilation"},
"missing": {field: "media_file.missing"},
"explicitstatus": {field: "media_file.explicit_status"}, "explicitstatus": {field: "media_file.explicit_status"},
"dateadded": {field: "media_file.created_at"}, "dateadded": {field: "media_file.created_at"},
"datemodified": {field: "media_file.updated_at"}, "datemodified": {field: "media_file.updated_at"},
@ -49,9 +50,11 @@ var fieldMap = map[string]*mappedField{
"catalognumber": {field: "media_file.catalog_num"}, "catalognumber": {field: "media_file.catalog_num"},
"filepath": {field: "media_file.path"}, "filepath": {field: "media_file.path"},
"filetype": {field: "media_file.suffix"}, "filetype": {field: "media_file.suffix"},
"codec": {field: "media_file.codec"},
"duration": {field: "media_file.duration"}, "duration": {field: "media_file.duration"},
"bitrate": {field: "media_file.bit_rate"}, "bitrate": {field: "media_file.bit_rate"},
"bitdepth": {field: "media_file.bit_depth"}, "bitdepth": {field: "media_file.bit_depth"},
"samplerate": {field: "media_file.sample_rate"},
"bpm": {field: "media_file.bpm"}, "bpm": {field: "media_file.bpm"},
"channels": {field: "media_file.channels"}, "channels": {field: "media_file.channels"},
"loved": {field: "COALESCE(annotation.starred, false)"}, "loved": {field: "COALESCE(annotation.starred, false)"},