musicbrainz: dedupe some code

This commit is contained in:
2026-06-17 12:04:26 +02:00
parent 8716350a4e
commit e78a2c3215
2 changed files with 95 additions and 57 deletions
+95 -56
View File
@@ -1,3 +1,6 @@
use log::Record;
use musicbrainz_rs::entity::artist_credit::ArtistCredit;
use musicbrainz_rs::entity::release::Release;
use musicbrainz_rs::{ApiEndpointError, entity::recording::Recording}; use musicbrainz_rs::{ApiEndpointError, entity::recording::Recording};
use musicbrainz_rs::prelude::*; use musicbrainz_rs::prelude::*;
use schemars::JsonSchema; use schemars::JsonSchema;
@@ -7,16 +10,94 @@ use uuid::Uuid;
use crate::artifacts::tools::{DataSource, ToolDescription}; use crate::artifacts::tools::{DataSource, ToolDescription};
use crate::artifacts::{Album, Artifact, ArtifactBuilder, Artist, Contents, Merge, SourceID, Track}; use crate::artifacts::{Album, Artifact, ArtifactBuilder, Artist, Contents, Merge, SourceID, Track};
impl From<Recording> for Track {
fn from(value: Recording) -> Self {
let artist = if let Some(artist) = value.artist_credit.unwrap_or_default().first() {
Some(artist.name.clone())
} else {
None
};
let album = if let Some(album) = value.releases.unwrap_or_default().first() {
Some(album.title.clone())
} else {
None
};
Self {
title: value.title,
artist,
album,
..Default::default()
}
}
}
impl From<Release> for Album {
fn from(value: Release) -> Self {
let artist = if let Some(artist) = value.artist_credit.unwrap_or_default().first() {
Some(artist.name.clone())
} else {
None
}.unwrap_or_default();
Self {
about: value.annotation,
title: value.title,
artist,
..Default::default()
}
}
}
impl From<ArtistCredit> for Artist {
fn from(value: ArtistCredit) -> Self {
Self {
bio: value.artist.annotation,
location: value.artist.country,
name: value.name,
..Default::default()
}
}
}
pub struct MBQuery; pub struct MBQuery;
impl MBQuery {
fn extract_recording_data(track: Recording) -> (Artifact, Vec<Artifact>) {
let mut ret = vec![];
let ret_track = ArtifactBuilder::new(SourceID::Musicbrainz)
.contents(Track::from(track.clone()))
.mbid(Uuid::parse_str(&track.id).unwrap())
.build();
for release in track.releases.unwrap_or_default() {
log::debug!("Found new release: {:?}", release);
ret.push(ArtifactBuilder::new(SourceID::Musicbrainz)
.mbid(Uuid::parse_str(&release.id).unwrap())
.contents(Album::from(release))
.build());
}
for artist in track.artist_credit.unwrap_or_default() {
ret.push(ArtifactBuilder::new(SourceID::Musicbrainz)
.mbid(Uuid::parse_str(&artist.artist.id).unwrap())
.contents(Artist::from(artist))
.build());
}
(ret_track, ret)
}
}
impl DataSource for MBQuery { impl DataSource for MBQuery {
type Error = ApiEndpointError; type Error = ApiEndpointError;
type Args = MusicbrainzQueryArgs; type Args = MusicbrainzQueryArgs;
async fn synchronize(&mut self, artifact: &mut Artifact) -> Result<Vec<Artifact>, Self::Error> { async fn synchronize(&mut self, artifact: &mut Artifact) -> Result<Vec<Artifact>, Self::Error> {
let mut new_artifacts = vec![]; let mut ret = vec![];
if artifact.mbid.is_none() { if artifact.mbid.is_none() {
return Ok(new_artifacts); return Ok(ret);
} }
let artifact_id = artifact.mbid.clone().unwrap(); let artifact_id = artifact.mbid.clone().unwrap();
log::debug!("Synchronizing {} with musicbrainz", artifact_id); log::debug!("Synchronizing {} with musicbrainz", artifact_id);
@@ -34,39 +115,21 @@ impl DataSource for MBQuery {
} }
}; };
let (track, mut new_artifacts) = Self::extract_recording_data(track);
ret.push(track.clone());
ret.append(&mut new_artifacts);
artifact.sources.insert(SourceID::Musicbrainz); artifact.sources.insert(SourceID::Musicbrainz);
for release in track.releases.unwrap_or_default() { if let Contents::Track(track) = track.contents {
log::debug!("Found new release: {:?}", release); target_track.merge(track);
let first_artist = release.artist_credit.unwrap_or_default().first().unwrap().clone();
new_artifacts.push(ArtifactBuilder::new(SourceID::Musicbrainz)
.contents(Album {
title: release.title.clone(),
artist: first_artist.name.clone(),
about: release.annotation,
..Default::default()
})
.mbid(Uuid::parse_str(&release.id).unwrap()).build());
target_track.merge(Track {
album: Some(release.title),
title: track.title.clone(),
artist: Some(first_artist.artist.name.clone()),
..Default::default()
});
new_artifacts.push(ArtifactBuilder::new(SourceID::Musicbrainz)
.contents(Artist {
name: first_artist.name,
bio: first_artist.artist.annotation,
location: first_artist.artist.area.and_then(|area| { Some(area.name) }),
..Default::default()
})
.mbid(Uuid::parse_str(&first_artist.artist.id).unwrap()).build());
} }
}, },
_ => () _ => ()
} }
Ok(new_artifacts) Ok(ret)
} }
async fn query(&mut self, args: &Self::Args) -> Result<Vec<Artifact>, Self::Error> { async fn query(&mut self, args: &Self::Args) -> Result<Vec<Artifact>, Self::Error> {
@@ -86,34 +149,10 @@ impl DataSource for MBQuery {
} }
}; };
for release in track.releases.unwrap_or_default() { let (track, mut new_artifacts) = Self::extract_recording_data(track);
log::debug!("Found new release: {:?}", release);
let first_artist = release.artist_credit.unwrap_or_default().first().unwrap().clone(); ret.push(track);
ret.push(ArtifactBuilder::new(SourceID::Musicbrainz) ret.append(&mut new_artifacts);
.contents(Album {
title: release.title.clone(),
artist: first_artist.name.clone(),
about: release.annotation,
..Default::default()
})
.mbid(Uuid::parse_str(&release.id).unwrap()).build());
ret.push(ArtifactBuilder::new(SourceID::Musicbrainz)
.contents(Track {
album: Some(release.title),
title: track.title.clone(),
artist: Some(first_artist.artist.name.clone()),
..Default::default()
})
.mbid(Uuid::parse_str(&mbid).unwrap()).build());
ret.push(ArtifactBuilder::new(SourceID::Musicbrainz)
.contents(Artist {
name: first_artist.name,
bio: first_artist.artist.annotation,
location: first_artist.artist.area.and_then(|area| { Some(area.name) }),
..Default::default()
})
.mbid(Uuid::parse_str(&first_artist.artist.id).unwrap()).build());
}
} }
Ok(ret) Ok(ret)
-1
View File
@@ -148,7 +148,6 @@ impl Session {
let tools = vec![ let tools = vec![
Tool { name: "log_stage_event".into(), description: "Inserts an event into the current scene script".into(), schema: schema_for!(StageEventArgs)}.into(), Tool { name: "log_stage_event".into(), description: "Inserts an event into the current scene script".into(), schema: schema_for!(StageEventArgs)}.into(),
// TODO: There should only be two queries, one against the ship's onboard archive, and another against the relay network, or whatever we call it. Both should be structured with the same arguments schema // TODO: There should only be two queries, one against the ship's onboard archive, and another against the relay network, or whatever we call it. Both should be structured with the same arguments schema
// TODO: A relay search should try to grab first from beets, then musicbrainz, then from bandcamp.
// TODO: A query should specify what parts of metadata are sufficient for the result, so we don't always have to hit all the layers of data. beets can of course, ignore this. // TODO: A query should specify what parts of metadata are sufficient for the result, so we don't always have to hit all the layers of data. beets can of course, ignore this.
// TODO: A query should be hierarchical somehow? eg, "I already know about artist X, but I want to know everything about track Y from album Z" or "I don't know anything about artist X/album Y, please give me an overview" // TODO: A query should be hierarchical somehow? eg, "I already know about artist X, but I want to know everything about track Y from album Z" or "I don't know anything about artist X/album Y, please give me an overview"
Tool::from_datasource(&MBQuery).into(), Tool::from_datasource(&MBQuery).into(),