librespot_metadata/
artist.rs

1use std::{
2    fmt::Debug,
3    ops::{Deref, DerefMut},
4};
5
6use crate::{
7    album::Albums,
8    availability::Availabilities,
9    external_id::ExternalIds,
10    image::Images,
11    request::RequestResult,
12    restriction::Restrictions,
13    sale_period::SalePeriods,
14    track::Tracks,
15    util::{impl_deref_wrapped, impl_from_repeated, impl_try_from_repeated},
16    Metadata,
17};
18
19use librespot_core::{Error, Session, SpotifyId};
20
21use librespot_protocol as protocol;
22pub use protocol::metadata::artist_with_role::ArtistRole;
23
24use protocol::metadata::ActivityPeriod as ActivityPeriodMessage;
25use protocol::metadata::AlbumGroup as AlbumGroupMessage;
26use protocol::metadata::ArtistWithRole as ArtistWithRoleMessage;
27use protocol::metadata::Biography as BiographyMessage;
28use protocol::metadata::TopTracks as TopTracksMessage;
29
30#[derive(Debug, Clone)]
31pub struct Artist {
32    pub id: SpotifyId,
33    pub name: String,
34    pub popularity: i32,
35    pub top_tracks: CountryTopTracks,
36    pub albums: AlbumGroups,
37    pub singles: AlbumGroups,
38    pub compilations: AlbumGroups,
39    pub appears_on_albums: AlbumGroups,
40    pub genre: Vec<String>,
41    pub external_ids: ExternalIds,
42    pub portraits: Images,
43    pub biographies: Biographies,
44    pub activity_periods: ActivityPeriods,
45    pub restrictions: Restrictions,
46    pub related: Artists,
47    pub is_portrait_album_cover: bool,
48    pub portrait_group: Images,
49    pub sales_periods: SalePeriods,
50    pub availabilities: Availabilities,
51}
52
53#[derive(Debug, Clone, Default)]
54pub struct Artists(pub Vec<Artist>);
55
56impl_deref_wrapped!(Artists, Vec<Artist>);
57
58#[derive(Debug, Clone)]
59pub struct ArtistWithRole {
60    pub id: SpotifyId,
61    pub name: String,
62    pub role: ArtistRole,
63}
64
65#[derive(Debug, Clone, Default)]
66pub struct ArtistsWithRole(pub Vec<ArtistWithRole>);
67
68impl_deref_wrapped!(ArtistsWithRole, Vec<ArtistWithRole>);
69
70#[derive(Debug, Clone)]
71pub struct TopTracks {
72    pub country: String,
73    pub tracks: Tracks,
74}
75
76#[derive(Debug, Clone, Default)]
77pub struct CountryTopTracks(pub Vec<TopTracks>);
78
79impl_deref_wrapped!(CountryTopTracks, Vec<TopTracks>);
80
81#[derive(Debug, Clone, Default)]
82pub struct AlbumGroup(pub Albums);
83
84impl_deref_wrapped!(AlbumGroup, Albums);
85
86/// `AlbumGroups` contains collections of album variants (different releases of the same album).
87/// Ignoring the wrapping types it is structured roughly like this:
88/// ```text
89/// AlbumGroups [
90///     [Album1], [Album2-relelease, Album2-older-release], [Album3]
91/// ]
92/// ```
93/// In most cases only the current variant of each album is needed. A list of every album in its
94/// current release variant can be obtained by using [`AlbumGroups::current_releases`]
95#[derive(Debug, Clone, Default)]
96pub struct AlbumGroups(pub Vec<AlbumGroup>);
97
98impl_deref_wrapped!(AlbumGroups, Vec<AlbumGroup>);
99
100#[derive(Debug, Clone)]
101pub struct Biography {
102    pub text: String,
103    pub portraits: Images,
104    pub portrait_group: Vec<Images>,
105}
106
107#[derive(Debug, Clone, Default)]
108pub struct Biographies(pub Vec<Biography>);
109
110impl_deref_wrapped!(Biographies, Vec<Biography>);
111
112#[derive(Debug, Clone)]
113pub enum ActivityPeriod {
114    Timespan {
115        start_year: u16,
116        end_year: Option<u16>,
117    },
118    Decade(u16),
119}
120
121#[derive(Debug, Clone, Default)]
122pub struct ActivityPeriods(pub Vec<ActivityPeriod>);
123
124impl_deref_wrapped!(ActivityPeriods, Vec<ActivityPeriod>);
125
126impl CountryTopTracks {
127    pub fn for_country(&self, country: &str) -> Tracks {
128        if let Some(country) = self.0.iter().find(|top_track| top_track.country == country) {
129            return country.tracks.clone();
130        }
131
132        if let Some(global) = self.0.iter().find(|top_track| top_track.country.is_empty()) {
133            return global.tracks.clone();
134        }
135
136        Tracks(vec![]) // none found
137    }
138}
139
140impl Artist {
141    /// Get the full list of albums, not containing duplicate variants of the same albums.
142    ///
143    /// See also [`AlbumGroups`](struct@AlbumGroups) and [`AlbumGroups::current_releases`]
144    pub fn albums_current(&self) -> impl Iterator<Item = &SpotifyId> {
145        self.albums.current_releases()
146    }
147
148    /// Get the full list of singles, not containing duplicate variants of the same singles.
149    ///
150    /// See also [`AlbumGroups`](struct@AlbumGroups) and [`AlbumGroups::current_releases`]
151    pub fn singles_current(&self) -> impl Iterator<Item = &SpotifyId> {
152        self.singles.current_releases()
153    }
154
155    /// Get the full list of compilations, not containing duplicate variants of the same
156    /// compilations.
157    ///
158    /// See also [`AlbumGroups`](struct@AlbumGroups) and [`AlbumGroups::current_releases`]
159    pub fn compilations_current(&self) -> impl Iterator<Item = &SpotifyId> {
160        self.compilations.current_releases()
161    }
162
163    /// Get the full list of albums, not containing duplicate variants of the same albums.
164    ///
165    /// See also [`AlbumGroups`](struct@AlbumGroups) and [`AlbumGroups::current_releases`]
166    pub fn appears_on_albums_current(&self) -> impl Iterator<Item = &SpotifyId> {
167        self.appears_on_albums.current_releases()
168    }
169}
170
171#[async_trait]
172impl Metadata for Artist {
173    type Message = protocol::metadata::Artist;
174
175    async fn request(session: &Session, artist_id: &SpotifyId) -> RequestResult {
176        session.spclient().get_artist_metadata(artist_id).await
177    }
178
179    fn parse(msg: &Self::Message, _: &SpotifyId) -> Result<Self, Error> {
180        Self::try_from(msg)
181    }
182}
183
184impl TryFrom<&<Self as Metadata>::Message> for Artist {
185    type Error = librespot_core::Error;
186    fn try_from(artist: &<Self as Metadata>::Message) -> Result<Self, Self::Error> {
187        Ok(Self {
188            id: artist.try_into()?,
189            name: artist.name().to_owned(),
190            popularity: artist.popularity(),
191            top_tracks: artist.top_track.as_slice().try_into()?,
192            albums: artist.album_group.as_slice().try_into()?,
193            singles: artist.single_group.as_slice().try_into()?,
194            compilations: artist.compilation_group.as_slice().try_into()?,
195            appears_on_albums: artist.appears_on_group.as_slice().try_into()?,
196            genre: artist.genre.to_vec(),
197            external_ids: artist.external_id.as_slice().into(),
198            portraits: artist.portrait.as_slice().into(),
199            biographies: artist.biography.as_slice().into(),
200            activity_periods: artist.activity_period.as_slice().try_into()?,
201            restrictions: artist.restriction.as_slice().into(),
202            related: artist.related.as_slice().try_into()?,
203            is_portrait_album_cover: artist.is_portrait_album_cover(),
204            portrait_group: artist
205                .portrait_group
206                .get_or_default()
207                .image
208                .as_slice()
209                .into(),
210            sales_periods: artist.sale_period.as_slice().try_into()?,
211            availabilities: artist.availability.as_slice().try_into()?,
212        })
213    }
214}
215
216impl_try_from_repeated!(<Artist as Metadata>::Message, Artists);
217
218impl TryFrom<&ArtistWithRoleMessage> for ArtistWithRole {
219    type Error = librespot_core::Error;
220    fn try_from(artist_with_role: &ArtistWithRoleMessage) -> Result<Self, Self::Error> {
221        Ok(Self {
222            id: artist_with_role.try_into()?,
223            name: artist_with_role.artist_name().to_owned(),
224            role: artist_with_role.role(),
225        })
226    }
227}
228
229impl_try_from_repeated!(ArtistWithRoleMessage, ArtistsWithRole);
230
231impl TryFrom<&TopTracksMessage> for TopTracks {
232    type Error = librespot_core::Error;
233    fn try_from(top_tracks: &TopTracksMessage) -> Result<Self, Self::Error> {
234        Ok(Self {
235            country: top_tracks.country().to_owned(),
236            tracks: top_tracks.track.as_slice().try_into()?,
237        })
238    }
239}
240
241impl_try_from_repeated!(TopTracksMessage, CountryTopTracks);
242
243impl TryFrom<&AlbumGroupMessage> for AlbumGroup {
244    type Error = librespot_core::Error;
245    fn try_from(album_groups: &AlbumGroupMessage) -> Result<Self, Self::Error> {
246        Ok(Self(album_groups.album.as_slice().try_into()?))
247    }
248}
249
250impl AlbumGroups {
251    /// Get the contained albums. This will only use the latest release / variant of an album if
252    /// multiple variants are available. This should be used if multiple variants of the same album
253    /// are not explicitely desired.
254    pub fn current_releases(&self) -> impl Iterator<Item = &SpotifyId> {
255        self.iter().filter_map(|agrp| agrp.first())
256    }
257}
258
259impl_try_from_repeated!(AlbumGroupMessage, AlbumGroups);
260
261impl From<&BiographyMessage> for Biography {
262    fn from(biography: &BiographyMessage) -> Self {
263        let portrait_group = biography
264            .portrait_group
265            .iter()
266            .map(|it| it.image.as_slice().into())
267            .collect();
268
269        Self {
270            text: biography.text().to_owned(),
271            portraits: biography.portrait.as_slice().into(),
272            portrait_group,
273        }
274    }
275}
276
277impl_from_repeated!(BiographyMessage, Biographies);
278
279impl TryFrom<&ActivityPeriodMessage> for ActivityPeriod {
280    type Error = librespot_core::Error;
281
282    fn try_from(period: &ActivityPeriodMessage) -> Result<Self, Self::Error> {
283        let activity_period = match (
284            period.has_decade(),
285            period.has_start_year(),
286            period.has_end_year(),
287        ) {
288            // (decade, start_year, end_year)
289            (true, false, false) => Self::Decade(period.decade().try_into()?),
290            (false, true, closed_period) => Self::Timespan {
291                start_year: period.start_year().try_into()?,
292                end_year: closed_period
293                    .then(|| period.end_year().try_into())
294                    .transpose()?,
295            },
296            _ => {
297                return Err(librespot_core::Error::failed_precondition(
298                    "ActivityPeriod is expected to be either a decade or timespan",
299                ))
300            }
301        };
302        Ok(activity_period)
303    }
304}
305
306impl_try_from_repeated!(ActivityPeriodMessage, ActivityPeriods);