4678 Commits

Author SHA1 Message Date
Deluan
a46cd491fb test(e2e): add NewTestStream function and implement spyStreamer for testing
Signed-off-by: Deluan <deluan@navidrome.org>
2026-03-08 23:51:30 -04:00
Deluan
a1a55141f6 refactor(transcode): reorder parameters in applyServerOverride function
Signed-off-by: Deluan <deluan@navidrome.org>
2026-03-08 21:38:48 -04:00
Deluan
a905a01008 refactor(transcode): split oversized files into focused modules
Split transcode.go and transcode_test.go into focused files by concern:
- decider.go: decision engine (MakeDecision, direct play/transcode evaluation, probe)
- token.go: JWT token encode/decode (params, toClaimsMap, paramsFromToken, CreateTranscodeParams, ResolveRequestFromToken)
- legacy_client.go: legacy Subsonic bridge (buildLegacyClientInfo, ResolveRequest)
- codec_test.go: isLosslessFormat and normalizeProbeCodec tests
- token_test.go: token round-trip and ResolveRequestFromToken tests

Moved the Decider interface from types.go to decider.go to keep it near
its implementation, and cleaned up types.go to contain only pure type
definitions and constants. No public API changes.
2026-03-08 21:20:07 -04:00
Deluan
41141219d8 test(transcode): remove redundant tests and use constants
Remove tests that duplicate coverage from integration-level tests
(toClaimsMap, paramsFromToken round-trips, applyServerOverride direct
call, duplicate 410 handler test). Replace raw "http" strings with
ProtocolHTTP constant. Consolidate lossy -sample_fmt tests into
DescribeTable.
2026-03-08 20:39:21 -04:00
Deluan
d5038c3194 refactor(transcode): rename Decider methods and unexport Params type
Rename ResolveStream → ResolveRequest and ValidateTranscodeParams →
ResolveRequestFromToken for clarity and consistency. The new
ResolveRequestFromToken returns a StreamRequest directly (instead of
the intermediate Params type), eliminating manual Params→StreamRequest
conversion in callers. Unexport Params to params since it is now only
used internally for JWT token parsing.
2026-03-08 20:25:20 -04:00
Deluan
3259040db0 refactor(transcode): move MediaStreamer into core/transcode and unify StreamRequest
Moved MediaStreamer, Stream, TranscodingCache and related types from
core/media_streamer.go into core/transcode/, eliminating the duplicate
StreamRequest type. The transcode.StreamRequest now carries all fields
(ID, Format, BitRate, SampleRate, BitDepth, Channels, Offset) and
ResolveStream returns a fully-populated value, removing manual field
copying at every call site. Also moved buildLegacyClientInfo into the
transcode package alongside ResolveStream, and unexported
ParseTranscodeParams since it was only used internally by
ValidateTranscodeParams.
2026-03-08 20:06:17 -04:00
Deluan
1ce16d8315 refactor(transcode): unify streaming decision engine via MakeDecision
Move transcoding decision-making out of mediaStreamer and into the
subsonic Stream/Download handlers, using transcode.Decider.MakeDecision
as the single decision engine. This eliminates selectTranscodingOptions
and the mismatch between decision and streaming code paths (decision
used LookupTranscodeCommand with built-in fallbacks, while streaming
used FindByFormat which only checked the DB).

- Add DecisionOptions with SkipProbe to MakeDecision so the legacy
  streaming path never calls ffprobe
- Add buildLegacyClientInfo to translate legacy stream params (format,
  maxBitRate, DefaultDownsamplingFormat) into a synthetic ClientInfo
- Add resolveStreamRequest on the subsonic Router to resolve legacy
  params into a fully specified StreamRequest via MakeDecision
- Simplify DoStream to a dumb executor that receives pre-resolved params
- Remove selectTranscodingOptions entirely

