From c87db92cee70fa5aa78e781b9a2ef93c46000d20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Deluan=20Quint=C3=A3o?= Date: Sat, 4 Apr 2026 15:17:01 -0400 Subject: [PATCH] fix(artwork): address WebP performance regression on low-power hardware (#5286) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor(artwork): rename DevJpegCoverArt to EnableWebPEncoding Replaced the internal DevJpegCoverArt flag with a user-facing EnableWebPEncoding config option (defaults to true). When disabled, the fallback encoding now preserves the original image format — PNG sources stay PNG for non-square resizes, matching v0.60.3 behavior. The previous implementation incorrectly re-encoded PNG sources as JPEG in non-square mode. Also added EnableWebPEncoding to the insights data. * feat: add configurable UICoverArtSize option Converted the hardcoded UICoverArtSize constant (600px) into a configurable option, allowing users to reduce the cover art size requested by the UI to mitigate slow image encoding. The value is served to the frontend via the app config and used by all components that request cover art. Also simplified the cache warmer by removing a single-iteration loop in favor of direct code. * style: fix prettier formatting in subsonic test * feat: log WebP encoder/decoder selection Signed-off-by: Deluan * fix(artwork): address PR review feedback - Add DevJpegCoverArt to logRemovedOptions so users with the old config key get a clear warning instead of a silent ignore. - Include EnableWebPEncoding in the resized artwork cache key to prevent stale WebP responses after toggling the setting. - Skip animated GIF to WebP conversion via ffmpeg when EnableWebPEncoding is false, so the setting is consistent across all image types. - Fix data race in cache warmer by reading UICoverArtSize at construction time instead of per-image, avoiding concurrent access with config cleanup in tests. - Clarify cache warmer docstring to accurately describe caching behavior. * Revert "fix(artwork): address PR review feedback" This reverts commit 3a213ef03e401930977138afe0e84c83290df683. * fix(artwork): avoid data race in cache warmer config access Capture UICoverArtSize at construction time instead of reading from conf.Server on each doCacheImage call. The background goroutine could race with test config cleanup, causing intermittent race detector failures in CI. * fix(configuration): clamp UICoverArtSize to be within 200 and 1200 Signed-off-by: Deluan * 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 * 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. * fix(configuration): update DefaultUICoverArtSize to 300 Signed-off-by: Deluan * fix(Makefile): append EXTRA_BUILD_TAGS to GO_BUILD_TAGS Signed-off-by: Deluan --------- Signed-off-by: Deluan --- Makefile | 4 +- conf/configuration.go | 15 +++++-- consts/consts.go | 4 +- core/artwork/artwork_internal_test.go | 55 +++++++++++++++++++------- core/artwork/cache_warmer.go | 39 +++++++++--------- core/artwork/cache_warmer_test.go | 3 +- core/artwork/reader_album.go | 2 +- core/artwork/reader_resized.go | 24 +++++++---- core/metrics/insights.go | 2 + core/metrics/insights/data.go | 2 + server/public/handle_shares.go | 3 +- server/serve_index.go | 1 + server/serve_index_test.go | 1 + ui/src/album/AlbumDetails.jsx | 5 +-- ui/src/album/AlbumGridView.jsx | 5 ++- ui/src/artist/DesktopArtistDetails.jsx | 3 +- ui/src/artist/MobileArtistDetails.jsx | 3 +- ui/src/common/CoverArtAvatar.jsx | 4 +- ui/src/config.js | 1 + ui/src/consts.js | 2 - ui/src/playlist/PlaylistDetails.jsx | 4 +- ui/src/radio/RadioEdit.jsx | 5 ++- ui/src/radio/helper.jsx | 5 ++- ui/src/subsonic/index.test.js | 28 ++++++++++--- 24 files changed, 142 insertions(+), 78 deletions(-) diff --git a/Makefile b/Makefile index 3bad5b620..0673838c2 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,8 @@ GO_VERSION=$(shell grep "^go " go.mod | cut -f 2 -d ' ') 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 export CGO_CFLAGS_ALLOW=--define-prefix diff --git a/conf/configuration.go b/conf/configuration.go index fce5e0b2f..58239884a 100644 --- a/conf/configuration.go +++ b/conf/configuration.go @@ -70,6 +70,7 @@ type configOptions struct { MPVCmdTemplate string CoverArtPriority string CoverArtQuality int + EnableWebPEncoding bool ArtistArtPriority string ArtistImageFolder string DiscArtPriority string @@ -87,6 +88,7 @@ type configOptions struct { DefaultLanguage string DefaultUIVolume int UISearchDebounceMs int + UICoverArtSize int EnableReplayGain bool EnableCoverAnimation bool EnableNowPlaying bool @@ -141,7 +143,6 @@ type configOptions struct { DevOptimizeDB bool DevPreserveUnicodeInExternalCalls bool DevEnableMediaFileProbe bool - DevJpegCoverArt bool } type scannerOptions struct { @@ -424,6 +425,13 @@ func Load(noConfigDump bool) { // Removed options logRemovedOptions("Spotify.ID", "Spotify.Secret") + // Validate other options + if Server.UICoverArtSize < 200 || Server.UICoverArtSize > 1200 { + newValue := max(200, min(1200, Server.UICoverArtSize)) + log.Warn("UICoverArtSize must be between 200 and 1200, clamping", "value", Server.UICoverArtSize, "newValue", newValue) + Server.UICoverArtSize = newValue + } + // Call init hooks for _, hook := range hooks { hook() @@ -716,6 +724,7 @@ func setViperDefaults() { 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("coverartquality", 75) + viper.SetDefault("enablewebpencoding", false) viper.SetDefault("artistartpriority", "artist.*, album/artist.*, external") viper.SetDefault("artistimagefolder", "") viper.SetDefault("discartpriority", "disc*.*, cd*.*, cover.*, folder.*, front.*, discsubtitle, embedded") @@ -728,6 +737,7 @@ func setViperDefaults() { viper.SetDefault("defaultlanguage", "") viper.SetDefault("defaultuivolume", consts.DefaultUIVolume) viper.SetDefault("uisearchdebouncems", consts.DefaultUISearchDebounceMs) + viper.SetDefault("uicoverartsize", consts.DefaultUICoverArtSize) viper.SetDefault("enablereplaygain", true) viper.SetDefault("enablecoveranimation", true) viper.SetDefault("enablenowplaying", true) @@ -810,7 +820,7 @@ func setViperDefaults() { viper.SetDefault("devuishowconfig", true) viper.SetDefault("devneweventstream", true) 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("devartworkthrottlebacklogtimeout", consts.RequestThrottleBacklogTimeout) viper.SetDefault("devartistinfotimetolive", consts.ArtistInfoTimeToLive) @@ -826,7 +836,6 @@ func setViperDefaults() { viper.SetDefault("devoptimizedb", true) viper.SetDefault("devpreserveunicodeinexternalcalls", false) viper.SetDefault("devenablemediafileprobe", true) - viper.SetDefault("devjpegcoverart", false) } func init() { diff --git a/consts/consts.go b/consts/consts.go index f1010a872..ff5dedc2b 100644 --- a/consts/consts.go +++ b/consts/consts.go @@ -85,11 +85,9 @@ const ( ) const ( - UICoverArtSize = 600 + DefaultUICoverArtSize = 300 ) -var CacheWarmerImageSizes = []int{UICoverArtSize} - // Prometheus options const ( PrometheusDefaultPath = "/metrics" diff --git a/core/artwork/artwork_internal_test.go b/core/artwork/artwork_internal_test.go index 4b2359898..380352d3f 100644 --- a/core/artwork/artwork_internal_test.go +++ b/core/artwork/artwork_internal_test.go @@ -380,24 +380,24 @@ var _ = Describe("Artwork", 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" 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(format).To(Equal("png")) 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() { + It("returns JPEG 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(format).To(Equal("jpeg")) Expect(err).ToNot(HaveOccurred()) Expect(img.Bounds().Size().X).To(Equal(200)) Expect(img.Bounds().Size().Y).To(Equal(200)) @@ -430,24 +430,51 @@ var _ = Describe("Artwork", func() { Expect(img.Bounds().Size().X).To(Equal(size)) Expect(img.Bounds().Size().Y).To(Equal(size)) }, - Entry("portrait png image", "png", "webp", false, 200), - Entry("landscape png image", "png", "webp", true, 200), - Entry("portrait jpg image", "jpg", "webp", false, 200), - Entry("landscape jpg image", "jpg", "webp", true, 200), + Entry("portrait png image", "png", "png", false, 200), + Entry("landscape png image", "png", "png", true, 200), + Entry("portrait jpg image", "jpg", "png", false, 200), + Entry("landscape jpg image", "jpg", "png", true, 200), ) }) - When("DevJpegCoverArt is true and square is false", func() { + When("EnableWebPEncoding is true and square is false", func() { BeforeEach(func() { - conf.Server.DevJpegCoverArt = true + conf.Server.EnableWebPEncoding = true }) - It("returns JPEG even if original image is a PNG", func() { + 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("jpeg")) + 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() { + BeforeEach(func() { + conf.Server.EnableWebPEncoding = false + }) + It("returns PNG 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("png")) Expect(img.Bounds().Size().X).To(Equal(15)) Expect(img.Bounds().Size().Y).To(Equal(15)) }) @@ -463,11 +490,11 @@ var _ = Describe("Artwork", func() { Expect(img.Bounds().Size().Y).To(Equal(200)) }) }) - When("DevJpegCoverArt is true and square is true", func() { + When("EnableWebPEncoding is false and square is true", func() { var alCover model.Album BeforeEach(func() { - conf.Server.DevJpegCoverArt = true + conf.Server.EnableWebPEncoding = false }) It("returns PNG for square mode", func() { dirName := createImage("png", false, 200) diff --git a/core/artwork/cache_warmer.go b/core/artwork/cache_warmer.go index bd1359b74..5090d638e 100644 --- a/core/artwork/cache_warmer.go +++ b/core/artwork/cache_warmer.go @@ -10,7 +10,6 @@ import ( "time" "github.com/navidrome/navidrome/conf" - "github.com/navidrome/navidrome/consts" "github.com/navidrome/navidrome/log" "github.com/navidrome/navidrome/model" "github.com/navidrome/navidrome/model/request" @@ -24,7 +23,7 @@ type CacheWarmer interface { // NewCacheWarmer creates a new CacheWarmer instance. The CacheWarmer will pre-cache Artwork images in the background // to speed up the response time when the image is requested by the UI. The cache is pre-populated with the original -// image size, as well as the size defined in the UICoverArtSize constant. +// image size, as well as the size defined by the UICoverArtSize config option. func NewCacheWarmer(artwork Artwork, cache cache.FileCache) CacheWarmer { // If image cache is disabled, return a NOOP implementation if conf.Server.ImageCacheSize == "0" || !conf.Server.EnableArtworkPrecache { @@ -38,10 +37,11 @@ func NewCacheWarmer(artwork Artwork, cache cache.FileCache) CacheWarmer { } a := &cacheWarmer{ - artwork: artwork, - cache: cache, - buffer: make(map[model.ArtworkID]struct{}), - wakeSignal: make(chan struct{}, 1), + artwork: artwork, + cache: cache, + buffer: make(map[model.ArtworkID]struct{}), + wakeSignal: make(chan struct{}, 1), + coverArtSize: conf.Server.UICoverArtSize, } // Create a context with a fake admin user, to be able to pre-cache Playlist CoverArts @@ -51,11 +51,12 @@ func NewCacheWarmer(artwork Artwork, cache cache.FileCache) CacheWarmer { } type cacheWarmer struct { - artwork Artwork - buffer map[model.ArtworkID]struct{} - mutex sync.Mutex - cache cache.FileCache - wakeSignal chan struct{} + artwork Artwork + buffer map[model.ArtworkID]struct{} + mutex sync.Mutex + cache cache.FileCache + wakeSignal chan struct{} + coverArtSize int } func (a *cacheWarmer) PreCache(artID model.ArtworkID) { @@ -142,16 +143,14 @@ func (a *cacheWarmer) doCacheImage(ctx context.Context, id model.ArtworkID) erro ctx, cancel := context.WithTimeout(ctx, 10*time.Second) defer cancel() - for _, size := range consts.CacheWarmerImageSizes { - r, _, err := a.artwork.Get(ctx, id, size, true) - if err != nil { - return fmt.Errorf("caching id='%s', size=%d: %w", id, size, err) - } - _, err = io.Copy(io.Discard, r) - r.Close() - return err + size := a.coverArtSize + r, _, err := a.artwork.Get(ctx, id, size, true) + if err != nil { + return fmt.Errorf("caching id='%s', size=%d: %w", id, size, err) } - return nil + _, err = io.Copy(io.Discard, r) + r.Close() + return err } func NoopCacheWarmer() CacheWarmer { diff --git a/core/artwork/cache_warmer_test.go b/core/artwork/cache_warmer_test.go index 9798ea8d6..a5da2004c 100644 --- a/core/artwork/cache_warmer_test.go +++ b/core/artwork/cache_warmer_test.go @@ -12,7 +12,6 @@ import ( "github.com/navidrome/navidrome/conf" "github.com/navidrome/navidrome/conf/configtest" - "github.com/navidrome/navidrome/consts" "github.com/navidrome/navidrome/model" "github.com/navidrome/navidrome/utils/cache" . "github.com/onsi/ginkgo/v2" @@ -182,7 +181,7 @@ var _ = Describe("CacheWarmer", func() { Eventually(func() []int { return aw.getCachedSizes() - }).Should(ContainElements(consts.UICoverArtSize)) + }).Should(ContainElements(conf.Server.UICoverArtSize)) }) }) }) diff --git a/core/artwork/reader_album.go b/core/artwork/reader_album.go index 6de1d31d1..641b12b33 100644 --- a/core/artwork/reader_album.go +++ b/core/artwork/reader_album.go @@ -61,7 +61,7 @@ func newAlbumArtworkReader(ctx context.Context, artwork *artwork, artID model.Ar func (a *albumArtworkReader) Key() string { hashInput := conf.Server.CoverArtPriority if conf.Server.EnableExternalServices { - hashInput += conf.Server.Agents + hashInput = conf.Server.Agents + hashInput } hash := md5.Sum([]byte(hashInput)) return fmt.Sprintf( diff --git a/core/artwork/reader_resized.go b/core/artwork/reader_resized.go index 88ca8b83b..85a19a4c3 100644 --- a/core/artwork/reader_resized.go +++ b/core/artwork/reader_resized.go @@ -19,6 +19,16 @@ import ( xdraw "golang.org/x/image/draw" ) +func init() { + conf.AddHook(func() { + if err := webp.Dynamic(); err != nil { + log.Debug("Using WASM WebP encoder/decoder", "reason", err) + } else { + log.Debug("Using native libwebp for WebP encoding/decoding") + } + }) +} + var bufPool = sync.Pool{ New: func() any { return new(bytes.Buffer) @@ -117,7 +127,7 @@ func (a *resizedArtworkReader) resizeImage(ctx context.Context, reader io.Reader } func resizeStaticImage(data []byte, size int, square bool) (io.Reader, int, error) { - original, _, err := image.Decode(bytes.NewReader(data)) + original, format, err := image.Decode(bytes.NewReader(data)) if err != nil { return nil, 0, err } @@ -157,14 +167,12 @@ func resizeStaticImage(data []byte, size int, square bool) (io.Reader, int, erro buf := bufPool.Get().(*bytes.Buffer) buf.Reset() - if conf.Server.DevJpegCoverArt { - if square { - err = png.Encode(buf, dst) - } else { - err = jpeg.Encode(buf, dst, &jpeg.Options{Quality: conf.Server.CoverArtQuality}) - } - } else { + if conf.Server.EnableWebPEncoding { err = webp.Encode(buf, dst, webp.Options{Quality: conf.Server.CoverArtQuality}) + } else if format == "png" || square { + err = png.Encode(buf, dst) + } else { + err = jpeg.Encode(buf, dst, &jpeg.Options{Quality: conf.Server.CoverArtQuality}) } if err != nil { bufPool.Put(buf) diff --git a/core/metrics/insights.go b/core/metrics/insights.go index b87f1df5e..f069d3fb6 100644 --- a/core/metrics/insights.go +++ b/core/metrics/insights.go @@ -195,6 +195,8 @@ var staticData = sync.OnceValue(func() insights.Data { data.Config.EnableArtworkPrecache = conf.Server.EnableArtworkPrecache data.Config.EnableArtworkUpload = conf.Server.EnableArtworkUpload data.Config.CoverArtQuality = conf.Server.CoverArtQuality + data.Config.EnableWebPEncoding = conf.Server.EnableWebPEncoding + data.Config.UICoverArtSize = conf.Server.UICoverArtSize data.Config.EnableCoverAnimation = conf.Server.EnableCoverAnimation data.Config.EnableNowPlaying = conf.Server.EnableNowPlaying data.Config.EnableDownloads = conf.Server.EnableDownloads diff --git a/core/metrics/insights/data.go b/core/metrics/insights/data.go index b316c866d..34648a49b 100644 --- a/core/metrics/insights/data.go +++ b/core/metrics/insights/data.go @@ -65,6 +65,8 @@ type Data struct { EnablePrometheus bool `json:"enablePrometheus,omitempty"` EnableArtworkUpload bool `json:"enableArtworkUpload,omitempty"` CoverArtQuality int `json:"coverArtQuality,omitempty"` + EnableWebPEncoding bool `json:"enableWebPEncoding,omitempty"` + UICoverArtSize int `json:"uiCoverArtSize,omitempty"` EnableCoverAnimation bool `json:"enableCoverAnimation,omitempty"` EnableNowPlaying bool `json:"enableNowPlaying,omitempty"` SessionTimeout uint64 `json:"sessionTimeout,omitempty"` diff --git a/server/public/handle_shares.go b/server/public/handle_shares.go index 15e63d4db..24ecff1d6 100644 --- a/server/public/handle_shares.go +++ b/server/public/handle_shares.go @@ -6,6 +6,7 @@ import ( "net/http" "path" + "github.com/navidrome/navidrome/conf" "github.com/navidrome/navidrome/consts" "github.com/navidrome/navidrome/core/auth" "github.com/navidrome/navidrome/core/publicurl" @@ -81,7 +82,7 @@ func checkShareError(ctx context.Context, w http.ResponseWriter, err error, id s func (pub *Router) mapShareInfo(r *http.Request, s model.Share) *model.Share { s.URL = ShareURL(r, s.ID) - s.ImageURL = publicurl.ImageURL(r, s.CoverArtID(), consts.UICoverArtSize) + s.ImageURL = publicurl.ImageURL(r, s.CoverArtID(), conf.Server.UICoverArtSize) for i := range s.Tracks { s.Tracks[i].ID = encodeMediafileShare(s, s.Tracks[i].ID) } diff --git a/server/serve_index.go b/server/serve_index.go index 0d1a2f330..bd5be44f5 100644 --- a/server/serve_index.go +++ b/server/serve_index.go @@ -55,6 +55,7 @@ func serveIndex(ds model.DataStore, fs fs.FS, shareInfo *model.Share) http.Handl "defaultLanguage": conf.Server.DefaultLanguage, "defaultUIVolume": conf.Server.DefaultUIVolume, "uiSearchDebounceMs": conf.Server.UISearchDebounceMs, + "uiCoverArtSize": conf.Server.UICoverArtSize, "enableCoverAnimation": conf.Server.EnableCoverAnimation, "enableNowPlaying": conf.Server.EnableNowPlaying, "gaTrackingId": conf.Server.GATrackingID, diff --git a/server/serve_index_test.go b/server/serve_index_test.go index e08a42643..7515e7276 100644 --- a/server/serve_index_test.go +++ b/server/serve_index_test.go @@ -86,6 +86,7 @@ var _ = Describe("serveIndex", func() { Entry("defaultLanguage", func() { conf.Server.DefaultLanguage = "pt" }, "defaultLanguage", "pt"), Entry("defaultUIVolume", func() { conf.Server.DefaultUIVolume = 45 }, "defaultUIVolume", float64(45)), Entry("uiSearchDebounceMs", func() { conf.Server.UISearchDebounceMs = 500 }, "uiSearchDebounceMs", float64(500)), + Entry("uiCoverArtSize", func() { conf.Server.UICoverArtSize = 300 }, "uiCoverArtSize", float64(300)), Entry("enableCoverAnimation", func() { conf.Server.EnableCoverAnimation = true }, "enableCoverAnimation", true), Entry("enableNowPlaying", func() { conf.Server.EnableNowPlaying = true }, "enableNowPlaying", true), Entry("gaTrackingId", func() { conf.Server.GATrackingID = "UA-12345" }, "gaTrackingId", "UA-12345"), diff --git a/ui/src/album/AlbumDetails.jsx b/ui/src/album/AlbumDetails.jsx index 2411b8611..cec66eb8b 100644 --- a/ui/src/album/AlbumDetails.jsx +++ b/ui/src/album/AlbumDetails.jsx @@ -18,7 +18,7 @@ import { useTranslate, } from 'react-admin' import Lightbox from 'react-image-lightbox' -import { COVER_ART_SIZE } from '../consts' +import config from '../config' import 'react-image-lightbox/style.css' import subsonic from '../subsonic' import { @@ -32,7 +32,6 @@ import { useAlbumsPerPage, useImageLoadingState, } from '../common' -import config from '../config' import { formatFullDate, intersperse } from '../utils' import AlbumExternalLinks from './AlbumExternalLinks' import { SafeHTML } from '../common/SafeHTML' @@ -255,7 +254,7 @@ const AlbumDetails = (props) => { }) }, [record]) - const imageUrl = subsonic.getCoverArtUrl(record, COVER_ART_SIZE) + const imageUrl = subsonic.getCoverArtUrl(record, config.uiCoverArtSize) const fullImageUrl = subsonic.getCoverArtUrl(record) return ( diff --git a/ui/src/album/AlbumGridView.jsx b/ui/src/album/AlbumGridView.jsx index c8a161571..9717618fa 100644 --- a/ui/src/album/AlbumGridView.jsx +++ b/ui/src/album/AlbumGridView.jsx @@ -20,7 +20,8 @@ import { OverflowTooltip, useImageUrl, } from '../common' -import { COVER_ART_SIZE, DraggableTypes } from '../consts' +import config from '../config' +import { DraggableTypes } from '../consts' import clsx from 'clsx' import { AlbumDatesField } from './AlbumDatesField.jsx' @@ -135,7 +136,7 @@ const Cover = withContentRect('bounds')(({ [record], ) - const url = subsonic.getCoverArtUrl(record, COVER_ART_SIZE, true) + const url = subsonic.getCoverArtUrl(record, config.uiCoverArtSize, true) const { imgUrl, loading: imageLoading } = useImageUrl(url) return ( diff --git a/ui/src/artist/DesktopArtistDetails.jsx b/ui/src/artist/DesktopArtistDetails.jsx index bc2312477..dda761097 100644 --- a/ui/src/artist/DesktopArtistDetails.jsx +++ b/ui/src/artist/DesktopArtistDetails.jsx @@ -15,7 +15,6 @@ import { import Lightbox from 'react-image-lightbox' import ExpandInfoDialog from '../dialogs/ExpandInfoDialog' import AlbumInfo from '../album/AlbumInfo' -import { COVER_ART_SIZE } from '../consts' import subsonic from '../subsonic' import { SafeHTML } from '../common/SafeHTML' @@ -110,7 +109,7 @@ const DesktopArtistDetails = ({ artistInfo, record, biography }) => { { { handleCloseLightbox, } = useImageLoadingState(record.id) - const imageUrl = subsonic.getCoverArtUrl(record, COVER_ART_SIZE, true) + const imageUrl = subsonic.getCoverArtUrl(record, config.uiCoverArtSize, true) const fullImageUrl = subsonic.getCoverArtUrl(record) return ( diff --git a/ui/src/radio/RadioEdit.jsx b/ui/src/radio/RadioEdit.jsx index 5f804535a..bbe001e6f 100644 --- a/ui/src/radio/RadioEdit.jsx +++ b/ui/src/radio/RadioEdit.jsx @@ -11,7 +11,8 @@ import { makeStyles } from '@material-ui/core/styles' import { urlValidate } from '../utils/validations' import { Title, ImageUploadOverlay, useImageLoadingState } from '../common' import subsonic from '../subsonic' -import { COVER_ART_SIZE, RADIO_PLACEHOLDER_IMAGE } from '../consts' +import config from '../config' +import { RADIO_PLACEHOLDER_IMAGE } from '../consts' const useStyles = makeStyles({ coverParent: { @@ -83,7 +84,7 @@ const RadioCoverArt = ({ record }) => { {record.uploadedImage ? ( { @@ -31,7 +31,11 @@ describe('getCoverArtUrl', () => { updatedAt: '2023-01-01T00:00:00Z', } - const url = subsonic.getCoverArtUrl(playlistRecord, COVER_ART_SIZE, true) + const url = subsonic.getCoverArtUrl( + playlistRecord, + config.uiCoverArtSize, + true, + ) expect(url).toContain('pl-playlist-123') expect(url).toContain('size=600') @@ -45,7 +49,11 @@ describe('getCoverArtUrl', () => { sync: true, } - const url = subsonic.getCoverArtUrl(playlistRecord, COVER_ART_SIZE, true) + const url = subsonic.getCoverArtUrl( + playlistRecord, + config.uiCoverArtSize, + true, + ) expect(url).toContain('pl-playlist-123') expect(url).toContain('size=600') @@ -60,7 +68,11 @@ describe('getCoverArtUrl', () => { updatedAt: '2023-01-01T00:00:00Z', } - const url = subsonic.getCoverArtUrl(albumRecord, COVER_ART_SIZE, true) + const url = subsonic.getCoverArtUrl( + albumRecord, + config.uiCoverArtSize, + true, + ) expect(url).toContain('al-album-123') expect(url).toContain('size=600') @@ -74,7 +86,7 @@ describe('getCoverArtUrl', () => { updatedAt: '2023-01-01T00:00:00Z', } - const url = subsonic.getCoverArtUrl(songRecord, COVER_ART_SIZE, true) + const url = subsonic.getCoverArtUrl(songRecord, config.uiCoverArtSize, true) expect(url).toContain('mf-song-123') expect(url).toContain('size=600') @@ -87,7 +99,11 @@ describe('getCoverArtUrl', () => { updatedAt: '2023-01-01T00:00:00Z', } - const url = subsonic.getCoverArtUrl(artistRecord, COVER_ART_SIZE, true) + const url = subsonic.getCoverArtUrl( + artistRecord, + config.uiCoverArtSize, + true, + ) expect(url).toContain('ar-artist-123') expect(url).toContain('size=600')