Add initial (buggy) fetch implementation
parent
0e8fcd0390
commit
a91044c0a9
File diff suppressed because it is too large
Load Diff
|
@ -6,7 +6,10 @@ edition = "2021"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
musicbrainz_rs = "0.5.0"
|
||||||
nanoserde = "0.1.32"
|
nanoserde = "0.1.32"
|
||||||
|
pico-args = { version = "0.5.0", features = ["eq-separator", "short-space-opt", "combined-flags"] }
|
||||||
|
tokio = { version = "1.27.0", features = ["full"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
criterion = "0.4.0"
|
criterion = "0.4.0"
|
||||||
|
|
|
@ -15,10 +15,6 @@ fn bench_random_library(c: &mut Criterion) {
|
||||||
bench_random_library_with_sizes(c, "massive", 1000, 2500);
|
bench_random_library_with_sizes(c, "massive", 1000, 2500);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bench_supermassive_random_library(c: &mut Criterion) {
|
|
||||||
bench_random_library_with_sizes(c, "supermassive", 2500, 7500);
|
|
||||||
}
|
|
||||||
|
|
||||||
// benchmark helpers
|
// benchmark helpers
|
||||||
|
|
||||||
fn bench_random_library_with_sizes(c: &mut Criterion, size: &str, artists: usize, songs: usize) {
|
fn bench_random_library_with_sizes(c: &mut Criterion, size: &str, artists: usize, songs: usize) {
|
||||||
|
@ -70,9 +66,4 @@ fn random_library(artists: usize, songs_per_artist: usize) -> Library {
|
||||||
// entrypoint
|
// entrypoint
|
||||||
|
|
||||||
criterion_group!(benches, bench_random_library);
|
criterion_group!(benches, bench_random_library);
|
||||||
criterion_group! {
|
criterion_main!(benches);
|
||||||
name = slow_benches;
|
|
||||||
config = Criterion::default().sample_size(20);
|
|
||||||
targets = bench_supermassive_random_library
|
|
||||||
}
|
|
||||||
criterion_main!(benches, slow_benches);
|
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
bench:
|
||||||
|
cargo bench --profile release -- "#rnd"
|
||||||
|
|
||||||
|
bench-supermassive:
|
||||||
|
cargo bench --profile release -- "#spr"
|
||||||
|
|
||||||
|
bench-all:
|
||||||
|
cargo bench --profile release
|
||||||
|
|
||||||
|
flamegraph:
|
||||||
|
cargo flamegraph --bench bench_library --root --open -- --bench --profile-time 10
|
|
@ -0,0 +1,111 @@
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use musicbrainz_rs::{
|
||||||
|
config,
|
||||||
|
entity::{
|
||||||
|
artist::{Artist, ArtistSearchQuery, ArtistType},
|
||||||
|
recording::Recording,
|
||||||
|
relations::{Relation, RelationContent},
|
||||||
|
},
|
||||||
|
prelude::*,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::library;
|
||||||
|
|
||||||
|
const USER_AGENT: &str = "cover/0.1.0 ( mat@mat.services )";
|
||||||
|
|
||||||
|
pub async fn fetch_library(
|
||||||
|
names: impl IntoIterator<Item = String>,
|
||||||
|
) -> Result<library::Library, Error> {
|
||||||
|
config::set_user_agent(USER_AGENT);
|
||||||
|
let mut artists = vec![];
|
||||||
|
for name in names {
|
||||||
|
let artist = fetch_artist(name).await?;
|
||||||
|
println!("{artist:#?}");
|
||||||
|
artists.push(artist);
|
||||||
|
}
|
||||||
|
Ok(library::Library::new(artists))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn fetch_artist(name: String) -> Result<library::Artist, Error> {
|
||||||
|
let query = ArtistSearchQuery::query_builder().artist(&name).build();
|
||||||
|
let result = Artist::search(query)
|
||||||
|
.with_artist_relations()
|
||||||
|
.execute()
|
||||||
|
.await?;
|
||||||
|
let artist = result.entities.first().expect("Failed to load {name}");
|
||||||
|
let artist = Artist::fetch()
|
||||||
|
.id(&artist.id)
|
||||||
|
.with_artist_relations()
|
||||||
|
.execute()
|
||||||
|
.await?;
|
||||||
|
let members =
|
||||||
|
if artist.artist_type.is_some() && is_plural_artist(artist.artist_type.as_ref().unwrap()) {
|
||||||
|
artist
|
||||||
|
.relations
|
||||||
|
.as_ref()
|
||||||
|
.expect("Failed to load relations")
|
||||||
|
.iter()
|
||||||
|
.flat_map(member_from_relation)
|
||||||
|
.collect()
|
||||||
|
} else {
|
||||||
|
vec![artist.name.clone()]
|
||||||
|
};
|
||||||
|
let recordings = Recording::browse()
|
||||||
|
.by_artist(&artist.id)
|
||||||
|
.with_work_relations()
|
||||||
|
.with_work_level_relations()
|
||||||
|
.execute()
|
||||||
|
.await?;
|
||||||
|
let catalog = recordings
|
||||||
|
.entities
|
||||||
|
.into_iter()
|
||||||
|
.map(|recording| convert_recording(&artist, &members, recording));
|
||||||
|
Ok(library::Artist::new(name, members.clone(), catalog))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_plural_artist(kind: &ArtistType) -> bool {
|
||||||
|
matches!(
|
||||||
|
kind,
|
||||||
|
ArtistType::Choir | ArtistType::Group | ArtistType::Orchestra
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn member_from_relation(relation: &Relation) -> Option<String> {
|
||||||
|
if relation.relation_type == "member of band" {
|
||||||
|
let artist = extract_artist(relation);
|
||||||
|
Some(artist.name.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_recording(artist: &Artist, members: &[String], recording: Recording) -> library::Song {
|
||||||
|
let writers = get_writers(members, &recording);
|
||||||
|
library::Song {
|
||||||
|
title: recording.title,
|
||||||
|
performer: artist.name.clone(),
|
||||||
|
writers,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_writers(members: &[String], recording: &Recording) -> HashSet<String> {
|
||||||
|
recording.relations.as_ref().map_or_else(
|
||||||
|
|| HashSet::from_iter(members.iter().cloned()),
|
||||||
|
|relations| relations.iter().flat_map(writer_from_relation).collect(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn writer_from_relation(relation: &Relation) -> Option<String> {
|
||||||
|
match relation.relation_type.as_str() {
|
||||||
|
"writer" | "composer" => Some(extract_artist(relation).name.clone()),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extract_artist(relation: &Relation) -> &Artist {
|
||||||
|
match &relation.content {
|
||||||
|
RelationContent::Artist(artist) => artist,
|
||||||
|
_ => unreachable!("Bad relation"),
|
||||||
|
}
|
||||||
|
}
|
|
@ -1 +1,2 @@
|
||||||
|
pub mod fetch;
|
||||||
pub mod library;
|
pub mod library;
|
||||||
|
|
42
src/main.rs
42
src/main.rs
|
@ -1,16 +1,40 @@
|
||||||
use std::{env, fs};
|
use std::fs;
|
||||||
|
use std::process::exit;
|
||||||
|
|
||||||
use nanoserde::DeJson;
|
use nanoserde::DeJson;
|
||||||
|
use pico_args::Arguments;
|
||||||
|
|
||||||
use cover::library::Library;
|
use cover::fetch::fetch_library;
|
||||||
|
|
||||||
fn main() {
|
const USAGE: &str = "Usage:
|
||||||
let file_path = env::args()
|
# process library.json
|
||||||
.nth(1)
|
$ cover json path/to/library.json
|
||||||
.expect("Usage: cover path/to/library.json");
|
# fetch artists from musicbrainz
|
||||||
let json = fs::read_to_string(file_path).expect("Failed to open library file");
|
$ cover fetch 'Taylor Swift' 'black midi'";
|
||||||
let library: Library =
|
|
||||||
DeJson::deserialize_json(&json).expect("Failed to deserialize library file");
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
let mut args = Arguments::from_env();
|
||||||
|
let library = match args.subcommand().unwrap().as_deref() {
|
||||||
|
Some("json") => {
|
||||||
|
let file_path = args
|
||||||
|
.finish()
|
||||||
|
.into_iter()
|
||||||
|
.next()
|
||||||
|
.unwrap()
|
||||||
|
.into_string()
|
||||||
|
.unwrap();
|
||||||
|
let json = fs::read_to_string(file_path).expect("Failed to open library file");
|
||||||
|
DeJson::deserialize_json(&json).expect("Failed to deserialize library file")
|
||||||
|
}
|
||||||
|
Some("fetch") => fetch_library(args.finish().into_iter().flat_map(|s| s.into_string()))
|
||||||
|
.await
|
||||||
|
.unwrap(),
|
||||||
|
_ => {
|
||||||
|
eprintln!("{USAGE}");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
for cover in library.library_covers() {
|
for cover in library.library_covers() {
|
||||||
println!("Found cover!");
|
println!("Found cover!");
|
||||||
println!(
|
println!(
|
||||||
|
|
Loading…
Reference in New Issue