cover/src/fetch.rs

112 lines
3.2 KiB
Rust

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"),
}
}