feat(core): musicbrainz_resolve_musician + musician appearances
Two new QbzCore methods unblocking the MusicianPageView port — both
direct ports of v2_resolve_musician and v2_get_musician_appearances
from src-tauri/commands_v2/integrations.rs.
musicbrainz_resolve_musician(name, role):
- MB client resolve_artist for the MBID, then a Qobuz artist
search for an exact name match. Confirmed when the Qobuz
artist matches; Contextual when there are appears-on albums
only; Weak when MB knows the artist but Qobuz has nothing;
None otherwise.
- The sidebar's musician-clicked dispatcher uses the confidence
to decide whether to route the click into the artist page
(Confirmed) or into MusicianPageView (Contextual / Weak).
musicbrainz_get_musician_appearances(name, role, limit, offset):
- Wraps QbzCore::search_albums with the same per-row mapping
Tauri does: album_id, title, artwork (large/small fallback),
artist name, year, and the caller's role string stamped onto
each row as `role_on_album`. Returns
`MusicianAppearances { albums, total }`.
cc
This commit is contained in:
1 file changed
+96
-2
@@ -16,8 +16,9 @@ use qbz_models::{
|
||||
};
|
||||
use qbz_integrations::musicbrainz::cache::MusicBrainzCache;
|
||||
use qbz_integrations::musicbrainz::{
|
||||
location, ArtistMetadata, ArtistRelationships, DiscoveryArtist, DiscoveryResponse,
|
||||
MusicBrainzClient, Period, RelatedArtist, ResolvedArtist,
|
||||
location, AlbumAppearance, ArtistMetadata, ArtistRelationships, DiscoveryArtist,
|
||||
DiscoveryResponse, MusicBrainzClient, MusicianAppearances, MusicianConfidence, Period,
|
||||
RelatedArtist, ResolvedArtist, ResolvedMusician,
|
||||
};
|
||||
use qbz_player::{PlaybackState, Player, QueueManager};
|
||||
use qbz_qobuz::QobuzClient;
|
||||
@@ -1734,6 +1735,99 @@ impl<A: FrontendAdapter + Send + Sync + 'static> QbzCore<A> {
|
||||
}
|
||||
}
|
||||
|
||||
// ----- Musician resolution + appearances -----------------------------------
|
||||
|
||||
impl<A: FrontendAdapter + Send + Sync + 'static> QbzCore<A> {
|
||||
/// Resolve a musician (band member / collaborator) to the
|
||||
/// strongest available identity: MBID via MusicBrainz, exact-
|
||||
/// name Qobuz artist match, or just an appears-on count. Ports
|
||||
/// v2_resolve_musician end to end so the artist-network sidebar
|
||||
/// click can route the user to the right view (artist page when
|
||||
/// confident, MusicianPageView otherwise).
|
||||
pub async fn musicbrainz_resolve_musician(
|
||||
&self,
|
||||
name: &str,
|
||||
role: &str,
|
||||
) -> Result<ResolvedMusician, CoreError> {
|
||||
let resolved_artist = self
|
||||
.musicbrainz
|
||||
.resolve_artist(name)
|
||||
.await
|
||||
.map_err(|e| CoreError::Internal(e.to_string()))?;
|
||||
|
||||
let normalized_target = name.trim().to_lowercase();
|
||||
let artist_results = self.search_artists(name, 10, 0, None).await?;
|
||||
let exact = artist_results
|
||||
.items
|
||||
.iter()
|
||||
.find(|artist| artist.name.trim().to_lowercase() == normalized_target);
|
||||
|
||||
if let Some(artist) = exact {
|
||||
let qobuz_artist_id = i64::try_from(artist.id).ok();
|
||||
return Ok(ResolvedMusician {
|
||||
name: name.to_string(),
|
||||
role: role.to_string(),
|
||||
mbid: None,
|
||||
qobuz_artist_id,
|
||||
confidence: MusicianConfidence::Confirmed,
|
||||
bands: Vec::new(),
|
||||
appears_on_count: 0,
|
||||
});
|
||||
}
|
||||
|
||||
let album_results = self.search_albums(name, 20, 0, None).await?;
|
||||
let appears_on_count = album_results.total as usize;
|
||||
|
||||
let confidence = if appears_on_count > 0 {
|
||||
MusicianConfidence::Contextual
|
||||
} else if resolved_artist.is_some() {
|
||||
MusicianConfidence::Weak
|
||||
} else {
|
||||
MusicianConfidence::None
|
||||
};
|
||||
|
||||
Ok(ResolvedMusician {
|
||||
name: name.to_string(),
|
||||
role: role.to_string(),
|
||||
mbid: resolved_artist.as_ref().map(|a| a.mbid.clone()),
|
||||
qobuz_artist_id: None,
|
||||
confidence,
|
||||
bands: Vec::new(),
|
||||
appears_on_count,
|
||||
})
|
||||
}
|
||||
|
||||
/// Fetch the paginated list of albums on which `name` appears,
|
||||
/// for the Appears On grid in MusicianPageView. Ports
|
||||
/// v2_get_musician_appearances — same Qobuz album search +
|
||||
/// per-row mapping with `role_on_album = role`.
|
||||
pub async fn musicbrainz_get_musician_appearances(
|
||||
&self,
|
||||
name: &str,
|
||||
role: &str,
|
||||
limit: u32,
|
||||
offset: u32,
|
||||
) -> Result<MusicianAppearances, CoreError> {
|
||||
let results = self.search_albums(name, limit, offset, None).await?;
|
||||
let albums = results
|
||||
.items
|
||||
.into_iter()
|
||||
.map(|album| AlbumAppearance {
|
||||
album_id: album.id,
|
||||
album_title: album.title,
|
||||
album_artwork: album.image.large.or(album.image.small).unwrap_or_default(),
|
||||
artist_name: album.artist.name,
|
||||
year: album.release_date_original,
|
||||
role_on_album: role.to_string(),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
Ok(MusicianAppearances {
|
||||
albums,
|
||||
total: results.total as usize,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Normalize an artist name for dedupe: trim, lowercase, collapse
|
||||
/// whitespace. Used by the discovery pipeline so "Iron Maiden" and
|
||||
/// "iron maiden" hash to the same key in the dismiss store.
|
||||
|
||||
Reference in new issue
Block a user