Deluan Quintão 1afcf7775b
feat: add ISRC matching for similar songs (#4946)
* feat: add ISRC support to similar songs matching and plugin interface

Add ISRC (International Standard Recording Code) as a high-priority
identifier in the provider matching algorithm, alongside MBID. The
matching pipeline now uses four strategies in priority order:
ID > MBID > ISRC > Title+Artist fuzzy match.

- Add ISRC field to agents.Song struct
- Add ISRC field to plugin capability SongRef (Go, Rust PDKs)
- Add loadTracksByISRC using json_tree query on tags column
- Integrate ISRC into matchSongsToLibrary, selectBestMatchingSongs,
  and buildTitleQueries

https://claude.ai/code/session_01Dd4mTq1VQZag4RNjCVusiF

* chore: regenerate plugin schema after ISRC addition

Run `make gen` to update the generated YAML schema for the
metadata agent capability with the new ISRC field on SongRef.

https://claude.ai/code/session_01Dd4mTq1VQZag4RNjCVusiF

* feat(mediafile): add GetAllByTags method to MediaFileRepository for tag-based retrieval

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

* feat(provider): speed up track matching by incorporating prior matches in ISRC and MBID lookups

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

---------

Signed-off-by: Deluan <deluan@navidrome.org>
Co-authored-by: Claude <noreply@anthropic.com>
2026-01-27 14:54:29 -05:00

622 lines
16 KiB
Go

// Code generated by ndpgen. DO NOT EDIT.
//
// This file contains export wrappers for the MetadataAgent capability.
// It is intended for use in Navidrome plugins built with TinyGo.
//
//go:build wasip1
package metadata
import (
"github.com/navidrome/navidrome/plugins/pdk/go/pdk"
)
// AlbumImagesResponse is the response for GetAlbumImages.
type AlbumImagesResponse struct {
// Images is the list of album images.
Images []ImageInfo `json:"images"`
}
// AlbumInfoResponse is the response for GetAlbumInfo.
type AlbumInfoResponse struct {
// Name is the album name.
Name string `json:"name"`
// MBID is the MusicBrainz ID for the album.
MBID string `json:"mbid"`
// Description is the album description/notes.
Description string `json:"description"`
// URL is the external URL for the album.
URL string `json:"url"`
}
// AlbumRequest is the common request for album-related functions.
type AlbumRequest struct {
// Name is the album name.
Name string `json:"name"`
// Artist is the album artist name.
Artist string `json:"artist"`
// MBID is the MusicBrainz ID for the album (if known).
MBID string `json:"mbid,omitempty"`
}
// ArtistBiographyResponse is the response for GetArtistBiography.
type ArtistBiographyResponse struct {
// Biography is the artist biography text.
Biography string `json:"biography"`
}
// ArtistImagesResponse is the response for GetArtistImages.
type ArtistImagesResponse struct {
// Images is the list of artist images.
Images []ImageInfo `json:"images"`
}
// ArtistMBIDRequest is the request for GetArtistMBID.
type ArtistMBIDRequest struct {
// ID is the internal Navidrome artist ID.
ID string `json:"id"`
// Name is the artist name.
Name string `json:"name"`
}
// ArtistMBIDResponse is the response for GetArtistMBID.
type ArtistMBIDResponse struct {
// MBID is the MusicBrainz ID for the artist.
MBID string `json:"mbid"`
}
// 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"`
}
// ArtistRequest is the common request for artist-related functions.
type ArtistRequest struct {
// ID is the internal Navidrome artist ID.
ID string `json:"id"`
// Name is the artist name.
Name string `json:"name"`
// MBID is the MusicBrainz ID for the artist (if known).
MBID string `json:"mbid,omitempty"`
}
// ArtistURLResponse is the response for GetArtistURL.
type ArtistURLResponse struct {
// URL is the external URL for the artist.
URL string `json:"url"`
}
// ImageInfo represents an image with URL and size.
type ImageInfo struct {
// URL is the URL of the image.
URL string `json:"url"`
// Size is the size of the image in pixels (width or height).
Size int32 `json:"size"`
}
// SimilarArtistsRequest is the request for GetSimilarArtists.
type SimilarArtistsRequest struct {
// ID is the internal Navidrome artist ID.
ID string `json:"id"`
// Name is the artist name.
Name string `json:"name"`
// MBID is the MusicBrainz ID for the artist (if known).
MBID string `json:"mbid,omitempty"`
// Limit is the maximum number of similar artists to return.
Limit int32 `json:"limit"`
}
// SimilarArtistsResponse is the response for GetSimilarArtists.
type SimilarArtistsResponse struct {
// Artists is the list of similar artists.
Artists []ArtistRef `json:"artists"`
}
// SimilarSongsByAlbumRequest is the request for GetSimilarSongsByAlbum.
type SimilarSongsByAlbumRequest struct {
// ID is the internal Navidrome album ID.
ID string `json:"id"`
// Name is the album name.
Name string `json:"name"`
// Artist is the album artist name.
Artist string `json:"artist"`
// MBID is the MusicBrainz release ID (if known).
MBID string `json:"mbid,omitempty"`
// Count is the maximum number of similar songs to return.
Count int32 `json:"count"`
}
// SimilarSongsByArtistRequest is the request for GetSimilarSongsByArtist.
type SimilarSongsByArtistRequest struct {
// ID is the internal Navidrome artist ID.
ID string `json:"id"`
// Name is the artist name.
Name string `json:"name"`
// MBID is the MusicBrainz artist ID (if known).
MBID string `json:"mbid,omitempty"`
// Count is the maximum number of similar songs to return.
Count int32 `json:"count"`
}
// SimilarSongsByTrackRequest is the request for GetSimilarSongsByTrack.
type SimilarSongsByTrackRequest struct {
// ID is the internal Navidrome mediafile ID.
ID string `json:"id"`
// Name is the track title.
Name string `json:"name"`
// Artist is the artist name.
Artist string `json:"artist"`
// MBID is the MusicBrainz recording ID (if known).
MBID string `json:"mbid,omitempty"`
// Count is the maximum number of similar songs to return.
Count int32 `json:"count"`
}
// SimilarSongsResponse is the response for GetSimilarSongsBy* functions.
type SimilarSongsResponse struct {
// Songs is the list of similar songs.
Songs []SongRef `json:"songs"`
}
// SongRef is a reference to a song with metadata for matching.
type SongRef struct {
// ID is the internal Navidrome mediafile ID (if known).
ID string `json:"id,omitempty"`
// Name is the song name.
Name string `json:"name"`
// MBID is the MusicBrainz ID for the song.
MBID string `json:"mbid,omitempty"`
// ISRC is the International Standard Recording Code for the song.
ISRC string `json:"isrc,omitempty"`
// Artist is the artist name.
Artist string `json:"artist,omitempty"`
// ArtistMBID is the MusicBrainz artist ID.
ArtistMBID string `json:"artistMbid,omitempty"`
// Album is the album name.
Album string `json:"album,omitempty"`
// AlbumMBID is the MusicBrainz release ID.
AlbumMBID string `json:"albumMbid,omitempty"`
// Duration is the song duration in seconds.
Duration float32 `json:"duration,omitempty"`
}
// TopSongsRequest is the request for GetArtistTopSongs.
type TopSongsRequest struct {
// ID is the internal Navidrome artist ID.
ID string `json:"id"`
// Name is the artist name.
Name string `json:"name"`
// MBID is the MusicBrainz ID for the artist (if known).
MBID string `json:"mbid,omitempty"`
// Count is the maximum number of top songs to return.
Count int32 `json:"count"`
}
// TopSongsResponse is the response for GetArtistTopSongs.
type TopSongsResponse struct {
// Songs is the list of top songs.
Songs []SongRef `json:"songs"`
}
// Metadata is the marker interface for metadata plugins.
// Implement one or more of the provider interfaces below.
// MetadataAgent provides artist and album metadata retrieval.
// This capability allows plugins to provide external metadata for artists and albums,
// such as biographies, images, similar artists, and top songs.
//
// Plugins implementing this capability can choose which methods to implement.
// Each method is optional - plugins only need to provide the functionality they support.
type Metadata interface{}
// ArtistMBIDProvider provides the GetArtistMBID function.
type ArtistMBIDProvider interface {
GetArtistMBID(ArtistMBIDRequest) (*ArtistMBIDResponse, error)
}
// ArtistURLProvider provides the GetArtistURL function.
type ArtistURLProvider interface {
GetArtistURL(ArtistRequest) (*ArtistURLResponse, error)
}
// ArtistBiographyProvider provides the GetArtistBiography function.
type ArtistBiographyProvider interface {
GetArtistBiography(ArtistRequest) (*ArtistBiographyResponse, error)
}
// SimilarArtistsProvider provides the GetSimilarArtists function.
type SimilarArtistsProvider interface {
GetSimilarArtists(SimilarArtistsRequest) (*SimilarArtistsResponse, error)
}
// ArtistImagesProvider provides the GetArtistImages function.
type ArtistImagesProvider interface {
GetArtistImages(ArtistRequest) (*ArtistImagesResponse, error)
}
// ArtistTopSongsProvider provides the GetArtistTopSongs function.
type ArtistTopSongsProvider interface {
GetArtistTopSongs(TopSongsRequest) (*TopSongsResponse, error)
}
// AlbumInfoProvider provides the GetAlbumInfo function.
type AlbumInfoProvider interface {
GetAlbumInfo(AlbumRequest) (*AlbumInfoResponse, error)
}
// AlbumImagesProvider provides the GetAlbumImages function.
type AlbumImagesProvider interface {
GetAlbumImages(AlbumRequest) (*AlbumImagesResponse, error)
}
// SimilarSongsByTrackProvider provides the GetSimilarSongsByTrack function.
type SimilarSongsByTrackProvider interface {
GetSimilarSongsByTrack(SimilarSongsByTrackRequest) (*SimilarSongsResponse, error)
}
// SimilarSongsByAlbumProvider provides the GetSimilarSongsByAlbum function.
type SimilarSongsByAlbumProvider interface {
GetSimilarSongsByAlbum(SimilarSongsByAlbumRequest) (*SimilarSongsResponse, error)
}
// SimilarSongsByArtistProvider provides the GetSimilarSongsByArtist function.
type SimilarSongsByArtistProvider interface {
GetSimilarSongsByArtist(SimilarSongsByArtistRequest) (*SimilarSongsResponse, error)
} // Internal implementation holders
var (
artistMBIDImpl func(ArtistMBIDRequest) (*ArtistMBIDResponse, error)
artistURLImpl func(ArtistRequest) (*ArtistURLResponse, error)
artistBiographyImpl func(ArtistRequest) (*ArtistBiographyResponse, error)
similarArtistsImpl func(SimilarArtistsRequest) (*SimilarArtistsResponse, error)
artistImagesImpl func(ArtistRequest) (*ArtistImagesResponse, error)
artistTopSongsImpl func(TopSongsRequest) (*TopSongsResponse, error)
albumInfoImpl func(AlbumRequest) (*AlbumInfoResponse, error)
albumImagesImpl func(AlbumRequest) (*AlbumImagesResponse, error)
similarSongsByTrackImpl func(SimilarSongsByTrackRequest) (*SimilarSongsResponse, error)
similarSongsByAlbumImpl func(SimilarSongsByAlbumRequest) (*SimilarSongsResponse, error)
similarSongsByArtistImpl func(SimilarSongsByArtistRequest) (*SimilarSongsResponse, error)
)
// Register registers a metadata implementation.
// The implementation is checked for optional provider interfaces.
func Register(impl Metadata) {
if p, ok := impl.(ArtistMBIDProvider); ok {
artistMBIDImpl = p.GetArtistMBID
}
if p, ok := impl.(ArtistURLProvider); ok {
artistURLImpl = p.GetArtistURL
}
if p, ok := impl.(ArtistBiographyProvider); ok {
artistBiographyImpl = p.GetArtistBiography
}
if p, ok := impl.(SimilarArtistsProvider); ok {
similarArtistsImpl = p.GetSimilarArtists
}
if p, ok := impl.(ArtistImagesProvider); ok {
artistImagesImpl = p.GetArtistImages
}
if p, ok := impl.(ArtistTopSongsProvider); ok {
artistTopSongsImpl = p.GetArtistTopSongs
}
if p, ok := impl.(AlbumInfoProvider); ok {
albumInfoImpl = p.GetAlbumInfo
}
if p, ok := impl.(AlbumImagesProvider); ok {
albumImagesImpl = p.GetAlbumImages
}
if p, ok := impl.(SimilarSongsByTrackProvider); ok {
similarSongsByTrackImpl = p.GetSimilarSongsByTrack
}
if p, ok := impl.(SimilarSongsByAlbumProvider); ok {
similarSongsByAlbumImpl = p.GetSimilarSongsByAlbum
}
if p, ok := impl.(SimilarSongsByArtistProvider); ok {
similarSongsByArtistImpl = p.GetSimilarSongsByArtist
}
}
// 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_get_artist_mbid
func _NdGetArtistMbid() int32 {
if artistMBIDImpl == nil {
// Return standard code - host will skip this plugin gracefully
return NotImplementedCode
}
var input ArtistMBIDRequest
if err := pdk.InputJSON(&input); err != nil {
pdk.SetError(err)
return -1
}
output, err := artistMBIDImpl(input)
if err != nil {
pdk.SetError(err)
return -1
}
if err := pdk.OutputJSON(output); err != nil {
pdk.SetError(err)
return -1
}
return 0
}
//go:wasmexport nd_get_artist_url
func _NdGetArtistUrl() int32 {
if artistURLImpl == nil {
// Return standard code - host will skip this plugin gracefully
return NotImplementedCode
}
var input ArtistRequest
if err := pdk.InputJSON(&input); err != nil {
pdk.SetError(err)
return -1
}
output, err := artistURLImpl(input)
if err != nil {
pdk.SetError(err)
return -1
}
if err := pdk.OutputJSON(output); err != nil {
pdk.SetError(err)
return -1
}
return 0
}
//go:wasmexport nd_get_artist_biography
func _NdGetArtistBiography() int32 {
if artistBiographyImpl == nil {
// Return standard code - host will skip this plugin gracefully
return NotImplementedCode
}
var input ArtistRequest
if err := pdk.InputJSON(&input); err != nil {
pdk.SetError(err)
return -1
}
output, err := artistBiographyImpl(input)
if err != nil {
pdk.SetError(err)
return -1
}
if err := pdk.OutputJSON(output); err != nil {
pdk.SetError(err)
return -1
}
return 0
}
//go:wasmexport nd_get_similar_artists
func _NdGetSimilarArtists() int32 {
if similarArtistsImpl == nil {
// Return standard code - host will skip this plugin gracefully
return NotImplementedCode
}
var input SimilarArtistsRequest
if err := pdk.InputJSON(&input); err != nil {
pdk.SetError(err)
return -1
}
output, err := similarArtistsImpl(input)
if err != nil {
pdk.SetError(err)
return -1
}
if err := pdk.OutputJSON(output); err != nil {
pdk.SetError(err)
return -1
}
return 0
}
//go:wasmexport nd_get_artist_images
func _NdGetArtistImages() int32 {
if artistImagesImpl == nil {
// Return standard code - host will skip this plugin gracefully
return NotImplementedCode
}
var input ArtistRequest
if err := pdk.InputJSON(&input); err != nil {
pdk.SetError(err)
return -1
}
output, err := artistImagesImpl(input)
if err != nil {
pdk.SetError(err)
return -1
}
if err := pdk.OutputJSON(output); err != nil {
pdk.SetError(err)
return -1
}
return 0
}
//go:wasmexport nd_get_artist_top_songs
func _NdGetArtistTopSongs() int32 {
if artistTopSongsImpl == nil {
// Return standard code - host will skip this plugin gracefully
return NotImplementedCode
}
var input TopSongsRequest
if err := pdk.InputJSON(&input); err != nil {
pdk.SetError(err)
return -1
}
output, err := artistTopSongsImpl(input)
if err != nil {
pdk.SetError(err)
return -1
}
if err := pdk.OutputJSON(output); err != nil {
pdk.SetError(err)
return -1
}
return 0
}
//go:wasmexport nd_get_album_info
func _NdGetAlbumInfo() int32 {
if albumInfoImpl == nil {
// Return standard code - host will skip this plugin gracefully
return NotImplementedCode
}
var input AlbumRequest
if err := pdk.InputJSON(&input); err != nil {
pdk.SetError(err)
return -1
}
output, err := albumInfoImpl(input)
if err != nil {
pdk.SetError(err)
return -1
}
if err := pdk.OutputJSON(output); err != nil {
pdk.SetError(err)
return -1
}
return 0
}
//go:wasmexport nd_get_album_images
func _NdGetAlbumImages() int32 {
if albumImagesImpl == nil {
// Return standard code - host will skip this plugin gracefully
return NotImplementedCode
}
var input AlbumRequest
if err := pdk.InputJSON(&input); err != nil {
pdk.SetError(err)
return -1
}
output, err := albumImagesImpl(input)
if err != nil {
pdk.SetError(err)
return -1
}
if err := pdk.OutputJSON(output); err != nil {
pdk.SetError(err)
return -1
}
return 0
}
//go:wasmexport nd_get_similar_songs_by_track
func _NdGetSimilarSongsByTrack() int32 {
if similarSongsByTrackImpl == nil {
// Return standard code - host will skip this plugin gracefully
return NotImplementedCode
}
var input SimilarSongsByTrackRequest
if err := pdk.InputJSON(&input); err != nil {
pdk.SetError(err)
return -1
}
output, err := similarSongsByTrackImpl(input)
if err != nil {
pdk.SetError(err)
return -1
}
if err := pdk.OutputJSON(output); err != nil {
pdk.SetError(err)
return -1
}
return 0
}
//go:wasmexport nd_get_similar_songs_by_album
func _NdGetSimilarSongsByAlbum() int32 {
if similarSongsByAlbumImpl == nil {
// Return standard code - host will skip this plugin gracefully
return NotImplementedCode
}
var input SimilarSongsByAlbumRequest
if err := pdk.InputJSON(&input); err != nil {
pdk.SetError(err)
return -1
}
output, err := similarSongsByAlbumImpl(input)
if err != nil {
pdk.SetError(err)
return -1
}
if err := pdk.OutputJSON(output); err != nil {
pdk.SetError(err)
return -1
}
return 0
}
//go:wasmexport nd_get_similar_songs_by_artist
func _NdGetSimilarSongsByArtist() int32 {
if similarSongsByArtistImpl == nil {
// Return standard code - host will skip this plugin gracefully
return NotImplementedCode
}
var input SimilarSongsByArtistRequest
if err := pdk.InputJSON(&input); err != nil {
pdk.SetError(err)
return -1
}
output, err := similarSongsByArtistImpl(input)
if err != nil {
pdk.SetError(err)
return -1
}
if err := pdk.OutputJSON(output); err != nil {
pdk.SetError(err)
return -1
}
return 0
}