4698 Commits

Author SHA1 Message Date
Patrik Wallström
d3bd3107e8
Merge branch 'master' into subsonic-folder 2026-03-16 23:52:54 +01:00
Deluan Quintão
2f5b2b5135
fix(artwork): fallback mediafile cover art to disc artwork before album (#5216)
* fix(artwork): fallback mediafile cover art to disc artwork before album

Changed the mediafile cover art fallback chain to go through disc artwork
before album artwork (mediafile → disc → album). Previously, mediafiles
without embedded art fell back directly to album cover, bypassing any
disc-specific artwork. Renamed AlbumCoverArtID() to DiscCoverArtID() to
encapsulate the disc-vs-album decision in a single method, used by both
CoverArtID() and the mediafile artwork reader.

Signed-off-by: Deluan <deluan@navidrome.org>

* fix(artwork): fix cache invalidation for mediafile and album cover art

Include imagesUpdatedAt from album folders in the mediafile artwork
reader's cache key, so that when a cover image file changes on disk
(without audio metadata changes) the mediafile cache properly
invalidates. Also include CoverArtPriority unconditionally in the album
artwork reader's cache key hash, so that changing the priority order
with external services disabled correctly invalidates the album cache.

* fix(artwork): skip disc artwork resolution for single-disc albums

Single-disc albums with DiscNumber=1 were unnecessarily routed through
discArtworkReader, which does extra DB queries only to fall through to
album art anyway. Now only multi-disc albums use the disc fallback path.

* refactor(artwork): restore AlbumCoverArtID as a separate method

Extract AlbumCoverArtID back out of DiscCoverArtID so the single-disc
fallback path in reader_mediafile can reference it by name instead of
inlining the artwork ID construction.

---------

Signed-off-by: Deluan <deluan@navidrome.org>
2026-03-16 18:08:39 -04:00
Deluan Quintão
e7c6e78dd0
fix(db): normalize timestamps and fix recently added album sorting (#5176)
* fix(db): normalize timestamps and fix recently added album sorting

SQLite stores timestamps as TEXT and uses string comparison for ORDER BY.
Timestamps in RFC3339 T-format ('2024-01-01T10:00:00Z') sort incorrectly
against space-format ('2024-01-01 10:00:00+00:00') because 'T' (ASCII 84)
> ' ' (ASCII 32), causing albums with T-format timestamps to appear as
newer than they are in the "Recently Added" list.

This adds a migration to normalize all T-format timestamps across all
tables to the space-format expected by go-sqlite3, wraps the
recently_added sort with datetime() to make it format-agnostic, and
replaces the plain album timestamp indexes with expression indexes to
maintain query performance.

* fix(test): improve recently_added sort test robustness

Use same-date timestamps (2024-01-15T08:00:00Z vs 2024-01-15 20:00:00)
so the T-vs-space character difference at position 10 actually triggers
the sorting bug. Initialize index variables to -1 and assert both test
albums are found before comparing positions.

* chore(db): update migration timestamp to 2026-03-16
2026-03-16 07:55:22 -04:00
Deluan
9ae9134a91 feat(ui): integrate CoverArtAvatar component into AlbumTableView
Signed-off-by: Deluan <deluan@navidrome.org>
2026-03-16 06:48:03 -04:00
Deluan
cefa6e9619 feat(ui): add CoverArtAvatar component and integrate it into artist and playlist lists
Signed-off-by: Deluan <deluan@navidrome.org>
2026-03-16 06:48:03 -04:00
Deluan Quintão
ab8a58157a
feat: add artist image uploads and image-folder artwork source (#5198)
* feat: add shared ImageUploadService for entity image management

* feat: add UploadedImage field and methods to Artist model

* feat: add uploaded_image column to artist table

* feat: add ArtistImageFolder config option

* refactor: wire ImageUploadService and delegate playlist file ops to it

Wire ImageUploadService into the DI container and refactor the playlist
service to delegate image file operations (SetImage/RemoveImage) to the
shared ImageUploadService, removing duplicated file I/O logic. A local
ImageUploadService interface is defined in core/playlists to avoid an
import cycle between core and core/playlists.

* feat: artist artwork reader checks uploaded image first

* feat: add image-folder priority source for artist artwork

* feat: cache key invalidation for image-folder and uploaded images

* refactor: extract shared image upload HTTP helpers

* feat: add artist image upload/delete API endpoints

* refactor: playlist handlers use shared image upload helpers

* feat: add shared ImageUploadOverlay component

* feat: add i18n keys for artist image upload

* feat: add image upload overlay to artist detail pages

* refactor: playlist details uses shared ImageUploadOverlay component

* fix: add gosec nolint directive for ParseMultipartForm

* refactor: deduplicate image upload code and optimize dir scanning

- Remove dead ImageFilename methods from Artist and Playlist models
  (production code uses core.imageFilename exclusively)
- Extract shared uploadedImagePath helper in model/image.go
- Extract findImageInArtistFolder to deduplicate dir-scanning logic
  between fromArtistImageFolder and getArtistImageFolderModTime
- Fix fileInputRef in useCallback dependency array

* fix: include artist UpdatedAt in artwork cache key

Without this, uploading or deleting an artist image would not
invalidate the cached artwork because the cache key was only based
on album folder timestamps, not the artist's own UpdatedAt field.

* feat: add Portuguese translations for artist image upload

* refactor: use shared i18n keys for cover art upload messages

Move cover art upload/remove translations from per-entity sections
(artist, playlist) to a shared top-level "message" section, avoiding
duplication across entity types and translation files.

* refactor: move cover art i18n keys to shared message section for all languages

* refactor: simplify image upload code and eliminate redundancies

Extracted duplicate image loading/lightbox state logic from
DesktopArtistDetails and MobileArtistDetails into a shared
useArtistImageState hook. Moved entity type constants to the consts
package and replaced raw string literals throughout model, core, and
nativeapi packages. Exported model.UploadedImagePath and reused it in
core/image_upload.go to consolidate path construction. Cached the
ArtistImageFolder lookup result in artistReader to eliminate a redundant
os.ReadDir call on every artwork request.

Signed-off-by: Deluan <deluan@navidrome.org>

* style: fix prettier formatting in ImageUploadOverlay

* fix: address code review feedback on image upload error handling

- RemoveImage now returns errors instead of swallowing them
- Artist handlers distinguish not-found from other DB errors
- Defer multipart temp file cleanup after parsing

* fix: enforce hard request size limit with MaxBytesReader for image uploads

Signed-off-by: Deluan <deluan@navidrome.org>

---------

Signed-off-by: Deluan <deluan@navidrome.org>
2026-03-15 22:19:55 -04:00
Deluan Quintão
be06196168
fix(ui): update Bulgarian, Catalan, Danish, German, Greek, Spanish, Finnish, French, Galician, Russian, Slovenian, Swedish, Thai, Chinese (traditional) translations from POEditor (#5044)
Co-authored-by: navidrome-bot <navidrome-bot@navidrome.org>
2026-03-15 20:44:59 -04:00
Patrik Wallström
61bb1a610c
Merge branch 'master' into subsonic-folder 2026-03-15 21:00:28 +01:00
Thiago Sfredo
36aea8a11f
feat(ui): add tooltips for long playlist and album names - 5068 (#5070)
* style(ui): add tooltips for long playlist and album names - 5068

Signed-off-by: Thiago Sfreddo <sfredo@gmail.com>

* fix dnd and improve performance

Signed-off-by: Thiago Sfreddo <sfredo@gmail.com>

* lint fixes

Signed-off-by: Thiago Sfreddo <sfredo@gmail.com>

* fix(ui): update tooltip styles for improved visibility and consistency

Signed-off-by: Deluan <deluan@navidrome.org>

* fix(ui): add overflow tooltip to playlist name for better visibility

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor(ui): simplify OverflowTooltip and improve render performance

- Inline styles from useMenuTooltipStyles into OverflowTooltip (single consumer)
- Use MUI named colors (grey[700]/grey[300] with alpha) instead of raw rgba
- Stabilize ref callback with useCallback to avoid unnecessary ref churn
- Memoize Tooltip classes and hoist TransitionProps to module level
- Fix useLayoutEffect dependency: observe DOM size, not title string

---------

Signed-off-by: Thiago Sfreddo <sfredo@gmail.com>
Signed-off-by: Deluan <deluan@navidrome.org>
Co-authored-by: Deluan Quintão <deluan@navidrome.org>
2026-03-15 14:55:55 -04:00
Tom Boucher
aa93911991
feat(server): add syslog priority prefixes for systemd-journald (#5192)
* fix: add syslog priority prefixes for systemd-journald

When running under systemd, all log messages were assigned priority 3
(error) by journald because navidrome wrote plain text to stderr without
syslog priority prefixes.

Add a journalFormatter that wraps the existing logrus formatter and
prepends <N> syslog priority prefixes (RFC 5424) to each log line.
The formatter is automatically enabled when the JOURNAL_STREAM
environment variable is set (indicating the process is managed by
systemd).

Priority mapping:
- Fatal/Panic → <2>/<0> (crit/emerg)
- Error → <3> (err)
- Warn → <4> (warning)
- Info → <6> (info)
- Debug/Trace → <7> (debug)

Fixes #5142

* test: refactor journalFormatter tests to use Ginkgo and DescribeTable

Signed-off-by: Deluan <deluan@navidrome.org>

---------

Signed-off-by: Deluan <deluan@navidrome.org>
Co-authored-by: Deluan <deluan@navidrome.org>
2026-03-15 14:14:05 -04:00
Tom Boucher
c42570446b
fix(ui): allow DefaultTheme "Auto" from config (#5190)
* fix(ui): allow DefaultTheme "Auto" from config

When DefaultTheme is set to "Auto" in the server config, the
defaultTheme() function in themeReducer now returns AUTO_THEME_ID
instead of falling through to the DarkTheme fallback.

This allows useCurrentTheme to correctly read prefers-color-scheme
and select Light or Dark theme automatically for new/incognito users.

Adds themeReducer unit tests covering Auto, named-theme, and
unrecognized-value fallback paths.

* chore: format

Signed-off-by: Deluan <deluan@navidrome.org>

---------

Signed-off-by: Deluan <deluan@navidrome.org>
Co-authored-by: Deluan <deluan@navidrome.org>
2026-03-15 14:00:21 -04:00
Deluan
a887521d7a fix(subsonic): always include mandatory title field in Child responses
Removed `omitempty` from the `Title` struct tag in the `Child` response
type. The Subsonic/OpenSubsonic API spec requires `title` to be a
mandatory field, but songs with empty titles caused the field to be
omitted entirely, crashing clients like Symfonium during sync.

Ref: https://support.symfonium.app/t/app-gets-stuck-on-syncing-large-database/13004/8
2026-03-15 13:36:26 -04:00
Deluan Quintão
69e7d163fc
remove built-in Spotify integration (#5197)
* refactor: remove built-in Spotify integration

Remove the Spotify adapter and all related configuration, replacing
the built-in integration with the plugin system. This deletes the
adapters/spotify package, removes Spotify config options (ID/Secret),
updates the default agents list from "deezer,lastfm,spotify" to
"deezer,lastfm", and cleans up all references across configuration,
metrics, logging, artwork caching, and documentation. Users with
Spotify config options will now see a warning that the options are
no longer available.

* feat: add ListenBrainz to list of default agents

Signed-off-by: Deluan <deluan@navidrome.org>

---------

Signed-off-by: Deluan <deluan@navidrome.org>
2026-03-15 13:18:54 -04:00
Patrik Wallström
467d05f7cc
Merge branch 'master' into subsonic-folder 2026-03-15 11:45:30 +01:00
Deluan Quintão
6b8fcc37c6
fix(share): add ownership checks to Delete and Update (#5189)
* test(share): add failing tests for Delete ownership checks

* fix(share): add ownership check to Delete

* test(share): add failing tests for Update ownership checks

* fix(share): add ownership check to Update

* refactor(share): extract checkOwnership helper with lightweight query

- Deduplicate ownership check from Delete and Update into a single helper
- Use a minimal single-column SELECT instead of Get (avoids loadMedia overhead)
- Use positive bypass form (IsAdmin || invalidUserId) matching codebase convention

* fix(share): convert model.ErrNotFound to rest.ErrNotFound in checkOwnership

Ensure consistent 404 responses when a nonexistent share ID is passed
to Delete or Update, by handling the conversion in checkOwnership
rather than relying on the subsequent write operation.
2026-03-15 00:12:58 -04:00
Patrik Wallström
453742d95e
Merge branch 'master' into subsonic-folder 2026-03-15 03:48:58 +01:00
Patrik Wallström
9275f6c542 Handle folder names starting with non-ASCII letters
Signed-off-by: Patrik Wallström <pawal@amplitut.de>
2026-03-15 03:24:29 +01:00
Patrik Wallström
fd61e7e73f Update getIndexes tests for folder-based browsing, add FolderBrowsing=false fallback test
Signed-off-by: Patrik Wallström <pawal@amplitut.de>
2026-03-15 03:11:37 +01:00
Patrik Wallström
cba0cfe569 Return empty ArtistInfo for folder IDs to avoid client errors (from i.e. DSub)
Signed-off-by: Patrik Wallström <pawal@amplitut.de>
2026-03-15 02:58:11 +01:00
Patrik Wallström
bc2cb3ab4d Added subsonic.folderbrowsing option, and removed debugging
Signed-off-by: Patrik Wallström <pawal@amplitut.de>
2026-03-15 02:58:11 +01:00
Patrik Wallström
501cfce841 Add Subsonic.FolderBrowsing config flag to gate getIndexes behavior
Signed-off-by: Patrik Wallström <pawal@amplitut.de>
2026-03-15 02:58:11 +01:00
Patrik Wallström
235632f3e5 Implement folder-based getIndexes per Subsonic spec
Signed-off-by: Patrik Wallström <pawal@amplitut.de>
2026-03-15 02:58:11 +01:00
Patrik Wallström
03f43c1f60 Handle folder IDs in getMusicDirectory per Subsonic spec
Signed-off-by: Patrik Wallström <pawal@amplitut.de>
2026-03-15 02:58:11 +01:00
Patrik Wallström
c2b2e42170 Add buildFolderDirectory for folder-based getMusicDirectory
Signed-off-by: Patrik Wallström <pawal@amplitut.de>
2026-03-15 02:58:11 +01:00
Patrik Wallström
b763ff7bab Add Folder lookup to GetEntityByID, add MockFolderRepo
Signed-off-by: Patrik Wallström <pawal@amplitut.de>
2026-03-15 02:58:11 +01:00
Patrik Wallström
0addb23bf5 Add folder artwork support and SongsByFolder filter
Signed-off-by: Patrik Wallström <pawal@amplitut.de>
2026-03-15 02:58:11 +01:00
Patrik Wallström
32200abeb9 Added childFromFolder helper
Signed-off-by: Patrik Wallström <pawal@amplitut.de>
2026-03-15 02:58:11 +01:00
Patrik Wallström
8de1f8bfff Added SongsByFolder filter
Signed-off-by: Patrik Wallström <pawal@amplitut.de>
2026-03-15 02:58:11 +01:00
Deluan
197d357f02 fix(ui): prevent mobile touch events from triggering playback after lightbox close
Signed-off-by: Deluan <deluan@navidrome.org>
2026-03-14 21:47:26 -04:00
Deluan
549b812633 fix(ui): prevent duplicate getCoverArt requests on artist page
useMediaQuery defaults to false on the first render (SSR compat),
causing MobileArtistDetails to briefly render on desktop. Its CSS
background-image triggered a full-size image fetch before the
component switched to DesktopArtistDetails, which fetched again.

Pass noSsr: true so the media query evaluates synchronously, and
cap the mobile background image to 800px.
2026-03-14 20:36:57 -04:00
Deluan
c63346de04 chore: run go mod tidy after dependency replacements 2026-03-14 10:23:45 -04:00
Deluan
ba3974ee59 refactor(shellquote): replace go-shellquote with custom shell quoting implementation 2026-03-14 10:23:45 -04:00
Deluan
8939f31d55 refactor(jsoncommentstrip): replace go-jsoncommentstrip with custom JSON comment stripping 2026-03-14 10:18:56 -04:00
Deluan
d79b812467 refactor(natural): replace maruel/natural with custom natural sort implementation 2026-03-14 10:18:56 -04:00
Deluan Quintão
55331b5fd9
fix(scanner): prevent duplicate tracks when multiple missing files match same target (#5183)
In processMissingTracks, matched tracks were not removed from the candidate
pool after being consumed by moveMatched. This allowed the same target track
to be paired with multiple missing tracks, creating duplicate non-missing
records with the same path. Track consumed matches in a usedMatched map so
each target is used at most once.

Fixes #5169
2026-03-14 00:07:21 -04:00
Deluan
d042fc138c refactor(nanoid): replace gonanoid with custom nanoid implementation for ID generation
Signed-off-by: Deluan <deluan@navidrome.org>
2026-03-13 21:06:26 -04:00
Deluan
55e10b9c77 fix(playlist): update smart playlist rules during metadata update
Signed-off-by: Deluan <deluan@navidrome.org>
2026-03-13 19:20:07 -04:00
Deluan Quintão
49a14d4583
feat(artwork): add per-disc cover art support (#5182)
* feat(artwork): add KindDiscArtwork and ParseDiscArtworkID

Add new disc artwork kind with 'dc' prefix for per-disc cover art
support. The composite ID format is albumID:discNumber, parsed by
the new ParseDiscArtworkID helper.

* feat(conf): add DiscArtPriority configuration option

Default: 'disc*.*, cd*.*, embedded'. Controls how per-disc cover
art is resolved, following the same pattern as CoverArtPriority
and ArtistArtPriority.

* feat(artwork): implement extractDiscNumber helper

Extracts disc number from filenames based on glob patterns by
parsing leading digits from the wildcard-matched portion.
Used for matching disc-specific artwork files like disc1.jpg.

* feat(artwork): implement fromDiscExternalFile source function

Disc-aware variant of fromExternalFile that filters image files
by disc number (extracted from filename) or folder association
(for multi-folder albums).

* feat(artwork): implement discArtworkReader

Resolves disc artwork using DiscArtPriority config patterns.
Supports glob patterns with disc number extraction, embedded
images from first track, and falls back to album cover art.
Handles both multi-folder and single-folder multi-disc albums.

* feat(artwork): register disc artwork reader in dispatcher

Add KindDiscArtwork case to getArtworkReader switch, routing
disc artwork requests to the new discArtworkReader.

* feat(subsonic): add CoverArt field to DiscTitle response

Implements OpenSubsonic PR #220: optional cover art ID in
DiscTitle responses for per-disc artwork support.

* feat(subsonic): populate CoverArt in DiscTitle responses

Each DiscTitle now includes a disc artwork ID (dc-albumID:discNum)
that clients can use with getCoverArt to retrieve per-disc artwork.

* style: fix file permission in test to satisfy gosec

* feat(ui): add disc cover art display and lightbox functionality

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor: simplify disc artwork code

- Add DiscArtworkID constructor to encapsulate the "albumID:discNumber"
  format in one place
- Convert fromDiscExternalFile to a method on discArtworkReader,
  reducing parameter count from 6 to 2
- Remove unused rootFolder field from discArtworkReader

* style: fix prettier formatting in subsonic index

* style(ui): move cursor style to makeStyles in SongDatagrid

* feat(artwork): add discsubtitle option to DiscArtPriority

Allow matching disc cover art by the disc's subtitle/name.
When the "discsubtitle" keyword is in the priority list, image files
whose stem matches the disc subtitle (case-insensitive) are used.
This is useful for box sets with named discs (e.g., "The Blue Disc.jpg").

* feat(configuration): update discartpriority to include cover art options

Signed-off-by: Deluan <deluan@navidrome.org>

---------

Signed-off-by: Deluan <deluan@navidrome.org>
2026-03-13 18:33:18 -04:00
Deluan Quintão
a50b2a1e72
feat(artwork): preserve animated image artwork during resize (#5184)
* feat(artwork): preserve animated image artwork during resize

Detect animated GIFs, WebPs, and APNGs via lightweight byte scanning
and preserve their animation when serving resized artwork. Animated GIFs
are converted to animated WebP via ffmpeg with optional downscaling;
animated WebP/APNG are returned as-is since ffmpeg cannot re-encode them.

Adds ConvertAnimatedImage to the FFmpeg interface for piping stdin data
through ffmpeg with animated WebP output.

* fix(artwork): address code review feedback for animated artwork

Fix ReadCloser leak where ffmpeg pipe's Close was discarded by
io.NopCloser wrapping — now preserves ReadCloser semantics when the
resized reader already supports Close. Use uint64 for PNG chunk position
to prevent potential overflow on 32-bit platforms. Add integration tests
for the animation branching logic in resizeImage.
2026-03-13 18:11:12 -04:00
Deluan Quintão
4ddb0774ec
perf(artwork): improve image serving performance with WebP encoding and optimized pipeline (#5181)
* test(artwork): add benchmark helpers for generating test images

* test(artwork): add image decode benchmarks for JPEG/PNG at various sizes

* test(artwork): add image resize benchmarks for Lanczos at various sizes

* test(artwork): add image encode benchmarks for JPEG quality levels and PNG

* test(artwork): add full resize pipeline benchmark (decode+resize+encode)

* test(artwork): add tag extraction benchmark for embedded art

* test(cache): add file cache benchmarks for read, write, and concurrent access

* test(artwork): add E2E benchmarks for artwork.Get with cache on/off and concurrency

* fix(test): use absolute path for tag extraction benchmark fixture

* test(artwork): add resize alternatives benchmark comparing resamplers

* perf(artwork): switch to CatmullRom resampler and JPEG for square images

Replace imaging.Lanczos with imaging.CatmullRom for image resizing
(30% faster, indistinguishable quality at thumbnail sizes). Stop forcing
PNG encoding for square images when the source is JPEG — JPEG is smaller
and faster to encode. Square images from JPEG sources went from 52ms to
10ms (80% improvement). Add sync.Pool for encode buffers to reduce GC
pressure under concurrent load.

* perf(artwork): increase cache warmer concurrency from 2 to 4 workers

Resize is CPU-bound, so more workers improve throughput on multi-core
systems. Doubled worker count to better utilize available cores during
background cache warming.

* perf(artwork): switch to xdraw.ApproxBiLinear and always encode as JPEG

Replace disintegration/imaging with golang.org/x/image/draw for image
resizing. This eliminates ~92K allocations per resize (from imaging's
internal goroutine parallelism) down to ~20, reducing GC pressure under
concurrent load.

Always encode resized artwork as JPEG regardless of source format, since
cover art doesn't need transparency. This is ~5x faster than PNG encode
and produces much smaller output (e.g. 18KB JPEG vs 124KB PNG).

* perf(artwork): skip external API call when artist image URL is cached

ArtistImage() was always calling the external agent (Spotify/Last.fm)
to get the image URL, even when the artist already had URLs stored in
the database. This caused every artist image request to block on an
external API call, creating severe serialization when loading artist
grids (5-20 seconds for the first page).

Now use the stored URL directly when available. Artists with no stored
URL still fetch synchronously. Background refresh via UpdateArtistInfo
handles TTL-based URL updates.

* perf(artwork): increase getCoverArt throttle from NumCPU/3 to NumCPU

The previous default of max(2, NumCPU/3) was too aggressive for artist
images which are I/O-bound (downloading from external CDNs), not
CPU-bound. On an 8-core machine this meant only 2 concurrent requests,
causing a staircase pattern where 12 images took ~2.4s wall-clock.

Bumping to max(4, NumCPU) cuts wall-clock time by ~50% for artist image
grids while still preventing unbounded concurrency for CPU-bound resizes.

* perf(artwork): encode resized images as WebP instead of JPEG

Switch from JPEG to WebP encoding for resized artwork using gen2brain/webp
(libwebp via WASM, no CGo). WebP produces ~74% smaller output at the same
quality with only ~25% slower full-pipeline encode time (cached, so only
paid once per artwork+size).

Use NRGBA image type to preserve alpha channel in WebP output, and
transparent padding for square canvas instead of black.

Also removes the disintegration/imaging dependency entirely by replacing
imaging.Fill in playlist tile generation with a custom fillCenter function
using xdraw.ApproxBiLinear.

* perf(artwork): switch from ApproxBiLinear to BiLinear scaling for improved image processing

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor(configuration): rename CoverJpegQuality to CoverArtQuality and update references

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(artwork): add DevJpegCoverArt option to control JPEG encoding for cover art

Signed-off-by: Deluan <deluan@navidrome.org>

* fix(artwork): remove redundant transparent fill and handle encode errors in resizeImage

Removed a no-op draw.Draw call that filled the NRGBA canvas with
transparent pixels — NewNRGBA already zero-initializes to fully
transparent. Also added an early return on encode failure to avoid
allocating and copying potentially corrupt buffer data before returning
the error.

* fix(configuration): reorder default agents (deezer is faster)

Signed-off-by: Deluan <deluan@navidrome.org>

* fix(test): resolve dogsled lint warning in tag extraction benchmark

Use all return values from runtime.Caller instead of discarding three
with blank identifiers, which triggered the dogsled linter.

* fix(artwork): revert cache key format

Signed-off-by: Deluan <deluan@navidrome.org>

* fix(configuration): remove deprecated CoverJpegQuality field and update references to CoverArtQuality

Signed-off-by: Deluan <deluan@navidrome.org>

---------

Signed-off-by: Deluan <deluan@navidrome.org>
2026-03-13 09:35:59 -04:00
Deluan
0790f66627 fix(scanner): increase watcher channel buffers to prevent dropped filesystem events
When files were moved between libraries, the small channel buffers (size 1)
throughout the watcher pipeline caused backpressure that led to dropped
filesystem events. This meant only some of the affected folders were scanned,
preventing cross-library move detection from working correctly.

Increase all watcher channel buffers to 500 and switch to blocking sends
to ensure no filesystem events are silently dropped.
2026-03-12 17:07:34 -04:00
Deluan Quintão
d0fbba14ff
fix(db): check both name and target_format in default transcodings migration (#5175)
The ensure_default_transcodings migration only checked target_format
before inserting, but the transcoding table has UNIQUE constraints on
both name and target_format. Older installations may have entries where
the name matches a default (e.g., 'opus audio') but the target_format
differs (e.g., 'oga' instead of 'opus'), causing a UNIQUE constraint
violation on name during the INSERT.

Fixes #5174
2026-03-12 11:39:31 -04:00
Kendall Garner
903e3f070f
fix(subsonic): always return required playqueue fields (#5172) 2026-03-12 08:29:37 -04:00
Deluan Quintão
0312eb33f1
fix(ui): improve browser codec detection and limit Safari transcoding to mp3 (#5171)
* fix: update codec MIME types to support multiple variants for better compatibility

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: limit Safari transcoding to mp3

Signed-off-by: Deluan <deluan@navidrome.org>

* style: format browserProfile test file with prettier

* fix: comment

Signed-off-by: Deluan <deluan@navidrome.org>

---------

Signed-off-by: Deluan <deluan@navidrome.org>
2026-03-12 08:21:49 -04:00
Deluan
5ecbe31a06 fix: implement fallback to DefaultDownsamplingFormat for unknown formats
Signed-off-by: Deluan <deluan@navidrome.org>
2026-03-11 09:46:13 -04:00
Deluan Quintão
d8bc41fbb1
fix: use ADTS for AAC transcoding, temporarily exclude AAC from transcode decisions (#5167)
* fix: use ADTS format for AAC transcoding to avoid silent output on ffmpeg 8.0+

The fragmented MP4 muxer (`-f ipod -movflags frag_keyframe+empty_moov`)
produces corrupt/silent audio when ffmpeg pipes to stdout, confirmed on
ffmpeg 8.0+. The moof atom offset values are zeroed out in pipe mode,
causing AAC decoder errors. Switch to `-f adts` (raw AAC framing) which
works reliably via pipe and is widely supported by clients including
UPnP/Sonos devices.

* fix: exclude AAC from transcode decision, as it is not working for Sonos.

Signed-off-by: Deluan <deluan@navidrome.org>

---------

Signed-off-by: Deluan <deluan@navidrome.org>
2026-03-11 09:26:32 -04:00
Deluan
51c48bcacd fix(ui): enforce consistent delete button contrast for delete in AMusic theme
Signed-off-by: Deluan <deluan@navidrome.org>
2026-03-10 18:12:57 -04:00
Deluan
75e5bc4e81 refactor: rename spy to streamerSpy in e2e tests for clarity
Renamed the spy variable to streamerSpy across all e2e test files so
that its purpose is immediately clear without needing to look up the
declaration.
2026-03-10 17:19:25 -04:00
Deluan
053a0fd6c0 fix: prevent raw file being returned when explicit transcode format is requested
When a client requests transcoding with an explicit format (e.g.,
format=opus) but no maxBitRate, buildLegacyClientInfo was adding a
direct play profile matching the source format. Since there was no
bitrate constraint to block it, MakeDecision would match the source
against the direct play profile and return the raw file instead of
transcoding. This fix only adds the direct play profile when no
explicit format was requested (bitrate-only downsampling) or when the
requested format matches the source format (allowing direct play when
no actual transcoding is needed).
2026-03-10 17:14:21 -04:00
Deluan Quintão
767744a301
refactor: rename core/transcode to core/stream, simplify MediaStreamer (#5166)
* refactor: rename core/transcode directory to core/stream

* refactor: update all imports from core/transcode to core/stream

* refactor: rename exported symbols to fit core/stream package name

* refactor: simplify MediaStreamer interface to single NewStream method

Remove the two-method interface (NewStream + DoStream) in favor of a
single NewStream(ctx, mf, req) method. Callers are now responsible for
fetching the MediaFile before calling NewStream. This removes the
implicit DB lookup from the streamer, making it a pure streaming
concern.

* refactor: update all callers from DoStream to NewStream

* chore: update wire_gen.go and stale comment for core/stream rename

* refactor: update wire command to handle GO_BUILD_TAGS correctly

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: distinguish not-found from internal errors in public stream handler

* refactor: remove unused ID field from stream.Request

* refactor: simplify ResolveRequestFromToken to receive *model.MediaFile

Move MediaFile fetching responsibility to callers, making the method
focused on token validation and request resolution. Remove ErrMediaNotFound
(no longer produced). Update GetTranscodeStream handler to fetch the
media file before calling ResolveRequestFromToken.

* refactor: extend tokenTTL from 12 to 48 hours

Signed-off-by: Deluan <deluan@navidrome.org>

---------

Signed-off-by: Deluan <deluan@navidrome.org>
2026-03-09 22:22:58 -04:00