Signed-off-by: Deluan <deluan@navidrome.org>
2026-03-08 19:20:58 -04:00
Deluan
c69ed2a913 fix(transcode): honor bit depth and channel constraints in transcoding selection
selectTranscodingOptions only checked sample rate when deciding whether
same-format transcoding was needed, ignoring requested bit depth and
channel reductions. This caused the streamer to return raw audio when
the transcode decision requested downmix or bit-depth conversion.
2026-03-08 17:23:29 -04:00
Deluan
8f508d08f7 feat(transcode): implement server-side transcoding override for player formats
Signed-off-by: Deluan <deluan@navidrome.org>
2026-03-08 11:18:10 -04:00
Deluan
3e3f4e0e50 refactor(transcode): streamline transcoding command lookup and format resolution
Signed-off-by: Deluan <deluan@navidrome.org>
2026-03-07 21:50:29 -05:00
Deluan
b99092a697 fix(transcode): improve code review findings across transcode implementation
- Fix parseProbeData to return nil on JSON unmarshal failure instead of
  a zero-valued struct, preventing silent degradation of source stream details
- Use probe-resolved codec for lossless detection in buildSourceStream
  instead of the potentially stale scanner data
- Remove MediaFile.IsLossless() (dead code) and consolidate lossless
  detection in isLosslessFormat(), using codec name only — bit depth is
  not reliable since lossy codecs like ADPCM report non-zero values
- Add "wavpack" to lossless codec list (ffprobe codec_name for WavPack)
- Guard bpsToKbps against negative input values
- Fix misleading comment in buildTemplateArgs about conditional injection
- Avoid leaking internal error details in Subsonic API responses
- Add missing test for ErrNotFound branch in GetTranscodeDecision
- Add TODO for hardcoded protocol in toResponseStreamDetails
2026-03-07 20:20:29 -05:00
Deluan
580f455825 fix(ffmpeg): enhance bit_rate parsing logic for audio streams
Signed-off-by: Deluan <deluan@navidrome.org>
2026-03-07 18:40:22 -05:00
Deluan
4c0dd76bd7 refactor(transcode): simplify code after review
Extract getIntClaim helper to eliminate repeated int/int64/float64 JWT
claim extraction pattern in paramsFromToken and ClaimsFromToken. Rewrite
checkIntLimitation as a one-liner delegating to applyIntLimitation.
Return probe result from ensureProbed to avoid redundant JSON round-trip.
Extract toResponseStreamDetails helper and mediaTypeSong constant in
the API layer, and use transcode.ProtocolHTTP constant instead of
hardcoded string.

Signed-off-by: Deluan <deluan@navidrome.org>
2026-03-07 17:22:08 -05:00
Deluan
54d7c3a284 refactor(transcode): decouple transcode token claims from auth.Claims
Remove six transcode-specific fields (MediaID, DirectPlay, UpdatedAt,
Channels, SampleRate, BitDepth) from auth.Claims, which is shared with
session and share tokens. Transcode tokens are signed parameter-passing
tokens, not authentication tokens, so coupling them to auth created
misleading dependencies.

The transcode package now owns its own JWT claim serialization via
Decision.toClaimsMap() and paramsFromToken(), using generic
auth.EncodeToken/DecodeAndVerifyToken wrappers that keep TokenAuth
encapsulated. Wire format (JWT claim keys) is unchanged, so in-flight
tokens remain compatible.

