Files
navidrome/plugins/pdk/rust/nd-pdk-capabilities/src/metadata.rs
T
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

540 lines
20 KiB
Rust

// 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 extism-pdk.
use serde::{Deserialize, Serialize};
// Helper functions for skip_serializing_if with numeric types
#[allow(dead_code)]
fn is_zero_i32(value: &i32) -> bool { *value == 0 }
#[allow(dead_code)]
fn is_zero_u32(value: &u32) -> bool { *value == 0 }
#[allow(dead_code)]
fn is_zero_i64(value: &i64) -> bool { *value == 0 }
#[allow(dead_code)]
fn is_zero_u64(value: &u64) -> bool { *value == 0 }
#[allow(dead_code)]
fn is_zero_f32(value: &f32) -> bool { *value == 0.0 }
#[allow(dead_code)]
fn is_zero_f64(value: &f64) -> bool { *value == 0.0 }
/// AlbumImagesResponse is the response for GetAlbumImages.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AlbumImagesResponse {
/// Images is the list of album images.
#[serde(default)]
pub images: Vec<ImageInfo>,
}
/// AlbumInfoResponse is the response for GetAlbumInfo.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AlbumInfoResponse {
/// Name is the album name.
#[serde(default)]
pub name: String,
/// MBID is the MusicBrainz ID for the album.
#[serde(default)]
pub mbid: String,
/// Description is the album description/notes.
#[serde(default)]
pub description: String,
/// URL is the external URL for the album.
#[serde(default)]
pub url: String,
}
/// AlbumRequest is the common request for album-related functions.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AlbumRequest {
/// Name is the album name.
#[serde(default)]
pub name: String,
/// Artist is the album artist name.
#[serde(default)]
pub artist: String,
/// MBID is the MusicBrainz ID for the album (if known).
#[serde(default, skip_serializing_if = "String::is_empty")]
pub mbid: String,
}
/// ArtistBiographyResponse is the response for GetArtistBiography.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ArtistBiographyResponse {
/// Biography is the artist biography text.
#[serde(default)]
pub biography: String,
}
/// ArtistImagesResponse is the response for GetArtistImages.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ArtistImagesResponse {
/// Images is the list of artist images.
#[serde(default)]
pub images: Vec<ImageInfo>,
}
/// ArtistMBIDRequest is the request for GetArtistMBID.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ArtistMBIDRequest {
/// ID is the internal Navidrome artist ID.
#[serde(default)]
pub id: String,
/// Name is the artist name.
#[serde(default)]
pub name: String,
}
/// ArtistMBIDResponse is the response for GetArtistMBID.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ArtistMBIDResponse {
/// MBID is the MusicBrainz ID for the artist.
#[serde(default)]
pub mbid: String,
}
/// ArtistRef is a reference to an artist with name and optional MBID.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ArtistRef {
/// ID is the internal Navidrome artist ID (if known).
#[serde(default, skip_serializing_if = "String::is_empty")]
pub id: String,
/// Name is the artist name.
#[serde(default)]
pub name: String,
/// MBID is the MusicBrainz ID for the artist.
#[serde(default, skip_serializing_if = "String::is_empty")]
pub mbid: String,
}
/// ArtistRequest is the common request for artist-related functions.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ArtistRequest {
/// ID is the internal Navidrome artist ID.
#[serde(default)]
pub id: String,
/// Name is the artist name.
#[serde(default)]
pub name: String,
/// MBID is the MusicBrainz ID for the artist (if known).
#[serde(default, skip_serializing_if = "String::is_empty")]
pub mbid: String,
}
/// ArtistURLResponse is the response for GetArtistURL.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ArtistURLResponse {
/// URL is the external URL for the artist.
#[serde(default)]
pub url: String,
}
/// ImageInfo represents an image with URL and size.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ImageInfo {
/// URL is the URL of the image.
#[serde(default)]
pub url: String,
/// Size is the size of the image in pixels (width or height).
#[serde(default)]
pub size: i32,
}
/// SimilarArtistsRequest is the request for GetSimilarArtists.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SimilarArtistsRequest {
/// ID is the internal Navidrome artist ID.
#[serde(default)]
pub id: String,
/// Name is the artist name.
#[serde(default)]
pub name: String,
/// MBID is the MusicBrainz ID for the artist (if known).
#[serde(default, skip_serializing_if = "String::is_empty")]
pub mbid: String,
/// Limit is the maximum number of similar artists to return.
#[serde(default)]
pub limit: i32,
}
/// SimilarArtistsResponse is the response for GetSimilarArtists.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SimilarArtistsResponse {
/// Artists is the list of similar artists.
#[serde(default)]
pub artists: Vec<ArtistRef>,
}
/// SimilarSongsByAlbumRequest is the request for GetSimilarSongsByAlbum.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SimilarSongsByAlbumRequest {
/// ID is the internal Navidrome album ID.
#[serde(default)]
pub id: String,
/// Name is the album name.
#[serde(default)]
pub name: String,
/// Artist is the album artist name.
#[serde(default)]
pub artist: String,
/// MBID is the MusicBrainz release ID (if known).
#[serde(default, skip_serializing_if = "String::is_empty")]
pub mbid: String,
/// Count is the maximum number of similar songs to return.
#[serde(default)]
pub count: i32,
}
/// SimilarSongsByArtistRequest is the request for GetSimilarSongsByArtist.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SimilarSongsByArtistRequest {
/// ID is the internal Navidrome artist ID.
#[serde(default)]
pub id: String,
/// Name is the artist name.
#[serde(default)]
pub name: String,
/// MBID is the MusicBrainz artist ID (if known).
#[serde(default, skip_serializing_if = "String::is_empty")]
pub mbid: String,
/// Count is the maximum number of similar songs to return.
#[serde(default)]
pub count: i32,
}
/// SimilarSongsByTrackRequest is the request for GetSimilarSongsByTrack.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SimilarSongsByTrackRequest {
/// ID is the internal Navidrome mediafile ID.
#[serde(default)]
pub id: String,
/// Name is the track title.
#[serde(default)]
pub name: String,
/// Artist is the artist name.
#[serde(default)]
pub artist: String,
/// MBID is the MusicBrainz recording ID (if known).
#[serde(default, skip_serializing_if = "String::is_empty")]
pub mbid: String,
/// Count is the maximum number of similar songs to return.
#[serde(default)]
pub count: i32,
}
/// SimilarSongsResponse is the response for GetSimilarSongsBy* functions.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SimilarSongsResponse {
/// Songs is the list of similar songs.
#[serde(default)]
pub songs: Vec<SongRef>,
}
/// SongRef is a reference to a song with metadata for matching.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SongRef {
/// ID is the internal Navidrome mediafile ID (if known).
#[serde(default, skip_serializing_if = "String::is_empty")]
pub id: String,
/// Name is the song name.
#[serde(default)]
pub name: String,
/// MBID is the MusicBrainz ID for the song.
#[serde(default, skip_serializing_if = "String::is_empty")]
pub mbid: String,
/// ISRC is the International Standard Recording Code for the song.
#[serde(default, skip_serializing_if = "String::is_empty")]
pub isrc: String,
/// Artist is the artist name.
#[serde(default, skip_serializing_if = "String::is_empty")]
pub artist: String,
/// ArtistMBID is the MusicBrainz artist ID.
#[serde(default, skip_serializing_if = "String::is_empty")]
pub artist_mbid: String,
/// Album is the album name.
#[serde(default, skip_serializing_if = "String::is_empty")]
pub album: String,
/// AlbumMBID is the MusicBrainz release ID.
#[serde(default, skip_serializing_if = "String::is_empty")]
pub album_mbid: String,
/// Duration is the song duration in seconds.
#[serde(default, skip_serializing_if = "is_zero_f32")]
pub duration: f32,
}
/// TopSongsRequest is the request for GetArtistTopSongs.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TopSongsRequest {
/// ID is the internal Navidrome artist ID.
#[serde(default)]
pub id: String,
/// Name is the artist name.
#[serde(default)]
pub name: String,
/// MBID is the MusicBrainz ID for the artist (if known).
#[serde(default, skip_serializing_if = "String::is_empty")]
pub mbid: String,
/// Count is the maximum number of top songs to return.
#[serde(default)]
pub count: i32,
}
/// TopSongsResponse is the response for GetArtistTopSongs.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TopSongsResponse {
/// Songs is the list of top songs.
#[serde(default)]
pub songs: Vec<SongRef>,
}
/// Error represents an error from a capability method.
#[derive(Debug)]
pub struct Error {
pub message: String,
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.message)
}
}
impl std::error::Error for Error {}
impl Error {
pub fn new(message: impl Into<String>) -> Self {
Self { message: message.into() }
}
}
/// ArtistMBIDProvider provides the GetArtistMBID function.
pub trait ArtistMBIDProvider {
fn get_artist_mbid(&self, req: ArtistMBIDRequest) -> Result<ArtistMBIDResponse, Error>;
}
/// Register the get_artist_mbid export.
/// This macro generates the WASM export function for this method.
#[macro_export]
macro_rules! register_metadata_artist_mbid {
($plugin_type:ty) => {
#[extism_pdk::plugin_fn]
pub fn nd_get_artist_mbid(
req: extism_pdk::Json<$crate::metadata::ArtistMBIDRequest>
) -> extism_pdk::FnResult<extism_pdk::Json<$crate::metadata::ArtistMBIDResponse>> {
let plugin = <$plugin_type>::default();
let result = $crate::metadata::ArtistMBIDProvider::get_artist_mbid(&plugin, req.into_inner())?;
Ok(extism_pdk::Json(result))
}
};
}
/// ArtistURLProvider provides the GetArtistURL function.
pub trait ArtistURLProvider {
fn get_artist_url(&self, req: ArtistRequest) -> Result<ArtistURLResponse, Error>;
}
/// Register the get_artist_url export.
/// This macro generates the WASM export function for this method.
#[macro_export]
macro_rules! register_metadata_artist_url {
($plugin_type:ty) => {
#[extism_pdk::plugin_fn]
pub fn nd_get_artist_url(
req: extism_pdk::Json<$crate::metadata::ArtistRequest>
) -> extism_pdk::FnResult<extism_pdk::Json<$crate::metadata::ArtistURLResponse>> {
let plugin = <$plugin_type>::default();
let result = $crate::metadata::ArtistURLProvider::get_artist_url(&plugin, req.into_inner())?;
Ok(extism_pdk::Json(result))
}
};
}
/// ArtistBiographyProvider provides the GetArtistBiography function.
pub trait ArtistBiographyProvider {
fn get_artist_biography(&self, req: ArtistRequest) -> Result<ArtistBiographyResponse, Error>;
}
/// Register the get_artist_biography export.
/// This macro generates the WASM export function for this method.
#[macro_export]
macro_rules! register_metadata_artist_biography {
($plugin_type:ty) => {
#[extism_pdk::plugin_fn]
pub fn nd_get_artist_biography(
req: extism_pdk::Json<$crate::metadata::ArtistRequest>
) -> extism_pdk::FnResult<extism_pdk::Json<$crate::metadata::ArtistBiographyResponse>> {
let plugin = <$plugin_type>::default();
let result = $crate::metadata::ArtistBiographyProvider::get_artist_biography(&plugin, req.into_inner())?;
Ok(extism_pdk::Json(result))
}
};
}
/// SimilarArtistsProvider provides the GetSimilarArtists function.
pub trait SimilarArtistsProvider {
fn get_similar_artists(&self, req: SimilarArtistsRequest) -> Result<SimilarArtistsResponse, Error>;
}
/// Register the get_similar_artists export.
/// This macro generates the WASM export function for this method.
#[macro_export]
macro_rules! register_metadata_similar_artists {
($plugin_type:ty) => {
#[extism_pdk::plugin_fn]
pub fn nd_get_similar_artists(
req: extism_pdk::Json<$crate::metadata::SimilarArtistsRequest>
) -> extism_pdk::FnResult<extism_pdk::Json<$crate::metadata::SimilarArtistsResponse>> {
let plugin = <$plugin_type>::default();
let result = $crate::metadata::SimilarArtistsProvider::get_similar_artists(&plugin, req.into_inner())?;
Ok(extism_pdk::Json(result))
}
};
}
/// ArtistImagesProvider provides the GetArtistImages function.
pub trait ArtistImagesProvider {
fn get_artist_images(&self, req: ArtistRequest) -> Result<ArtistImagesResponse, Error>;
}
/// Register the get_artist_images export.
/// This macro generates the WASM export function for this method.
#[macro_export]
macro_rules! register_metadata_artist_images {
($plugin_type:ty) => {
#[extism_pdk::plugin_fn]
pub fn nd_get_artist_images(
req: extism_pdk::Json<$crate::metadata::ArtistRequest>
) -> extism_pdk::FnResult<extism_pdk::Json<$crate::metadata::ArtistImagesResponse>> {
let plugin = <$plugin_type>::default();
let result = $crate::metadata::ArtistImagesProvider::get_artist_images(&plugin, req.into_inner())?;
Ok(extism_pdk::Json(result))
}
};
}
/// ArtistTopSongsProvider provides the GetArtistTopSongs function.
pub trait ArtistTopSongsProvider {
fn get_artist_top_songs(&self, req: TopSongsRequest) -> Result<TopSongsResponse, Error>;
}
/// Register the get_artist_top_songs export.
/// This macro generates the WASM export function for this method.
#[macro_export]
macro_rules! register_metadata_artist_top_songs {
($plugin_type:ty) => {
#[extism_pdk::plugin_fn]
pub fn nd_get_artist_top_songs(
req: extism_pdk::Json<$crate::metadata::TopSongsRequest>
) -> extism_pdk::FnResult<extism_pdk::Json<$crate::metadata::TopSongsResponse>> {
let plugin = <$plugin_type>::default();
let result = $crate::metadata::ArtistTopSongsProvider::get_artist_top_songs(&plugin, req.into_inner())?;
Ok(extism_pdk::Json(result))
}
};
}
/// AlbumInfoProvider provides the GetAlbumInfo function.
pub trait AlbumInfoProvider {
fn get_album_info(&self, req: AlbumRequest) -> Result<AlbumInfoResponse, Error>;
}
/// Register the get_album_info export.
/// This macro generates the WASM export function for this method.
#[macro_export]
macro_rules! register_metadata_album_info {
($plugin_type:ty) => {
#[extism_pdk::plugin_fn]
pub fn nd_get_album_info(
req: extism_pdk::Json<$crate::metadata::AlbumRequest>
) -> extism_pdk::FnResult<extism_pdk::Json<$crate::metadata::AlbumInfoResponse>> {
let plugin = <$plugin_type>::default();
let result = $crate::metadata::AlbumInfoProvider::get_album_info(&plugin, req.into_inner())?;
Ok(extism_pdk::Json(result))
}
};
}
/// AlbumImagesProvider provides the GetAlbumImages function.
pub trait AlbumImagesProvider {
fn get_album_images(&self, req: AlbumRequest) -> Result<AlbumImagesResponse, Error>;
}
/// Register the get_album_images export.
/// This macro generates the WASM export function for this method.
#[macro_export]
macro_rules! register_metadata_album_images {
($plugin_type:ty) => {
#[extism_pdk::plugin_fn]
pub fn nd_get_album_images(
req: extism_pdk::Json<$crate::metadata::AlbumRequest>
) -> extism_pdk::FnResult<extism_pdk::Json<$crate::metadata::AlbumImagesResponse>> {
let plugin = <$plugin_type>::default();
let result = $crate::metadata::AlbumImagesProvider::get_album_images(&plugin, req.into_inner())?;
Ok(extism_pdk::Json(result))
}
};
}
/// SimilarSongsByTrackProvider provides the GetSimilarSongsByTrack function.
pub trait SimilarSongsByTrackProvider {
fn get_similar_songs_by_track(&self, req: SimilarSongsByTrackRequest) -> Result<SimilarSongsResponse, Error>;
}
/// Register the get_similar_songs_by_track export.
/// This macro generates the WASM export function for this method.
#[macro_export]
macro_rules! register_metadata_similar_songs_by_track {
($plugin_type:ty) => {
#[extism_pdk::plugin_fn]
pub fn nd_get_similar_songs_by_track(
req: extism_pdk::Json<$crate::metadata::SimilarSongsByTrackRequest>
) -> extism_pdk::FnResult<extism_pdk::Json<$crate::metadata::SimilarSongsResponse>> {
let plugin = <$plugin_type>::default();
let result = $crate::metadata::SimilarSongsByTrackProvider::get_similar_songs_by_track(&plugin, req.into_inner())?;
Ok(extism_pdk::Json(result))
}
};
}
/// SimilarSongsByAlbumProvider provides the GetSimilarSongsByAlbum function.
pub trait SimilarSongsByAlbumProvider {
fn get_similar_songs_by_album(&self, req: SimilarSongsByAlbumRequest) -> Result<SimilarSongsResponse, Error>;
}
/// Register the get_similar_songs_by_album export.
/// This macro generates the WASM export function for this method.
#[macro_export]
macro_rules! register_metadata_similar_songs_by_album {
($plugin_type:ty) => {
#[extism_pdk::plugin_fn]
pub fn nd_get_similar_songs_by_album(
req: extism_pdk::Json<$crate::metadata::SimilarSongsByAlbumRequest>
) -> extism_pdk::FnResult<extism_pdk::Json<$crate::metadata::SimilarSongsResponse>> {
let plugin = <$plugin_type>::default();
let result = $crate::metadata::SimilarSongsByAlbumProvider::get_similar_songs_by_album(&plugin, req.into_inner())?;
Ok(extism_pdk::Json(result))
}
};
}
/// SimilarSongsByArtistProvider provides the GetSimilarSongsByArtist function.
pub trait SimilarSongsByArtistProvider {
fn get_similar_songs_by_artist(&self, req: SimilarSongsByArtistRequest) -> Result<SimilarSongsResponse, Error>;
}
/// Register the get_similar_songs_by_artist export.
/// This macro generates the WASM export function for this method.
#[macro_export]
macro_rules! register_metadata_similar_songs_by_artist {
($plugin_type:ty) => {
#[extism_pdk::plugin_fn]
pub fn nd_get_similar_songs_by_artist(
req: extism_pdk::Json<$crate::metadata::SimilarSongsByArtistRequest>
) -> extism_pdk::FnResult<extism_pdk::Json<$crate::metadata::SimilarSongsResponse>> {
let plugin = <$plugin_type>::default();
let result = $crate::metadata::SimilarSongsByArtistProvider::get_similar_songs_by_artist(&plugin, req.into_inner())?;
Ok(extism_pdk::Json(result))
}
};
}