Files
search/src/musicbrainz.rs
Connor Johnstone d358b79a6b Formatting
2026-03-18 15:37:21 -04:00

138 lines
4.5 KiB
Rust

use shanty_tag::MusicBrainzClient;
use shanty_tag::provider::MetadataProvider;
use crate::error::SearchResult;
use crate::provider::{
AlbumResult, ArtistResult, Discography, DiscographyEntry, SearchProvider, TrackResult,
};
/// MusicBrainz implementation of `SearchProvider`, wrapping shanty-tag's client.
pub struct MusicBrainzSearch {
client: MusicBrainzClient,
}
impl MusicBrainzSearch {
pub fn new() -> SearchResult<Self> {
let client = MusicBrainzClient::new()
.map_err(|e| crate::error::SearchError::Provider(e.to_string()))?;
Ok(Self { client })
}
}
impl SearchProvider for MusicBrainzSearch {
async fn search_artist(&self, query: &str, limit: u32) -> SearchResult<Vec<ArtistResult>> {
let results = self.client.search_artist(query, limit).await?;
Ok(results
.into_iter()
.map(|a| ArtistResult {
id: a.mbid,
name: a.name,
disambiguation: a.disambiguation,
country: a.country,
artist_type: a.artist_type,
score: a.score,
})
.collect())
}
async fn search_album(
&self,
query: &str,
artist_hint: Option<&str>,
limit: u32,
) -> SearchResult<Vec<AlbumResult>> {
let artist = artist_hint.unwrap_or("");
let results = self.client.search_release(artist, query).await?;
Ok(results
.into_iter()
.take(limit as usize)
.map(|r| AlbumResult {
id: r.mbid,
title: r.title,
artist: r.artist,
artist_id: r.artist_mbid,
year: r
.date
.as_deref()
.and_then(|d| d.split('-').next())
.map(String::from),
track_count: r.track_count,
score: r.score,
})
.collect())
}
async fn search_track(
&self,
query: &str,
artist_hint: Option<&str>,
limit: u32,
) -> SearchResult<Vec<TrackResult>> {
let artist = artist_hint.unwrap_or("");
let results = self.client.search_recording(artist, query).await?;
Ok(results
.into_iter()
.take(limit as usize)
.map(|r| TrackResult {
id: r.mbid,
title: r.title,
artist: r.artist,
artist_id: r.artist_mbid,
album: r.releases.first().map(|rel| rel.title.clone()),
duration_ms: None, // not in search results
score: r.score,
})
.collect())
}
async fn get_discography(&self, artist_id: &str) -> SearchResult<Discography> {
let releases = self.client.get_artist_releases(artist_id, 100).await?;
// Try to get the artist name from the first release, or use the MBID
let artist_name = if !releases.is_empty() {
// We don't have the artist name from this endpoint directly,
// so do a quick artist search by MBID
let artists = self.client.search_artist(artist_id, 1).await.ok();
artists
.and_then(|a| a.into_iter().next())
.map(|a| a.name)
.unwrap_or_else(|| artist_id.to_string())
} else {
artist_id.to_string()
};
Ok(Discography {
artist_name,
artist_id: artist_id.to_string(),
releases: releases
.into_iter()
.map(|r| DiscographyEntry {
id: r.mbid,
title: r.title,
date: r.date,
release_type: r.release_type,
track_count: r.track_count,
})
.collect(),
})
}
async fn get_release_groups(
&self,
artist_id: &str,
) -> SearchResult<Vec<crate::provider::ReleaseGroupResult>> {
let groups = self.client.get_artist_release_groups(artist_id).await?;
Ok(groups
.into_iter()
.map(|rg| crate::provider::ReleaseGroupResult {
id: rg.mbid,
title: rg.title,
primary_type: rg.primary_type,
secondary_types: rg.secondary_types,
first_release_date: rg.first_release_date,
first_release_id: rg.first_release_mbid,
})
.collect())
}
}