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
|
||||
|
||||
[dependencies]
|
||||
musicbrainz_rs = "0.5.0"
|
||||
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]
|
||||
criterion = "0.4.0"
|
||||
|
|
|
@ -15,10 +15,6 @@ fn bench_random_library(c: &mut Criterion) {
|
|||
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
|
||||
|
||||
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
|
||||
|
||||
criterion_group!(benches, bench_random_library);
|
||||
criterion_group! {
|
||||
name = slow_benches;
|
||||
config = Criterion::default().sample_size(20);
|
||||
targets = bench_supermassive_random_library
|
||||
}
|
||||
criterion_main!(benches, slow_benches);
|
||||
criterion_main!(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;
|
||||
|
|
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 pico_args::Arguments;
|
||||
|
||||
use cover::library::Library;
|
||||
use cover::fetch::fetch_library;
|
||||
|
||||
fn main() {
|
||||
let file_path = env::args()
|
||||
.nth(1)
|
||||
.expect("Usage: cover path/to/library.json");
|
||||
let json = fs::read_to_string(file_path).expect("Failed to open library file");
|
||||
let library: Library =
|
||||
DeJson::deserialize_json(&json).expect("Failed to deserialize library file");
|
||||
const USAGE: &str = "Usage:
|
||||
# process library.json
|
||||
$ cover json path/to/library.json
|
||||
# fetch artists from musicbrainz
|
||||
$ cover fetch 'Taylor Swift' 'black midi'";
|
||||
|
||||
#[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() {
|
||||
println!("Found cover!");
|
||||
println!(
|
||||
|
|
Loading…
Reference in New Issue