Files
eva-pwm-cohost/src/artifacts/beets.rs
T

137 lines
4.2 KiB
Rust

use std::process::{Command, Stdio};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::artifacts::{Artifact, ArtifactBuilder, Contents, Merge, SourceID, Track, tools::{DataSource, ToolDescription}};
#[derive(Debug, Default, Serialize, Deserialize, Clone, JsonSchema)]
pub struct BeatsQueryArgs {
pub artist: Option<String>,
pub album: Option<String>,
pub genre: Option<String>,
pub title: Option<String>,
pub year: Option<u32>
}
#[derive(Debug, Default, Deserialize)]
struct BeetsTrack {
album: String,
artist: String,
genres: Option<Vec<String>>,
label: Option<String>,
title: String,
year: u32,
mb_trackid: Option<String>
}
impl Into<Artifact> for BeetsTrack {
fn into(self) -> Artifact {
let track_data = Track {
title: self.title,
label: self.label,
year: Some(self.year),
genres: self.genres.unwrap_or_default(),
album: Some(self.album),
artist: Some(self.artist),
bpm: None,
};
let builder = ArtifactBuilder::new(SourceID::Beets)
.contents(track_data);
if let Some(mbid) = self.mb_trackid {
builder.mbid(Uuid::parse_str(&mbid).unwrap()).build()
} else {
builder.build()
}
}
}
pub struct BeetsDB;
impl DataSource for BeetsDB {
type Args = BeatsQueryArgs;
type Error = ();
async fn synchronize(&mut self, artifact: &mut Artifact) -> Result<Vec<Artifact>, Self::Error> {
match artifact.contents {
Contents::Track(ref mut target_track) => {
let args = BeatsQueryArgs {
title: Some(target_track.title.clone()),
artist: target_track.artist.clone(),
album: target_track.album.clone(),
..Default::default()
};
let results = self.query(&args).await.unwrap();
if let Some(first) = results.first() {
artifact.merge(first.clone());
} else {
log::error!("Beets could not find {:?}", target_track);
}
},
_ => ()
}
Ok(vec![])
}
async fn query(&mut self, args: &Self::Args) -> Result<Vec<Artifact>, Self::Error> {
let mut beets_cmd = Command::new("beet");
beets_cmd.args(["export", "-f", "json", "-i", "title,label,year,genres,album,artist,mb_trackid"]);
let mut valid = false;
if let Some(ref artist) = args.artist {
beets_cmd.arg(format!("artist:{}", artist));
valid = true;
}
if let Some(ref genre) = args.genre {
beets_cmd.arg(format!("genre:{}", genre));
valid = true;
}
if let Some(ref album) = args.album {
beets_cmd.arg(format!("album:{}", album));
valid = true;
}
if let Some(ref title) = args.title {
beets_cmd.arg(format!("title:{}", title));
valid = true;
}
if let Some(year) = args.year {
beets_cmd.arg(format!("year:{}", year));
valid = true;
}
if !valid {
log::warn!("Tried to execute an empty beets query");
return Err(())
}
log::debug!("Executing beets: {:?}", beets_cmd);
if let Ok(output) = beets_cmd.stdout(Stdio::piped()).stderr(Stdio::null()).spawn().unwrap().wait_with_output() {
match serde_json::from_str::<Vec<BeetsTrack>>(str::from_utf8(&output.stdout).unwrap()) {
Ok(track) => Ok(track.into_iter().map(|t| { t.into()}).collect()),
Err(err) => {
log::error!("Failed to decode beets json: {:?}", err);
Err(())
}
}
} else {
log::error!("Unable to execute query!");
Err(())
}
}
}
impl ToolDescription for BeetsDB {
fn description(&self) -> &str {
"Queries the ship's musical artifact archives for tracks matching the given search parameters"
}
fn name(&self) -> &str {
"query_beets"
}
}