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
This commit is contained in:
dnlmlr 2022-08-02 11:43:48 +02:00 committed by GitHub
parent 8ffaf7cb8d
commit e5092c84fd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -4,20 +4,51 @@ use std::{
ops::Deref, 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_core::{Error, Session, SpotifyId};
use librespot_protocol as protocol; use librespot_protocol as protocol;
use protocol::metadata::ArtistWithRole as ArtistWithRoleMessage;
pub use protocol::metadata::ArtistWithRole_ArtistRole as ArtistRole; 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; use protocol::metadata::TopTracks as TopTracksMessage;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Artist { pub struct Artist {
pub id: SpotifyId, pub id: SpotifyId,
pub name: String, pub name: String,
pub popularity: i32,
pub top_tracks: CountryTopTracks, pub top_tracks: CountryTopTracks,
pub albums: AlbumGroups,
pub singles: AlbumGroups,
pub compilations: AlbumGroups,
pub appears_on_albums: AlbumGroups,
pub genre: Vec<String>,
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)] #[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<AlbumGroup>);
impl Deref for AlbumGroups {
type Target = Vec<AlbumGroup>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Debug, Clone)]
pub struct Biography {
pub text: String,
pub portraits: Images,
pub portrait_group: Vec<Images>,
}
#[derive(Debug, Clone, Default)]
pub struct Biographies(pub Vec<Biography>);
impl Deref for Biographies {
type Target = Vec<Biography>;
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<ActivityPeriod>);
impl Deref for ActivityPeriods {
type Target = Vec<ActivityPeriod>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl CountryTopTracks { impl CountryTopTracks {
pub fn for_country(&self, country: &str) -> Tracks { pub fn for_country(&self, country: &str) -> Tracks {
if let Some(country) = self.0.iter().find(|top_track| top_track.country == country) { 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] #[async_trait]
impl Metadata for Artist { impl Metadata for Artist {
type Message = protocol::metadata::Artist; type Message = protocol::metadata::Artist;
@ -96,7 +221,23 @@ impl TryFrom<&<Self as Metadata>::Message> for Artist {
Ok(Self { Ok(Self {
id: artist.try_into()?, id: artist.try_into()?,
name: artist.get_name().to_owned(), name: artist.get_name().to_owned(),
popularity: artist.get_popularity(),
top_tracks: artist.get_top_track().try_into()?, 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); try_from_repeated_message!(TopTracksMessage, CountryTopTracks);
impl TryFrom<&AlbumGroupMessage> for AlbumGroup {
type Error = librespot_core::Error;
fn try_from(album_groups: &AlbumGroupMessage) -> Result<Self, Self::Error> {
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);