Compare commits

..

1 Commits

Author SHA1 Message Date
Connor Johnstone
966dc6ca86 Fixes for track management 2026-03-18 13:44:08 -04:00
3 changed files with 86 additions and 1 deletions

View File

@@ -6,7 +6,7 @@ use crate::cleaning::escape_lucene;
use crate::error::{TagError, TagResult};
use crate::provider::{
ArtistSearchResult, DiscographyEntry, MetadataProvider, RecordingDetails, RecordingMatch,
ReleaseMatch, ReleaseRef, ReleaseTrack,
ReleaseGroupEntry, ReleaseMatch, ReleaseRef, ReleaseTrack,
};
const BASE_URL: &str = "https://musicbrainz.org/ws/2";
@@ -54,6 +54,13 @@ impl MusicBrainzClient {
}
Ok(resp.json().await?)
}
/// Look up an artist directly by MBID. Returns (name, disambiguation).
pub async fn get_artist_by_mbid(&self, mbid: &str) -> TagResult<(String, Option<String>)> {
let url = format!("{BASE_URL}/artist/{mbid}?fmt=json");
let resp: MbArtistLookup = self.get_json(&url).await?;
Ok((resp.name, resp.disambiguation.filter(|s| !s.is_empty())))
}
}
impl MetadataProvider for MusicBrainzClient {
@@ -240,6 +247,32 @@ impl MetadataProvider for MusicBrainzClient {
Ok(tracks)
}
async fn get_artist_release_groups(
&self,
artist_mbid: &str,
) -> TagResult<Vec<ReleaseGroupEntry>> {
// Fetch album, single, and EP release groups
let url = format!(
"{BASE_URL}/release-group?artist={artist_mbid}&type=album|single|ep&fmt=json&limit=100"
);
let resp: MbReleaseGroupResponse = self.get_json(&url).await?;
Ok(resp
.release_groups
.unwrap_or_default()
.into_iter()
.map(|rg| ReleaseGroupEntry {
mbid: rg.id,
title: rg.title,
primary_type: rg.primary_type,
secondary_types: rg.secondary_types.unwrap_or_default(),
first_release_date: rg.first_release_date,
first_release_mbid: rg.releases
.and_then(|r| r.into_iter().next().map(|rel| rel.id)),
})
.collect())
}
}
fn extract_artist_credit(credits: &Option<Vec<MbArtistCredit>>) -> (String, Option<String>) {
@@ -287,6 +320,12 @@ struct MbArtistResult {
artist_type: Option<String>,
}
#[derive(Deserialize)]
struct MbArtistLookup {
name: String,
disambiguation: Option<String>,
}
#[derive(Deserialize)]
struct MbRecordingSearchResponse {
recordings: Vec<MbRecordingResult>,
@@ -369,3 +408,27 @@ struct MbTrackEntry {
struct MbTrackRecording {
id: String,
}
#[derive(Deserialize)]
struct MbReleaseGroupResponse {
#[serde(rename = "release-groups")]
release_groups: Option<Vec<MbReleaseGroup>>,
}
#[derive(Deserialize)]
struct MbReleaseGroup {
id: String,
title: String,
#[serde(rename = "primary-type")]
primary_type: Option<String>,
#[serde(rename = "secondary-types", default)]
secondary_types: Option<Vec<String>>,
#[serde(rename = "first-release-date")]
first_release_date: Option<String>,
releases: Option<Vec<MbReleaseGroupRelease>>,
}
#[derive(Deserialize)]
struct MbReleaseGroupRelease {
id: String,
}

View File

@@ -104,6 +104,24 @@ pub trait MetadataProvider: Send + Sync {
&self,
release_mbid: &str,
) -> impl std::future::Future<Output = TagResult<Vec<ReleaseTrack>>> + Send;
/// Get deduplicated release groups (albums, EPs, singles) for an artist.
fn get_artist_release_groups(
&self,
artist_mbid: &str,
) -> impl std::future::Future<Output = TagResult<Vec<ReleaseGroupEntry>>> + Send;
}
/// A release group (deduplicated album/EP/single concept).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReleaseGroupEntry {
pub mbid: String,
pub title: String,
pub primary_type: Option<String>,
pub secondary_types: Vec<String>,
pub first_release_date: Option<String>,
/// MBID of the first release in this group (for fetching tracks).
pub first_release_mbid: Option<String>,
}
/// A track within a release.

View File

@@ -70,6 +70,10 @@ impl MetadataProvider for MockProvider {
async fn get_release_tracks(&self, _release_mbid: &str) -> TagResult<Vec<shanty_tag::provider::ReleaseTrack>> {
Ok(vec![])
}
async fn get_artist_release_groups(&self, _artist_mbid: &str) -> TagResult<Vec<shanty_tag::provider::ReleaseGroupEntry>> {
Ok(vec![])
}
}
async fn test_db() -> Database {