* feat(plugins): add sonicSimilarity capability types
Defines the SonicSimilarity plugin capability interface with
GetSonicSimilarTracks and FindSonicPath methods, along with
their request/response types.
* feat(sonic): add core sonic similarity service
Implements the Sonic service with HasProvider, GetSonicSimilarTracks,
and FindSonicPath, delegating to the PluginLoader and using the
Matcher for index-preserving library resolution.
* test(sonic): add sonic service unit tests
Covers HasProvider, GetSonicSimilarTracks, and FindSonicPath with
mock plugin loader and provider, verifying error propagation and
successful match resolution via the library matcher.
* feat(matcher): add MatchSongsToLibraryMap for index-preserving matching
Adds a new method alongside MatchSongsToLibrary that returns a
map[int]MediaFile keyed by input song index rather than a flat slice,
enabling callers to correlate similarity scores back to the original
position in the results.
* fix(sonic): check provider availability before MediaFile lookup
Avoids unnecessary DB call when no plugin is available, and ensures
the correct error path is tested.
* feat(plugins): add sonic similarity adapter
Adds SonicSimilarityAdapter implementing sonic.Provider, bridging the
plugin system to the core sonic service via Extism plugin functions.
Reuses existing songRefsToAgentSongs helper for SongRef conversion.
* feat(plugins): add LoadSonicSimilarity to plugin manager
Adds Manager.LoadSonicSimilarity method following the pattern of
LoadLyricsProvider, enabling the core sonic service to load a
SonicSimilarityAdapter from a named plugin.
* feat(subsonic): add sonicMatch response type
Add SonicMatch struct with Entry and Similarity fields, and a SonicMatches slice to the Subsonic response struct. These types support the OpenSubsonic sonicSimilarity extension for returning similarity-scored track results.
* feat(subsonic): add getSonicSimilarTracks and findSonicPath handlers
Add two new Subsonic API handlers for the sonicSimilarity OpenSubsonic extension: GetSonicSimilarTracks returns similarity-scored tracks similar to a given song, and FindSonicPath returns a path of tracks connecting two songs. Both handlers delegate to the sonic core service and map results to SonicMatch response types.
* feat(subsonic): advertise sonicSimilarity extension when plugin available
Update GetOpenSubsonicExtensions to conditionally include the sonicSimilarity extension only when a sonic similarity plugin provider is available. The nil guard ensures backward compatibility with tests that pass nil for the sonic field. Also update the existing test to pass the new nil parameter.
* feat(subsonic): wire sonic similarity service into router
Add the sonic.Sonic service to the Router struct and New() constructor, register the getSonicSimilarTracks and findSonicPath routes, and wire sonic.New and its PluginLoader binding into the Wire dependency injection graph. Update all existing test call sites to pass the new nil parameter. Regenerate wire_gen.go.
* fix(e2e): add sonic parameter to subsonic.New call in e2e tests
* test(subsonic): add sonicSimilarity extension advertisement tests
Restructures the GetOpenSubsonicExtensions test into two contexts: one verifying the baseline 5 extensions are returned when no sonic similarity plugin is configured, and one verifying that the sonicSimilarity extension is advertised (making 6 total) when a plugin loader reports an available provider. Adds a mockSonicPluginLoader to satisfy the sonic.PluginLoader interface without requiring a real plugin.
* feat(subsonic): add nil guard and e2e tests for sonic similarity endpoints
Handlers return ErrorDataNotFound when no sonic service is configured,
preventing nil panics. E2e tests verify both endpoints return proper
error responses when no plugin is available.
* fix(subsonic): return HTTP 404 when no sonic similarity plugin available
Endpoints are always registered but return 404 when no provider is
available, rather than a subsonic error code 70.
* refactor: clean up sonic similarity code after review
Extract shared helpers to reduce duplication across the sonic similarity
implementation: loadAllMatches in matcher consolidates the 4-phase
matching pipeline, songRefToAgentSong eliminates per-iteration slice
allocation in the adapter, sonicMatchResponse deduplicates response
building in handlers, and a package-level constant replaces raw
capability name strings in core/sonic.
* fix empty response shapes
Signed-off-by: Deluan <deluan@navidrome.org>
* test(plugins): add testdata plugin and e2e tests for sonic similarity
Add a test-sonic-similarity WASM plugin that implements both
GetSonicSimilarTracks and FindSonicPath via the generated sonicsimilarity
PDK. The plugin returns deterministic test data with decreasing similarity
scores and supports error injection via config. Adapter tests verify the
full round-trip through the WASM plugin including error handling. Also
includes regenerated PDK code from make gen.
* docs: update README to include new capabilities and usage examples for plugins
Signed-off-by: Deluan <deluan@navidrome.org>
* test(e2e): enhance sonic similarity tests with additional scenarios and mock provider
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: address PR review feedback for sonic similarity
Fix incorrect field names in README documentation ({from, to} →
{startSong, endSong}) and remove unnecessary XML serialization test
from e2e suite since OpenSubsonic endpoints only use JSON.
* refactor: rename Matcher methods for conciseness
Rename MatchSongsToLibrary to MatchSongs and MatchSongsToLibraryMap to
MatchSongsIndexed. The Matcher receiver already establishes the "to
library" context, making that suffix redundant, and "Indexed" better
describes the intent (preserving input ordering) than "Map" which
describes the data structure.
* refactor: standardize variable naming for media files in sonic path methods
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: simplify plugin loading by introducing adapter constructors
Signed-off-by: Deluan <deluan@navidrome.org>
---------
Signed-off-by: Deluan <deluan@navidrome.org>
* 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>
* feat(subsonic): implement transcode decision logic and codec handling for media files
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(subsonic): update codec limitation structure and decision logic for improved clarity
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(transcoding): update bitrate handling to use kilobits per second (kbps) across transcode decision logic
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(transcoding): simplify container alias handling in matchesContainer function
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(transcoding): enforce POST method for GetTranscodeDecision and handle non-POST requests
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(transcoding): add enums for protocol, comparison operators, limitations, and codec profiles in transcode decision logic
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(transcoding): streamline limitation checks and applyLimitation logic for improved readability and maintainability
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(transcoding): replace strings.EqualFold with direct comparison for protocol and limitation checks
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(transcoding): rename token methods to CreateTranscodeParams and ParseTranscodeParams for clarity
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(transcoding): enhance logging for transcode decision process and client info conversion
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(transcoding): rename TranscodeDecision to Decider and update related methods for clarity
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(transcoding): enhance transcoding config lookup logic for audio codecs
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(transcoding): enhance transcoding options with sample rate support and improve command handling
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(transcoding): add bit depth support for audio transcoding and enhance related logic
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(transcoding): enhance AAC command handling and support for audio channels in streaming
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(transcoding): streamline transcoding logic by consolidating stream parameter handling and enhancing alias mapping
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(transcoding): update default command handling and add codec support for transcoding
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: implement noopDecider for transcoding decision handling in tests
Signed-off-by: Deluan <deluan@navidrome.org>
* 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.
* feat(transcoding): add sourceUpdatedAt to decision and validate transcode parameters
Signed-off-by: Deluan <deluan@navidrome.org>
* 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>
* 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.
* 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.
* 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.
* 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.
* 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.
* test(transcode): add ensureProbed unit tests
Test probing when ProbeData is empty, skipping when already set,
error propagation from ffprobe, and DevEnableMediaFileProbe flag.
* 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.
* 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>
* 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>
* fix(ffmpeg): enhance bit_rate parsing logic for audio streams
Signed-off-by: Deluan <deluan@navidrome.org>
* 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
* refactor(transcode): streamline transcoding command lookup and format resolution
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(transcode): implement server-side transcoding override for player formats
Signed-off-by: Deluan <deluan@navidrome.org>
* 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.
* 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>
* 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.
* 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.
* 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.
* 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.
* refactor(transcode): reorder parameters in applyServerOverride function
Signed-off-by: Deluan <deluan@navidrome.org>
* test(e2e): add NewTestStream function and implement spyStreamer for testing
Signed-off-by: Deluan <deluan@navidrome.org>
---------
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(plugins): add lyrics provider plugin capability
Refactor the lyrics system from a static function to an interface-based
service that supports WASM plugin providers. Plugins listed in the
LyricsPriority config (alongside "embedded" and file extensions) are
now resolved through the plugin system.
Includes capability definition, Go/Rust PDK, adapter, Wire integration,
and tests for plugin fallback behavior.
* test(plugins): add lyrics capability integration test with test plugin
* fix(plugins): default lyrics language to 'xxx' when plugin omits it
Per the OpenSubsonic spec, the server must return 'und' or 'xxx' when
the lyrics language is unknown. The lyrics plugin adapter was passing
an empty string through when a plugin didn't provide a language value.
This defaults the language to 'xxx', consistent with all other callers
of model.ToLyrics() in the codebase.
* refactor(plugins): rename lyrics import to improve clarity
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(lyrics): update TrackInfo description for clarity
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(lyrics): enhance lyrics plugin handling and case sensitivity
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(plugins): update payload type to string with byte format for task data
Signed-off-by: Deluan <deluan@navidrome.org>
---------
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: move playlist business logic from repositories to core.Playlists service
Move authorization, permission checks, and orchestration logic from
playlist repositories to the core.Playlists service, following the
existing pattern used by core.Share and core.Library.
Changes:
- Expand core.Playlists interface with read, mutation, track management,
and REST adapter methods
- Add playlistRepositoryWrapper for REST Save/Update/Delete with
permission checks (follows Share/Library pattern)
- Simplify persistence/playlist_repository.go: remove isWritable(),
auth checks from Delete()/Put()/updatePlaylist()
- Simplify persistence/playlist_track_repository.go: remove
isTracksEditable() and permission checks from Add/Delete/Reorder
- Update Subsonic API handlers to route through service
- Update Native API handlers to accept core.Playlists instead of
model.DataStore
* test: add coverage for playlist service methods and REST wrapper
Add 30 new tests covering the service methods added during the playlist
refactoring:
- Delete: owner, admin, denied, not found
- Create: new playlist, replace tracks, admin bypass, denied, not found
- AddTracks: owner, admin, denied, smart playlist, not found
- RemoveTracks: owner, smart playlist denied, non-owner denied
- ReorderTrack: owner, smart playlist denied
- NewRepository wrapper: Save (owner assignment, ID clearing),
Update (owner, admin, denied, ownership change, not found),
Delete (delegation with permission checks)
Expand mockedPlaylistRepo with Get, Delete, Tracks, GetWithTracks, and
rest.Persistable methods. Add mockedPlaylistTrackRepo for track
operation verification.
* fix: add authorization check to playlist Update method
Added ownership verification to the Subsonic Update endpoint in the
playlist service layer. The authorization check was present in the old
repository code but was not carried over during the refactoring to the
service layer, allowing any authenticated user to modify playlists they
don't own via the Subsonic API. Also added corresponding tests for the
Update method's permission logic.
* refactor: improve playlist permission checks and error handling, add e2e tests
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: rename core.Playlists to playlists package and update references
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: rename playlists_internal_test.go to parse_m3u_test.go and update tests; add new parse_nsp.go and rest_adapter.go files
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: block track mutations on smart playlists in Create and Update
Create now rejects replacing tracks on smart playlists (pre-existing
gap). Update now uses checkTracksEditable instead of checkWritable
when track changes are requested, restoring the protection that was
removed from the repository layer during the refactoring. Metadata-only
updates on smart playlists remain allowed.
* test: add smart playlist protection tests to ensure readonly behavior and mutation restrictions
* refactor: optimize track removal and renumbering in playlists
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: implement track reordering in playlists with SQL updates
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: wrap track deletion and reordering in transactions for consistency
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: remove unused getTracks method from playlistTrackRepository
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: optimize playlist track renumbering with CTE-based UPDATE
Replace the DELETE + re-INSERT renumbering strategy with a two-step
UPDATE approach using a materialized CTE and ROW_NUMBER() window
function. The previous approach (SELECT all IDs, DELETE all tracks,
re-INSERT in chunks of 200) required 13 SQL operations for a 2000-track
playlist. The new approach uses just 2 UPDATEs: first negating all IDs
to clear the positive space, then assigning sequential positions via
UPDATE...FROM with a CTE. This avoids the UNIQUE constraint violations
that affected the original correlated subquery while reducing per-delete
request time from ~110ms to ~12ms on a 2000-track playlist.
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: rename New function to NewPlaylists for clarity
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: update mock playlist repository and tests for consistency
Signed-off-by: Deluan <deluan@navidrome.org>
---------
Signed-off-by: Deluan <deluan@navidrome.org>
Bump golangci-lint from v2.9.0 to v2.10.0, which includes a newer gosec
with additional taint-analysis rules (G117, G703, G704, G705) and a
stricter G101 check. Added inline //nolint:gosec comments to suppress
21 false positives across 19 files: struct fields flagged as secrets
(G117), w.Write calls flagged as XSS (G705), HTTP client calls flagged
as SSRF (G704), os.Stat/os.ReadFile/os.Remove flagged as path traversal
(G703), and a sort mapping flagged as hardcoded credentials (G101).
Signed-off-by: Deluan <deluan@navidrome.org>
Added validation to ensure the JSONP callback parameter is a valid
JavaScript identifier before reflecting it into the response. Invalid
callbacks now return a JSON error response instead. This prevents
malicious input from being injected into the response body via the
callback parameter.
* feat: Add selective folder scanning capability
Implement targeted scanning of specific library/folder pairs without
full recursion. This enables efficient rescanning of individual folders
when changes are detected, significantly reducing scan time for large
libraries.
Key changes:
- Add ScanTarget struct and ScanFolders API to Scanner interface
- Implement CLI flag --targets for specifying libraryID:folderPath pairs
- Add FolderRepository.GetByPaths() for batch folder info retrieval
- Create loadSpecificFolders() for non-recursive directory loading
- Scope GC operations to affected libraries only (with TODO for full impl)
- Add comprehensive tests for selective scanning behavior
The selective scan:
- Only processes specified folders (no subdirectory recursion)
- Maintains library isolation
- Runs full maintenance pipeline scoped to affected libraries
- Supports both full and quick scan modes
Examples:
navidrome scan --targets "1:Music/Rock,1:Music/Jazz"
navidrome scan --full --targets "2:Classical"
* feat(folder): replace GetByPaths with GetFolderUpdateInfo for improved folder updates retrieval
Signed-off-by: Deluan <deluan@navidrome.org>
* test: update parseTargets test to handle folder names with spaces
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(folder): remove unused LibraryPath struct and update GC logging message
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(folder): enhance external scanner to support target-specific scanning
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(scanner): simplify scanner methods
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(watcher): implement folder scanning notifications with deduplication
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(watcher): add resolveFolderPath function for testability
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(watcher): implement path ignoring based on .ndignore patterns
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(scanner): implement IgnoreChecker for managing .ndignore patterns
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(ignore_checker): rename scanner to lineScanner for clarity
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(scanner): enhance ScanTarget struct with String method for better target representation
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(scanner): validate library ID to prevent negative values
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(scanner): simplify GC method by removing library ID parameter
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(scanner): update folder scanning to include all descendants of specified folders
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(subsonic): allow selective scan in the /startScan endpoint
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(scanner): update CallScan to handle specific library/folder pairs
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(scanner): streamline scanning logic by removing scanAll method
Signed-off-by: Deluan <deluan@navidrome.org>
* test: enhance mockScanner for thread safety and improve test reliability
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(scanner): move scanner.ScanTarget to model.ScanTarget
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: move scanner types to model,implement MockScanner
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(scanner): update scanner interface and implementations to use model.Scanner
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(folder_repository): normalize target path handling by using filepath.Clean
Signed-off-by: Deluan <deluan@navidrome.org>
* test(folder_repository): add comprehensive tests for folder retrieval and child exclusion
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(scanner): simplify selective scan logic using slice.Filter
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(scanner): streamline phase folder and album creation by removing unnecessary library parameter
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(scanner): move initialization logic from phase_1 to the scanner itself
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(tests): rename selective scan test file to scanner_selective_test.go
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(configuration): add DevSelectiveWatcher configuration option
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(watcher): enhance .ndignore handling for folder deletions and file changes
Signed-off-by: Deluan <deluan@navidrome.org>
* docs(scanner): comments
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(scanner): enhance walkDirTree to support target folder scanning
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(scanner, watcher): handle errors when pushing ignore patterns for folders
Signed-off-by: Deluan <deluan@navidrome.org>
* Update scanner/phase_1_folders.go
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* refactor(scanner): replace parseTargets function with direct call to scanner.ParseTargets
Signed-off-by: Deluan <deluan@navidrome.org>
* test(scanner): add tests for ScanBegin and ScanEnd functionality
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(library): update PRAGMA optimize to check table sizes without ANALYZE
Signed-off-by: Deluan <deluan@navidrome.org>
* test(scanner): refactor tests
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(ui): add selective scan options and update translations
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(ui): add quick and full scan options for individual libraries
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(ui): add Scan buttonsto the LibraryList
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(scan): update scanning parameters from 'path' to 'target' for selective scans.
* refactor(scan): move ParseTargets function to model package
* test(scan): suppress unused return value from SetUserLibraries in tests
* feat(gc): enhance garbage collection to support selective library purging
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(scanner): prevent race condition when scanning deleted folders
When the watcher detects changes in a folder that gets deleted before
the scanner runs (due to the 10-second delay), the scanner was
prematurely removing these folders from the tracking map, preventing
them from being marked as missing.
The issue occurred because `newFolderEntry` was calling `popLastUpdate`
before verifying the folder actually exists on the filesystem.
Changes:
- Move fs.Stat check before newFolderEntry creation in loadDir to
ensure deleted folders remain in lastUpdates for finalize() to handle
- Add early existence check in walkDirTree to skip non-existent target
folders with a warning log
- Add unit test verifying non-existent folders aren't removed from
lastUpdates prematurely
- Add integration test for deleted folder scenario with ScanFolders
Fixes the issue where deleting entire folders (e.g., /music/AC_DC)
wouldn't mark tracks as missing when using selective folder scanning.
* refactor(scan): streamline folder entry creation and update handling
Signed-off-by: Deluan <deluan@navidrome.org>
* feat(scan): add '@Recycle' (QNAP) to ignored directories list
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(log): improve thread safety in logging level management
* test(scan): move unit tests for ParseTargets function
Signed-off-by: Deluan <deluan@navidrome.org>
* review
Signed-off-by: Deluan <deluan@navidrome.org>
---------
Signed-off-by: Deluan <deluan@navidrome.org>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: deluan <deluan.quintao@mechanical-orchard.com>
* Add prometheus metrics to subsonic and plugins
* address feedback, do not log error if operation is not supported
* add missing timestamp and client to stats
* remove .view from subsonic route
* directly inject DataStore in Prometheus, to avoid having to pass it in every call
Signed-off-by: Deluan <deluan@navidrome.org>
---------
Signed-off-by: Deluan <deluan@navidrome.org>
Co-authored-by: Deluan <deluan@navidrome.org>
* draft commit
* time to fight pipeline
* round 2 changes
* remove unnecessary line
* fight taglib. again
* make taglib work again???
* add id3 tags
* taglib 1.12 vs 1.13
* use int instead for windows
* store as json now
* add migration, more tests
* support repeated line, multiline
* fix ms and support .m, .mm, .mmm
* address some concerns, make cpp a bit safer
* separate responses from model
* remove [:]
* Add trace log
* Try to unblock pipeline
* Fix merge errors
* Fix SIGSEGV error (proper handling of empty frames)
* Add fallback artist/title to structured lyrics
* Rename conflicting named vars
* Fix tests
* Do we still need ffmpeg in the pipeline?
* Revert "Do we still need ffmpeg in the pipeline?"
Yes we do.
This reverts commit 87df7f6df7.
* Does this passes now, with a newer ffmpeg version?
* Revert "Does this passes now, with a newer ffmpeg version?"
No, it does not :(
This reverts commit 372eb4b0ae.
* My OCD made me do it :P
---------
Co-authored-by: Deluan Quintão <deluan@navidrome.org>
* Adding cache directory to ignore-list
* Adding jukebox-related config options
* Adding DevEnableJukebox config option pls. dummy server
* Adding types and routers
* Now without panic
* First draft on parsing the action
* Some cleanups
* Adding playback server
* Verify audio device configuration
* Adding debug-build target to have full symbol support
* Adding beep sound library pls some example code. Not working yet
* Play a fixed mp3 on any interface access for testing purposes
* Put action code into separate file, adding stringer, more debug output, prepare structs, validation
* Put action parameter parser code where it belongs
* Have a single Action transporting all information
* User fmt.Errorf for error-generation
* Adding wide playback interface
* Use action map for parsing, stringer instead switch stmt.
* Use but only one switch case and direct dispatch, refactoring
* Add error handling and pushing to client
* send decent errormessage, no internal server error
* Adding playback devices slice and load it from config
* Combine config-verification and structure init
* Return user-specific device
* Separate playback server from device
* Use dataStore to retrieve mediafile by id
* WIP: Playlist and start/stop handling. Doing start/stop the hard way as of now
* WIP: set, start and stop work on one single song. More to come
* Dont need to wait for the end
* Merge jukebox_action.go into jukebox.go
* Remove getParameterAsInt64(). Use existing requiredParamInt() instead
* Dont need to call newFailure() explicitly
* Remove int64, use int instead.
* Add and set action now accept multiple ids
* Kickout copy of childFromMediaFile(). It is not needed here.
* Refactoring devices and playbackServer
* Turn (internal) playback.DeviceStatus into subsonic JukeboxStatus when rendering output. Indexes int64 -> int
* Now we have a position and playing status
* Switching gain to float32, xs:float is defined as 32 bit. Fixing nasty copy/pointer bug
* Now with volume control
* Start working the queue
* Remove user from device interface
* Rename function GetDevice -> GetDeviceForUser to make intention clearer
* Have a nice stringer for the queue
* User Prepared boolean for now to allow pause/unpause
* Skipping works, but without offsets
* Make ChildFromMediaFile public to be used in jukebox get() implementation
* Return position in seconds and implement offset-skip in seconds
* Default offset to 0
* Adding a simple setGain implementation
* Prepare for transcoding AAC
* WIP: transcode to WAV to use beeps wav decoder. Not done yet.
* WIP: out of sheer desparation: convert to MP3 (which works) rather than WAV to troubleshoot issue.
* Use FLAC as intermediate format to play Apple AAC
* A bit of cleanup
* Catching the end-of-stream event for further reactions
* Have a trackSwitching goroutine waiting on channel when track ends
* Move decoder code into own file. Restructure code a bit
* Now with going on to play the next song in the playlist
* Adding shuffle feature
* Implementing remove action
* Cleanup code
* Remove templates for ffmpeg mp3 generation. Not needed anymore.
* Adding some documentation
* Check whether offset into track is in range. Fixing potential remove track bug. Documentation
* Make golangci-lint happy: handling return values
* Adding test suite and example dummy for playback package
* Adding some basic queue tests
* Only use Jukebox.Enabled config option
* Adding stream closing handling
* Pass context.Context to all PlaybackDevice methods
* Remove unneeded function
* Correct spelling
* Reduce visibility of ChildFromMediaFile
* Decomplicate action-parsing
* Adding simple tempfile-based AAC->FLAC transcoding. No parallel reading and writing yet.
* Try to optimize pipe-writing, tempfile-handling and reading. Not done yet.
* Do a synchronous copy of the tempfile. Racecondition detected
* More debugging statements and fixing the play/pause bug. More work needed
* Start the trackSwitcher() with each device once. Return JSON position even if its 0. More debug-output
* Moving all track-handling code into own module
* Fix typo. Do not pass ctx around when not applicable
* WIP: More refactoring, debugging output
* Fix nil pointer
* Repairing MP3 playback by pinning indirect dependencies: hajimehoshi/go-mp3 and hajimehoshi/oto
* Do not forget to cleanup after a skip action
* Make resync with master easy
* Adding missing mocks
* Adding missing error-handling found by linter
* Updating github.com/hajimehoshi/oto
* Removing duplicate function
* Move BEEP-related code into own package
* Juggle beep-related code around as preparation for interface access
* More refactoring for interface separation
* Gather CloseDevice() behind Track interface.
* Adding skeleton, draft audio-interface using mpv.io
* Adding majority of interface commands using messages to mpv socket.
* Adding end-of-stream handling
* MPV: start/stop are working
* postition is given in float in mpv
* Unify Close() and CloseDevice(). Using temp filename for controlling socket
* Wait until control-socket shows up. Cleanup socket in Close()
* Use canceable command. Rename to Executor
* Skipping tracks works now
* Now with actually setting the position
* Fix regain
* Add missing error-handling found by linter
* Adding retry mode on time-pos property getter
* Remove unneeded code on queue
* Putting build-tag beep onto beep files
* Remove deprecated call to rand.Seed()
"As of Go 1.20 there is no reason to call Seed with a random value. Programs that call Seed with a known value to get a specific sequence of results should use New(NewSource(seed)) to obtain a local random generator."
* Using int32 to conform to Subsonic API spec
* Fix merge error
* Minor style changes
* Get username from context
---------
Co-authored-by: Deluan <deluan@navidrome.org>
* lastfm album.getInfo, getAlbuminfo(2) endpoints
* ... for description and reduce not found log level
* address first comments
* return all images
* Update migration timestamp
* Handle a few edge cases
* Add CoverArtPriority option to retrieve albumart from external sources
* Make agents methods more descriptive
* Use Last.fm name consistently
Co-authored-by: Deluan <deluan@navidrome.org>
* add internet radio support
* Add dynamic sidebar icon to Radios
* Fix typos
* Make URL suffix consistent
* Fix typo
* address feedback
* Don't need to preload when playing Internet Radios
* Reorder migration, or else it won't be applied
* Make Radio list view responsive
Also added filter by name, removed RadioActions and RadioContextMenu, and added a default radio icon, in case of favicon is not available.
* Simplify StreamField usage
* fix button, hide progress on mobile
* use js styles over index.css
Co-authored-by: Deluan <deluan@navidrome.org>