From e5092c84fd7ecea74c0b1ac4fbbef9d6df35ee7b Mon Sep 17 00:00:00 2001 From: dnlmlr <34707428+dnlmlr@users.noreply.github.com> Date: Tue, 2 Aug 2022 11:43:48 +0200 Subject: [PATCH] Implement additional metadata for Artist (#1036) - Added `*-current()` functions to `Artist` to get the list of current versions / releases of each album - This is useful since the `AlbumGroups` can contain multiple versions / releases of the same album --- metadata/src/artist.rs | 198 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 196 insertions(+), 2 deletions(-) diff --git a/metadata/src/artist.rs b/metadata/src/artist.rs index 4144f0c3..d6d0b4e2 100644 --- a/metadata/src/artist.rs +++ b/metadata/src/artist.rs @@ -4,20 +4,51 @@ use std::{ ops::Deref, }; -use crate::{request::RequestResult, track::Tracks, util::try_from_repeated_message, Metadata}; +use crate::{ + album::Albums, + availability::Availabilities, + external_id::ExternalIds, + image::Images, + request::RequestResult, + restriction::Restrictions, + sale_period::SalePeriods, + track::Tracks, + util::{from_repeated_message, try_from_repeated_message}, + Metadata, +}; use librespot_core::{Error, Session, SpotifyId}; use librespot_protocol as protocol; -use protocol::metadata::ArtistWithRole as ArtistWithRoleMessage; pub use protocol::metadata::ArtistWithRole_ArtistRole as ArtistRole; + +use protocol::metadata::ActivityPeriod as ActivityPeriodMessage; +use protocol::metadata::AlbumGroup as AlbumGroupMessage; +use protocol::metadata::ArtistWithRole as ArtistWithRoleMessage; +use protocol::metadata::Biography as BiographyMessage; use protocol::metadata::TopTracks as TopTracksMessage; #[derive(Debug, Clone)] pub struct Artist { pub id: SpotifyId, pub name: String, + pub popularity: i32, pub top_tracks: CountryTopTracks, + pub albums: AlbumGroups, + pub singles: AlbumGroups, + pub compilations: AlbumGroups, + pub appears_on_albums: AlbumGroups, + pub genre: Vec, + pub external_ids: ExternalIds, + pub portraits: Images, + pub biographies: Biographies, + pub activity_periods: ActivityPeriods, + pub restrictions: Restrictions, + pub related: Artists, + pub is_portrait_album_cover: bool, + pub portrait_group: Images, + pub sales_periods: SalePeriods, + pub availabilities: Availabilities, } #[derive(Debug, Clone, Default)] @@ -63,6 +94,69 @@ impl Deref for CountryTopTracks { } } +#[derive(Debug, Clone, Default)] +pub struct AlbumGroup(pub Albums); + +impl Deref for AlbumGroup { + type Target = Albums; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +/// `AlbumGroups` contains collections of album variants (different releases of the same album). +/// Ignoring the wrapping types it is structured roughly like this: +/// ```text +/// AlbumGroups [ +/// [Album1], [Album2-relelease, Album2-older-release], [Album3] +/// ] +/// ``` +/// In most cases only the current variant of each album is needed. A list of every album in it's +/// current release variant can be obtained by using [`AlbumGroups::current_releases`] +#[derive(Debug, Clone, Default)] +pub struct AlbumGroups(pub Vec); + +impl Deref for AlbumGroups { + type Target = Vec; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[derive(Debug, Clone)] +pub struct Biography { + pub text: String, + pub portraits: Images, + pub portrait_group: Vec, +} + +#[derive(Debug, Clone, Default)] +pub struct Biographies(pub Vec); + +impl Deref for Biographies { + type Target = Vec; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[derive(Debug, Clone)] +pub struct ActivityPeriod { + pub start_year: i32, + pub end_year: i32, + pub decade: i32, +} + +#[derive(Debug, Clone, Default)] +pub struct ActivityPeriods(pub Vec); + +impl Deref for ActivityPeriods { + type Target = Vec; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + impl CountryTopTracks { pub fn for_country(&self, country: &str) -> Tracks { if let Some(country) = self.0.iter().find(|top_track| top_track.country == country) { @@ -77,6 +171,37 @@ impl CountryTopTracks { } } +impl Artist { + /// Get the full list of albums, not containing duplicate variants of the same albums. + /// + /// See also [`AlbumGroups`](struct@AlbumGroups) and [`AlbumGroups::current_releases`] + pub fn albums_current(&self) -> Albums { + self.albums.current_releases() + } + + /// Get the full list of singles, not containing duplicate variants of the same singles. + /// + /// See also [`AlbumGroups`](struct@AlbumGroups) and [`AlbumGroups::current_releases`] + pub fn singles_current(&self) -> Albums { + self.singles.current_releases() + } + + /// Get the full list of compilations, not containing duplicate variants of the same + /// compilations. + /// + /// See also [`AlbumGroups`](struct@AlbumGroups) and [`AlbumGroups::current_releases`] + pub fn compilations_current(&self) -> Albums { + self.compilations.current_releases() + } + + /// Get the full list of albums, not containing duplicate variants of the same albums. + /// + /// See also [`AlbumGroups`](struct@AlbumGroups) and [`AlbumGroups::current_releases`] + pub fn appears_on_albums_current(&self) -> Albums { + self.appears_on_albums.current_releases() + } +} + #[async_trait] impl Metadata for Artist { type Message = protocol::metadata::Artist; @@ -96,7 +221,23 @@ impl TryFrom<&::Message> for Artist { Ok(Self { id: artist.try_into()?, name: artist.get_name().to_owned(), + popularity: artist.get_popularity(), top_tracks: artist.get_top_track().try_into()?, + albums: artist.get_album_group().try_into()?, + singles: artist.get_single_group().try_into()?, + compilations: artist.get_compilation_group().try_into()?, + appears_on_albums: artist.get_appears_on_group().try_into()?, + genre: artist.get_genre().to_vec(), + external_ids: artist.get_external_id().into(), + portraits: artist.get_portrait().into(), + biographies: artist.get_biography().into(), + activity_periods: artist.get_activity_period().into(), + restrictions: artist.get_restriction().into(), + related: artist.get_related().try_into()?, + is_portrait_album_cover: artist.get_is_portrait_album_cover(), + portrait_group: artist.get_portrait_group().get_image().into(), + sales_periods: artist.get_sale_period().try_into()?, + availabilities: artist.get_availability().try_into()?, }) } } @@ -127,3 +268,56 @@ impl TryFrom<&TopTracksMessage> for TopTracks { } try_from_repeated_message!(TopTracksMessage, CountryTopTracks); + +impl TryFrom<&AlbumGroupMessage> for AlbumGroup { + type Error = librespot_core::Error; + fn try_from(album_groups: &AlbumGroupMessage) -> Result { + Ok(Self(album_groups.get_album().try_into()?)) + } +} + +impl AlbumGroups { + /// Get the contained albums. This will only use the latest release / variant of an album if + /// multiple variants are available. This should be used if multiple variants of the same album + /// are not explicitely desired. + pub fn current_releases(&self) -> Albums { + let albums = self + .iter() + .filter_map(|agrp| agrp.first()) + .cloned() + .collect(); + Albums(albums) + } +} + +try_from_repeated_message!(AlbumGroupMessage, AlbumGroups); + +impl From<&BiographyMessage> for Biography { + fn from(biography: &BiographyMessage) -> Self { + let portrait_group = biography + .get_portrait_group() + .iter() + .map(|it| it.get_image().into()) + .collect(); + + Self { + text: biography.get_text().to_owned(), + portraits: biography.get_portrait().into(), + portrait_group, + } + } +} + +from_repeated_message!(BiographyMessage, Biographies); + +impl From<&ActivityPeriodMessage> for ActivityPeriod { + fn from(activity_periode: &ActivityPeriodMessage) -> Self { + Self { + start_year: activity_periode.get_start_year(), + end_year: activity_periode.get_end_year(), + decade: activity_periode.get_decade(), + } + } +} + +from_repeated_message!(ActivityPeriodMessage, ActivityPeriods);