Signed-off-by: Deluan <deluan@navidrome.org>
2026-03-07 16:41:09 -05:00
Deluan
26c703e729 refactor(ffmpeg): use command constant and select_streams for ProbeAudioStream
Move ffprobe arguments to a probeAudioStreamCmd constant, following the
same pattern as extractImageCmd and probeCmd. Add -select_streams a:0 to
only probe the first audio stream, avoiding unnecessary parsing of video
and artwork streams. Derive the ffprobe binary path safely using
filepath.Dir/Base instead of replacing within the full path string.
2026-03-07 14:58:53 -05:00
Deluan
411363daee test(transcode): add ensureProbed unit tests
Test probing when ProbeData is empty, skipping when already set,
error propagation from ffprobe, and DevEnableMediaFileProbe flag.
2026-03-07 14:19:33 -05:00
Deluan
c68dc22784 feat(transcoding): add DevEnableMediaFileProbe config flag
Add DevEnableMediaFileProbe (default true) to allow disabling ffprobe-
based media file probing as a safety fallback. When disabled, the
decider uses tag-based metadata from the scanner instead.
2026-03-07 14:19:28 -05:00
Deluan
3539df0f8a feat(transcoding): integrate ffprobe into transcode decisions
Add ensureProbed to probe media files on first transcode decision,
caching results in probe_data. Build SourceStream from probe data
with fallback to tag-based metadata.

Refactor decision logic to pass StreamDetails instead of MediaFile,
enabling codec profile limitations (e.g., audioProfile) to use
probe data. Add normalizeProbeCodec to map ffprobe codec names
(dsd_lsbf_planar, pcm_s16le) to internal names (dsd, pcm).

NewDecider now accepts ffmpeg.FFmpeg; wire_gen.go regenerated.
2026-03-07 14:06:41 -05:00
Deluan
2151c7e962 feat(ffmpeg): add ProbeAudioStream for authoritative audio metadata
Add ProbeAudioStream to FFmpeg interface, using ffprobe to extract
codec, profile, bitrate, sample rate, bit depth, and channels.
Parse bits_per_raw_sample as fallback for FLAC/ALAC bit depth.
Normalize "unknown" profile to empty string.
All parseProbeOutput tests use real ffprobe JSON from actual files.
2026-03-07 14:06:32 -05:00
Deluan
67d33558dd feat(model): add ProbeData field and UpdateProbeData repository method
Add probe_data TEXT column to media_file for caching ffprobe results.
Add UpdateProbeData to MediaFileRepository interface and implementations.
Use hash:"ignore" tag so probe data doesn't affect MediaFile fingerprints.
2026-03-07 14:06:24 -05:00
Deluan
5af03e1feb fix(transcoding): adapt transcode claims to struct-based auth.Claims
Updated transcode token handling to use the struct-based auth.Claims
introduced on master, replacing the previous map[string]any approach.
Extended auth.Claims with transcoding-specific fields (MediaID, DirectPlay,
UpdatedAt, Channels, SampleRate, BitDepth) and added float64 fallback in
ClaimsFromToken for numeric claims that lose their Go type during JWT
string serialization. Also added the missing lyrics parameter to all
subsonic.New() calls in test files.
2026-03-07 12:02:00 -05:00
Deluan
004ebea7d0 fix: small issues
Updated mock AAC transcoding command to use the new default (ipod with
fragmented MP4) matching the migration, ensuring tests exercise the same
buildDynamicArgs code path as production. Improved archiver test mock to
match on the whole StreamRequest struct instead of decomposing fields,
making it resilient to future field additions. Added named constants for
JWT claim keys in the transcode token and wrapped ParseTranscodeParams
errors with ErrTokenInvalid for consistency. Documented the IsLossless
BitDepth fallback heuristic as temporary until Codec column is populated.

