navidrome/plugins/capabilities
Deluan Quintão 259c1a9484
feat(subsonic): add sonicSimilarity extension as plugin capability (#5419)
* 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>
2026-04-27 17:50:09 -04:00
..
doc.go feat(plugins): New Plugin System with multi-language PDK support (#4833) 2026-01-14 19:22:48 -05:00
lifecycle.go feat(plugins): New Plugin System with multi-language PDK support (#4833) 2026-01-14 19:22:48 -05:00
lifecycle.yaml feat(plugins): New Plugin System with multi-language PDK support (#4833) 2026-01-14 19:22:48 -05:00
lyrics.go feat(plugins): add lyrics provider plugin capability (#5126) 2026-03-03 15:48:39 -05:00
lyrics.yaml feat(subsonic): add sonicSimilarity extension as plugin capability (#5419) 2026-04-27 17:50:09 -04:00
metadata_agent.go feat: add ISRC matching for similar songs (#4946) 2026-01-27 14:54:29 -05:00
metadata_agent.yaml feat: add ISRC matching for similar songs (#4946) 2026-01-27 14:54:29 -05:00
README.md feat(plugins): New Plugin System with multi-language PDK support (#4833) 2026-01-14 19:22:48 -05:00
scheduler_callback.go feat(plugins): New Plugin System with multi-language PDK support (#4833) 2026-01-14 19:22:48 -05:00
scheduler_callback.yaml feat(plugins): New Plugin System with multi-language PDK support (#4833) 2026-01-14 19:22:48 -05:00
scrobbler.go feat(plugins): add LibraryID to TrackInfo 2026-04-26 16:36:57 -04:00
scrobbler.yaml feat(subsonic): add sonicSimilarity extension as plugin capability (#5419) 2026-04-27 17:50:09 -04:00
sonic_similarity.go feat(subsonic): add sonicSimilarity extension as plugin capability (#5419) 2026-04-27 17:50:09 -04:00
sonic_similarity.yaml feat(subsonic): add sonicSimilarity extension as plugin capability (#5419) 2026-04-27 17:50:09 -04:00
taskworker.go feat(plugins): add TaskQueue host service for persistent background task queues (#5116) 2026-03-03 13:48:49 -05:00
taskworker.yaml fix(plugins): update payload type to string with byte format for task data 2026-03-03 13:54:43 -05:00
websocket_callback.go feat(plugins): change websockets Data field type to []byte for binary support 2026-03-02 16:38:00 -05:00
websocket_callback.yaml feat(plugins): change websockets Data field type to []byte for binary support 2026-03-02 16:38:00 -05:00

Navidrome Plugin Capabilities

This directory contains the Go interface definitions for Navidrome plugin capabilities. These interfaces are the source of truth for plugin development and are used to generate:

  1. Go PDK packages (pdk/go/*/) - Type-safe wrappers for Go plugin developers
  2. Rust PDK crates (pdk/rust/*/) - Type-safe wrappers for Rust plugin developers
  3. XTP YAML schemas (*.yaml) - Schema files for other Extism plugin languages (TypeScript, Python, C#, Zig, C++, ...)

For Go Plugin Developers

Go developers should use the generated PDK packages in plugins/pdk/go/. See the example Go plugins in plugins/examples/ for usage patterns.

For Rust Plugin Developers

Rust developers should use the generated PDK crate in plugins/pdk/rust/nd-pdk. See the example Rust plugins in plugins/examples for usage patterns.

For Non-Go Plugin Developers

If you're developing plugins in other languages (TypeScript, Rust, Python, C#, Zig, C++), you can use the XTP CLI to generate type-safe bindings from the YAML schema files in this directory.

Prerequisites

Install the XTP CLI:

# macOS
brew install dylibso/tap/xtp

# Other platforms - see https://docs.xtp.dylibso.com/docs/cli
curl https://static.dylibso.com/cli/install.sh | bash

Generating Plugin Scaffolding

Use the XTP CLI to generate plugin boilerplate from any capability schema:

# TypeScript
xtp plugin init --schema-file plugins/capabilities/metadata_agent.yaml \
    --template typescript --path my-plugin

# Rust
xtp plugin init --schema-file plugins/capabilities/scrobbler.yaml \
    --template rust --path my-plugin

# Python
xtp plugin init --schema-file plugins/capabilities/lifecycle.yaml \
    --template python --path my-plugin

# C#
xtp plugin init --schema-file plugins/capabilities/scheduler_callback.yaml \
    --template csharp --path my-plugin

# Go (alternative to using the PDK packages)
xtp plugin init --schema-file plugins/capabilities/websocket_callback.yaml \
    --template go --path my-plugin

Available Capabilities

Capability Schema File Description
Metadata Agent metadata_agent.yaml Fetch artist biographies, album images, and similar artists
Scrobbler scrobbler.yaml Report listening activity to external services
Lifecycle lifecycle.yaml Plugin initialization callbacks
Scheduler Callback scheduler_callback.yaml Scheduled task execution
WebSocket Callback websocket_callback.yaml Real-time WebSocket message handling

Building Your Plugin

After generating the scaffolding, implement the required functions and build your plugin as a WebAssembly module. The exact build process depends on your chosen language - see the Extism PDK documentation for language-specific guides.

XTP Schema Generation

The YAML schemas in this package are automatically generated from the capability Go interfaces using ndpgen. To regenerate the schemas after modifying the interfaces, run:

cd plugins/cmd/ndpgen && go run . -schemas -input=./plugins/capabilities

Resources