feat(plugins): add lyrics provider plugin capability (#5126)

* 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>
This commit is contained in:
Deluan Quintão
2026-03-03 15:48:39 -05:00
committed by GitHub
parent eeb1bd5f41
commit f03ca44a8e
33 changed files with 930 additions and 28 deletions
+118
View File
@@ -0,0 +1,118 @@
// Code generated by ndpgen. DO NOT EDIT.
//
// This file contains export wrappers for the Lyrics capability.
// It is intended for use in Navidrome plugins built with TinyGo.
//
//go:build wasip1
package lyrics
import (
"github.com/navidrome/navidrome/plugins/pdk/go/pdk"
)
// ArtistRef is a reference to an artist with name and optional MBID.
type ArtistRef struct {
// ID is the internal Navidrome artist ID (if known).
ID string `json:"id,omitempty"`
// Name is the artist name.
Name string `json:"name"`
// MBID is the MusicBrainz ID for the artist.
MBID string `json:"mbid,omitempty"`
}
// GetLyricsRequest contains the track information for lyrics lookup.
type GetLyricsRequest struct {
Track TrackInfo `json:"track"`
}
// GetLyricsResponse contains the lyrics returned by the plugin.
type GetLyricsResponse struct {
Lyrics []LyricsText `json:"lyrics"`
}
// LyricsText represents a single set of lyrics in raw text format.
// Text can be plain text or LRC format — Navidrome will parse it.
type LyricsText struct {
Lang string `json:"lang,omitempty"`
Text string `json:"text"`
}
// TrackInfo contains track metadata.
type TrackInfo struct {
// ID is the internal Navidrome track ID.
ID string `json:"id"`
// Title is the track title.
Title string `json:"title"`
// Album is the album name.
Album string `json:"album"`
// Artist is the formatted artist name for display (e.g., "Artist1 • Artist2").
Artist string `json:"artist"`
// AlbumArtist is the formatted album artist name for display.
AlbumArtist string `json:"albumArtist"`
// Artists is the list of track artists.
Artists []ArtistRef `json:"artists"`
// AlbumArtists is the list of album artists.
AlbumArtists []ArtistRef `json:"albumArtists"`
// Duration is the track duration in seconds.
Duration float32 `json:"duration"`
// TrackNumber is the track number on the album.
TrackNumber int32 `json:"trackNumber"`
// DiscNumber is the disc number.
DiscNumber int32 `json:"discNumber"`
// MBZRecordingID is the MusicBrainz recording ID.
MBZRecordingID string `json:"mbzRecordingId,omitempty"`
// MBZAlbumID is the MusicBrainz album/release ID.
MBZAlbumID string `json:"mbzAlbumId,omitempty"`
// MBZReleaseGroupID is the MusicBrainz release group ID.
MBZReleaseGroupID string `json:"mbzReleaseGroupId,omitempty"`
// MBZReleaseTrackID is the MusicBrainz release track ID.
MBZReleaseTrackID string `json:"mbzReleaseTrackId,omitempty"`
}
// Lyrics requires all methods to be implemented.
// Lyrics provides lyrics for a given track from external sources.
type Lyrics interface {
// GetLyrics
GetLyrics(GetLyricsRequest) (GetLyricsResponse, error)
} // Internal implementation holders
var (
lyricsImpl func(GetLyricsRequest) (GetLyricsResponse, error)
)
// Register registers a lyrics implementation.
// All methods are required.
func Register(impl Lyrics) {
lyricsImpl = impl.GetLyrics
}
// NotImplementedCode is the standard return code for unimplemented functions.
// The host recognizes this and skips the plugin gracefully.
const NotImplementedCode int32 = -2
//go:wasmexport nd_lyrics_get_lyrics
func _NdLyricsGetLyrics() int32 {
if lyricsImpl == nil {
// Return standard code - host will skip this plugin gracefully
return NotImplementedCode
}
var input GetLyricsRequest
if err := pdk.InputJSON(&input); err != nil {
pdk.SetError(err)
return -1
}
output, err := lyricsImpl(input)
if err != nil {
pdk.SetError(err)
return -1
}
if err := pdk.OutputJSON(output); err != nil {
pdk.SetError(err)
return -1
}
return 0
}
+82
View File
@@ -0,0 +1,82 @@
// Code generated by ndpgen. DO NOT EDIT.
//
// This file provides stub implementations for non-WASM platforms.
// It allows Go plugins to compile and run tests outside of WASM,
// but the actual functionality is only available in WASM builds.
//
//go:build !wasip1
package lyrics
// ArtistRef is a reference to an artist with name and optional MBID.
type ArtistRef struct {
// ID is the internal Navidrome artist ID (if known).
ID string `json:"id,omitempty"`
// Name is the artist name.
Name string `json:"name"`
// MBID is the MusicBrainz ID for the artist.
MBID string `json:"mbid,omitempty"`
}
// GetLyricsRequest contains the track information for lyrics lookup.
type GetLyricsRequest struct {
Track TrackInfo `json:"track"`
}
// GetLyricsResponse contains the lyrics returned by the plugin.
type GetLyricsResponse struct {
Lyrics []LyricsText `json:"lyrics"`
}
// LyricsText represents a single set of lyrics in raw text format.
// Text can be plain text or LRC format — Navidrome will parse it.
type LyricsText struct {
Lang string `json:"lang,omitempty"`
Text string `json:"text"`
}
// TrackInfo contains track metadata.
type TrackInfo struct {
// ID is the internal Navidrome track ID.
ID string `json:"id"`
// Title is the track title.
Title string `json:"title"`
// Album is the album name.
Album string `json:"album"`
// Artist is the formatted artist name for display (e.g., "Artist1 • Artist2").
Artist string `json:"artist"`
// AlbumArtist is the formatted album artist name for display.
AlbumArtist string `json:"albumArtist"`
// Artists is the list of track artists.
Artists []ArtistRef `json:"artists"`
// AlbumArtists is the list of album artists.
AlbumArtists []ArtistRef `json:"albumArtists"`
// Duration is the track duration in seconds.
Duration float32 `json:"duration"`
// TrackNumber is the track number on the album.
TrackNumber int32 `json:"trackNumber"`
// DiscNumber is the disc number.
DiscNumber int32 `json:"discNumber"`
// MBZRecordingID is the MusicBrainz recording ID.
MBZRecordingID string `json:"mbzRecordingId,omitempty"`
// MBZAlbumID is the MusicBrainz album/release ID.
MBZAlbumID string `json:"mbzAlbumId,omitempty"`
// MBZReleaseGroupID is the MusicBrainz release group ID.
MBZReleaseGroupID string `json:"mbzReleaseGroupId,omitempty"`
// MBZReleaseTrackID is the MusicBrainz release track ID.
MBZReleaseTrackID string `json:"mbzReleaseTrackId,omitempty"`
}
// Lyrics requires all methods to be implemented.
// Lyrics provides lyrics for a given track from external sources.
type Lyrics interface {
// GetLyrics
GetLyrics(GetLyricsRequest) (GetLyricsResponse, error)
}
// NotImplementedCode is the standard return code for unimplemented functions.
const NotImplementedCode int32 = -2
// Register is a no-op on non-WASM platforms.
// This stub allows code to compile outside of WASM.
func Register(_ Lyrics) {}
+1 -1
View File
@@ -62,7 +62,7 @@ type ScrobbleRequest struct {
Timestamp int64 `json:"timestamp"`
}
// TrackInfo contains track metadata for scrobbling.
// TrackInfo contains track metadata.
type TrackInfo struct {
// ID is the internal Navidrome track ID.
ID string `json:"id"`
+1 -1
View File
@@ -59,7 +59,7 @@ type ScrobbleRequest struct {
Timestamp int64 `json:"timestamp"`
}
// TrackInfo contains track metadata for scrobbling.
// TrackInfo contains track metadata.
type TrackInfo struct {
// ID is the internal Navidrome track ID.
ID string `json:"id"`