Signed-off-by: Deluan <deluan@navidrome.org>
2026-03-07 12:02:00 -05:00
Deluan
1d3f3fdca2 feat(transcoding): add sourceUpdatedAt to decision and validate transcode parameters
Signed-off-by: Deluan <deluan@navidrome.org>
2026-03-07 12:02:00 -05:00
Deluan
8c7c6e536f fix: address review findings for OpenSubsonic transcoding PR
Fix multiple issues identified during code review of the transcoding
extension: add missing return after error in shared stream handler
preventing nil pointer panic, replace dead r.Body nil check with
MaxBytesReader size limit, distinguish not-found from other DB errors,
fix bpsToKbps integer truncation with rounding, add "pcm" to
isLosslessFormat for consistency with model.IsLossless(), add
sampleRate/bitDepth/channels to streaming log, fix outdated test
comment, and add tests for conversion functions and GetTranscodeStream
parameter passing.
2026-03-07 12:02:00 -05:00
Deluan
b4000fb99a fix: implement noopDecider for transcoding decision handling in tests
Signed-off-by: Deluan <deluan@navidrome.org>
2026-03-07 12:02:00 -05:00
Deluan
84ba98cca6 refactor(transcoding): update default command handling and add codec support for transcoding
Signed-off-by: Deluan <deluan@navidrome.org>
2026-03-07 12:02:00 -05:00
Deluan
d86675df3b refactor(transcoding): streamline transcoding logic by consolidating stream parameter handling and enhancing alias mapping
Signed-off-by: Deluan <deluan@navidrome.org>
2026-03-07 12:02:00 -05:00
Deluan
27549de4c7 refactor(transcoding): enhance AAC command handling and support for audio channels in streaming
Signed-off-by: Deluan <deluan@navidrome.org>
2026-03-07 12:02:00 -05:00
Deluan
6d19a23c9a refactor(transcoding): add bit depth support for audio transcoding and enhance related logic
Signed-off-by: Deluan <deluan@navidrome.org>
2026-03-07 12:02:00 -05:00
Deluan
e04f64e59e refactor(transcoding): enhance transcoding options with sample rate support and improve command handling
Signed-off-by: Deluan <deluan@navidrome.org>
2026-03-07 12:02:00 -05:00
Deluan
e67884c3b6 refactor(transcoding): enhance transcoding config lookup logic for audio codecs
Signed-off-by: Deluan <deluan@navidrome.org>
2026-03-07 12:02:00 -05:00
Deluan
1cc7a2cd66 refactor(transcoding): rename TranscodeDecision to Decider and update related methods for clarity
Signed-off-by: Deluan <deluan@navidrome.org>
2026-03-07 12:02:00 -05:00
Deluan
d4357c670b refactor(transcoding): enhance logging for transcode decision process and client info conversion
Signed-off-by: Deluan <deluan@navidrome.org>
2026-03-07 12:02:00 -05:00
Deluan
a4d97e0265 refactor(transcoding): rename token methods to CreateTranscodeParams and ParseTranscodeParams for clarity
Signed-off-by: Deluan <deluan@navidrome.org>
2026-03-07 12:02:00 -05:00
Deluan
0d727444ea refactor(transcoding): replace strings.EqualFold with direct comparison for protocol and limitation checks
Signed-off-by: Deluan <deluan@navidrome.org>
2026-03-07 12:02:00 -05:00
Deluan
c60fb2e419 refactor(transcoding): streamline limitation checks and applyLimitation logic for improved readability and maintainability
Signed-off-by: Deluan <deluan@navidrome.org>
2026-03-07 12:02:00 -05:00
Deluan
f0dee43a98 feat(transcoding): add enums for protocol, comparison operators, limitations, and codec profiles in transcode decision logic
Signed-off-by: Deluan <deluan@navidrome.org>
2026-03-07 12:02:00 -05:00
Deluan
288006c564 fix(transcoding): enforce POST method for GetTranscodeDecision and handle non-POST requests
Signed-off-by: Deluan <deluan@navidrome.org>
2026-03-07 12:02:00 -05:00
Deluan
736ec99e2a refactor(transcoding): simplify container alias handling in matchesContainer function
Signed-off-by: Deluan <deluan@navidrome.org>
2026-03-07 12:02:00 -05:00
Deluan
954139fa7b fix(transcoding): update bitrate handling to use kilobits per second (kbps) across transcode decision logic
Signed-off-by: Deluan <deluan@navidrome.org>
2026-03-07 12:02:00 -05:00
Deluan
0c9e3221e9 fix(subsonic): update codec limitation structure and decision logic for improved clarity
Signed-off-by: Deluan <deluan@navidrome.org>
2026-03-07 12:02:00 -05:00
Deluan
662e3cf723 feat(subsonic): implement transcode decision logic and codec handling for media files
Signed-off-by: Deluan <deluan@navidrome.org>
2026-03-07 12:02:00 -05:00
Deluan
e1b3412999 fix(scanner): update gotaglib version to reflect actual dependency version
Signed-off-by: Deluan <deluan@navidrome.org>
2026-03-07 12:00:09 -05:00
Deluan
3cd5d16b0a chore: upgrade golangci-lint to 2.11 and fix lint issues
Signed-off-by: Deluan <deluan@navidrome.org>
2026-03-06 19:23:47 -05:00
Deluan
f102036dc6 fix(server): clear server-managed fields in savePlaylist to prevent injection via REST API
Signed-off-by: Deluan <deluan@navidrome.org>
2026-03-05 20:56:16 -05:00
Deluan
d2db41691e fix(ui): conditionally render sync toggle based on screen size
Signed-off-by: Deluan <deluan@navidrome.org>
2026-03-05 20:47:35 -05:00
Deluan
1ce561cc8e refactor(server): remove legacy embedded coverart logic
Signed-off-by: Deluan <deluan@navidrome.org>
2026-03-05 19:53:59 -05:00
dependabot[bot]
12f28b9d97
chore(deps): bump dompurify in /ui (#5147)
Bumps [dompurify](https://github.com/cure53/DOMPurify) to 3.3.2 and updates ancestor dependency . These dependencies need to be updated together.


Updates `dompurify` from 3.3.1 to 3.3.2
- [Release notes](https://github.com/cure53/DOMPurify/releases)
- [Commits](https://github.com/cure53/DOMPurify/compare/3.3.1...3.3.2)

Updates `dompurify` from 2.5.8 to 2.5.9
- [Release notes](https://github.com/cure53/DOMPurify/releases)
- [Commits](https://github.com/cure53/DOMPurify/compare/3.3.1...3.3.2)

---
updated-dependencies:
- dependency-name: dompurify
  dependency-version: 3.3.2
  dependency-type: direct:production
- dependency-name: dompurify
  dependency-version: 2.5.9
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-05 19:06:12 -05:00
dependabot[bot]
627266ec82
chore(deps): bump immutable from 4.3.7 to 4.3.8 in /ui (#5145)
Bumps [immutable](https://github.com/immutable-js/immutable-js) from 4.3.7 to 4.3.8.
- [Release notes](https://github.com/immutable-js/immutable-js/releases)
- [Changelog](https://github.com/immutable-js/immutable-js/blob/main/CHANGELOG.md)
- [Commits](https://github.com/immutable-js/immutable-js/compare/v4.3.7...v4.3.8)

---
updated-dependencies:
- dependency-name: immutable
  dependency-version: 4.3.8
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-05 08:01:53 -05:00
Deluan Quintão
11e4aaed1b
feat(server): add percentage-based limits to smart playlists (#5144)
* feat(playlists): add percentage-based limits to smart playlists

Add a new `limitPercent` JSON field to Criteria that allows smart playlist
limits to be expressed as a percentage of matching tracks rather than a
fixed number. For example, a playlist matching 450 songs with a 10% limit
returns 45 songs, scaling dynamically as the library grows.

When `limitPercent` is set, refreshSmartPlaylist runs a COUNT query first
to determine the total matching tracks, then resolves the percentage to an
absolute LIMIT before executing the main query. The fixed `limit` field
takes precedence when both are set. Values are clamped to [0, 100] during
JSON unmarshaling.

No database migration is needed since rules are stored as a JSON string.

* fix(criteria): validate percentage limit range in IsPercentageLimit method

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

* fix(criteria): ensure idempotency of ToSql method for expressions

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

---------

Signed-off-by: Deluan <deluan@navidrome.org>
2026-03-04 22:42:49 -05:00