Update to artist credit handling
This commit is contained in:
@@ -150,6 +150,7 @@ impl MetadataProvider for MusicBrainzClient {
|
||||
let r: MbRecordingDetail = self.get_json(&url).await?;
|
||||
|
||||
let (artist_name, artist_mbid) = extract_artist_credit(&r.artist_credit);
|
||||
let secondary_artists = extract_secondary_artists(&r.artist_credit);
|
||||
Ok(RecordingDetails {
|
||||
mbid: r.id,
|
||||
title: r.title,
|
||||
@@ -173,6 +174,7 @@ impl MetadataProvider for MusicBrainzClient {
|
||||
.into_iter()
|
||||
.map(|g| g.name)
|
||||
.collect(),
|
||||
secondary_artists,
|
||||
})
|
||||
}
|
||||
async fn search_artist(
|
||||
@@ -275,19 +277,13 @@ impl MetadataProvider for MusicBrainzClient {
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract the primary artist from MusicBrainz artist credits.
|
||||
/// Always returns the first/primary artist only — never concatenates
|
||||
/// collaborators or featured artists into compound names.
|
||||
fn extract_artist_credit(credits: &Option<Vec<MbArtistCredit>>) -> (String, Option<String>) {
|
||||
match credits {
|
||||
Some(credits) if !credits.is_empty() => {
|
||||
let name: String = credits
|
||||
.iter()
|
||||
.map(|c| {
|
||||
let mut s = c.artist.name.clone();
|
||||
if let Some(ref join) = c.joinphrase {
|
||||
s.push_str(join);
|
||||
}
|
||||
s
|
||||
})
|
||||
.collect();
|
||||
let name = credits[0].artist.name.clone();
|
||||
let mbid = Some(credits[0].artist.id.clone());
|
||||
(name, mbid)
|
||||
}
|
||||
@@ -295,6 +291,30 @@ fn extract_artist_credit(credits: &Option<Vec<MbArtistCredit>>) -> (String, Opti
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract non-featuring secondary artists from MusicBrainz artist credits.
|
||||
/// Returns (name, mbid) pairs for collaborators that aren't "featuring" credits.
|
||||
fn extract_secondary_artists(credits: &Option<Vec<MbArtistCredit>>) -> Vec<(String, String)> {
|
||||
let Some(credits) = credits else { return vec![] };
|
||||
if credits.len() <= 1 {
|
||||
return vec![];
|
||||
}
|
||||
|
||||
// Walk credits after the first. Stop at any "feat"/"ft." joinphrase
|
||||
// from the PREVIOUS credit (since joinphrase is on the credit BEFORE the next artist).
|
||||
let mut result = Vec::new();
|
||||
for i in 0..credits.len() - 1 {
|
||||
let jp = credits[i].joinphrase.as_deref().unwrap_or("");
|
||||
let lower = jp.to_lowercase();
|
||||
if lower.contains("feat") || lower.contains("ft.") {
|
||||
break;
|
||||
}
|
||||
// The next credit is a non-featuring collaborator
|
||||
let next = &credits[i + 1];
|
||||
result.push((next.artist.name.clone(), next.artist.id.clone()));
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
fn urlencoded(s: &str) -> String {
|
||||
s.replace(' ', "+")
|
||||
.replace('&', "%26")
|
||||
|
||||
@@ -45,6 +45,9 @@ pub struct RecordingDetails {
|
||||
pub releases: Vec<ReleaseRef>,
|
||||
pub duration_ms: Option<u64>,
|
||||
pub genres: Vec<String>,
|
||||
/// Non-featuring collaborators beyond the primary artist.
|
||||
#[serde(default)]
|
||||
pub secondary_artists: Vec<(String, String)>,
|
||||
}
|
||||
|
||||
/// An artist match from a search query.
|
||||
|
||||
@@ -105,7 +105,7 @@ pub async fn tag_track(
|
||||
(details, best_release)
|
||||
};
|
||||
|
||||
// Upsert artist with MusicBrainz ID
|
||||
// Upsert primary artist with MusicBrainz ID
|
||||
let artist_id = match &details.artist_mbid {
|
||||
Some(mbid) => {
|
||||
Some(queries::artists::upsert(conn, &details.artist, Some(mbid)).await?.id)
|
||||
@@ -115,6 +115,13 @@ pub async fn tag_track(
|
||||
}
|
||||
};
|
||||
|
||||
// Upsert secondary collaborator artists so they exist as separate library entries
|
||||
for (name, mbid) in &details.secondary_artists {
|
||||
if let Err(e) = queries::artists::upsert(conn, name, Some(mbid)).await {
|
||||
tracing::warn!(artist = %name, error = %e, "failed to upsert secondary artist");
|
||||
}
|
||||
}
|
||||
|
||||
// Upsert album from best release
|
||||
let (album_id, album_name) = if let Some(ref release) = best_release {
|
||||
let album = queries::albums::upsert(
|
||||
|
||||
@@ -53,6 +53,7 @@ impl MetadataProvider for MockProvider {
|
||||
}],
|
||||
duration_ms: Some(413_000),
|
||||
genres: vec!["Progressive Rock".into()],
|
||||
secondary_artists: vec![],
|
||||
})
|
||||
} else {
|
||||
Err(shanty_tag::TagError::Other("not found".into()))
|
||||
|
||||
Reference in New Issue
Block a user