diff options
author | metamuffin <metamuffin@disroot.org> | 2025-04-23 19:04:33 +0200 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2025-04-23 19:04:33 +0200 |
commit | 388ae00ce1d510143789e85732831280ec803da7 (patch) | |
tree | 470092c081ca2abf71d884461f6d8c0cb2269748 /import/src/musicbrainz.rs | |
parent | 960007b06e2b47d41f88365c26f043f61c817f08 (diff) | |
download | jellything-388ae00ce1d510143789e85732831280ec803da7.tar jellything-388ae00ce1d510143789e85732831280ec803da7.tar.bz2 jellything-388ae00ce1d510143789e85732831280ec803da7.tar.zst |
more work on musicbrainz
Diffstat (limited to 'import/src/musicbrainz.rs')
-rw-r--r-- | import/src/musicbrainz.rs | 128 |
1 files changed, 122 insertions, 6 deletions
diff --git a/import/src/musicbrainz.rs b/import/src/musicbrainz.rs index 059b8f5..7a58cec 100644 --- a/import/src/musicbrainz.rs +++ b/import/src/musicbrainz.rs @@ -4,22 +4,95 @@ Copyright (C) 2025 metamuffin <metamuffin.org> */ +use crate::USER_AGENT; +use anyhow::Result; +use bincode::{Decode, Encode}; +use jellybase::cache::async_cache_memory; use reqwest::{ header::{HeaderMap, HeaderName, HeaderValue}, Client, ClientBuilder, }; -use std::sync::Arc; -use tokio::sync::Semaphore; -use crate::USER_AGENT; +use serde::Deserialize; +use std::{collections::BTreeMap, sync::Arc, time::Duration}; +use tokio::{ + sync::Semaphore, + time::{sleep_until, Instant}, +}; pub struct MusicBrainz { client: Client, - key: String, rate_limit: Arc<Semaphore>, } +#[derive(Debug, Deserialize, Encode, Decode)] +#[serde(rename_all = "kebab-case")] +pub struct MbRecording { + pub id: String, + pub first_release_date: String, + pub title: String, + pub isrcs: Vec<String>, + pub video: bool, + pub disambiguation: String, + pub length: u32, + pub relations: Vec<MbRelation>, +} + +#[derive(Debug, Deserialize, Encode, Decode)] +#[serde(rename_all = "kebab-case")] +pub struct MbRelation { + direction: String, + r#type: String, + type_id: String, + begin: Option<String>, + end: Option<String>, + ended: bool, + target_type: String, + target_credit: String, + source_credit: String, + attributes: Vec<String>, + attribute_ids: BTreeMap<String, String>, + attribute_values: BTreeMap<String, String>, + + work: Option<MbWork>, + artist: Option<MbArtist>, + url: Option<MbUrl>, +} + +#[derive(Debug, Deserialize, Encode, Decode)] +#[serde(rename_all = "kebab-case")] +pub struct MbWork { + id: String, + r#type: String, + type_id: String, + languages: Vec<String>, + iswcs: Vec<String>, + language: Option<String>, + title: String, + attributes: Vec<String>, + disambiguation: String, +} + +#[derive(Debug, Deserialize, Encode, Decode)] +#[serde(rename_all = "kebab-case")] +pub struct MbArtist { + id: String, + r#type: String, + type_id: String, + name: String, + disambiguation: String, + country: String, + sort_name: String, +} + +#[derive(Debug, Deserialize, Encode, Decode)] +#[serde(rename_all = "kebab-case")] +pub struct MbUrl { + id: String, + resource: String, +} + impl MusicBrainz { - pub fn new(api_key: &str) -> Self { + pub fn new() -> Self { let client = ClientBuilder::new() .default_headers(HeaderMap::from_iter([ ( @@ -38,7 +111,50 @@ impl MusicBrainz { // send at most 1 req/s according to musicbrainz docs, each lock is held for 10s // this implementation also never sends more than 10 requests in-flight. rate_limit: Arc::new(Semaphore::new(10)), - key: api_key.to_owned(), } } + + pub async fn lookup_recording(&self, id: String) -> Result<Arc<MbRecording>> { + async_cache_memory("api-musicbrainz-recording", id.clone(), || async move { + let _permit = self.rate_limit.clone().acquire_owned().await?; + let permit_drop_ts = Instant::now() + Duration::from_secs(10); + + let inc = [ + "isrcs", + "area-rels", + "artist-rels", + "event-rels", + "genre-rels", + "instrument-rels", + "label-rels", + "place-rels", + "recording-rels", + "release-rels", + "release-group-rels", + "series-rels", + "url-rels", + "work-rels", + ] + .join("+"); + + let resp = self + .client + .get(format!( + "https://musicbrainz.org/ws/2/recording/{id}?inc={inc}" + )) + .send() + .await? + .error_for_status()? + .json::<MbRecording>() + .await?; + + tokio::task::spawn(async move { + sleep_until(permit_drop_ts).await; + drop(_permit); + }); + + Ok(resp) + }) + .await + } } |