1use std::{
2 fmt::Debug,
3 ops::{Deref, DerefMut},
4};
5
6use crate::{
7 Metadata,
8 album::Albums,
9 availability::Availabilities,
10 external_id::ExternalIds,
11 image::Images,
12 request::RequestResult,
13 restriction::Restrictions,
14 sale_period::SalePeriods,
15 track::Tracks,
16 util::{impl_deref_wrapped, impl_from_repeated, impl_try_from_repeated},
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 external_ids: ExternalIds,
41 pub portraits: Images,
42 pub biographies: Biographies,
43 pub activity_periods: ActivityPeriods,
44 pub restrictions: Restrictions,
45 pub related: Artists,
46 pub is_portrait_album_cover: bool,
47 pub portrait_group: Images,
48 pub sales_periods: SalePeriods,
49 pub availabilities: Availabilities,
50}
51
52#[derive(Debug, Clone, Default)]
53pub struct Artists(pub Vec<Artist>);
54
55impl_deref_wrapped!(Artists, Vec<Artist>);
56
57#[derive(Debug, Clone)]
58pub struct ArtistWithRole {
59 pub id: SpotifyId,
60 pub name: String,
61 pub role: ArtistRole,
62}
63
64#[derive(Debug, Clone, Default)]
65pub struct ArtistsWithRole(pub Vec<ArtistWithRole>);
66
67impl_deref_wrapped!(ArtistsWithRole, Vec<ArtistWithRole>);
68
69#[derive(Debug, Clone)]
70pub struct TopTracks {
71 pub country: String,
72 pub tracks: Tracks,
73}
74
75#[derive(Debug, Clone, Default)]
76pub struct CountryTopTracks(pub Vec<TopTracks>);
77
78impl_deref_wrapped!(CountryTopTracks, Vec<TopTracks>);
79
80#[derive(Debug, Clone, Default)]
81pub struct AlbumGroup(pub Albums);
82
83impl_deref_wrapped!(AlbumGroup, Albums);
84
85#[derive(Debug, Clone, Default)]
95pub struct AlbumGroups(pub Vec<AlbumGroup>);
96
97impl_deref_wrapped!(AlbumGroups, Vec<AlbumGroup>);
98
99#[derive(Debug, Clone)]
100pub struct Biography {
101 pub text: String,
102 pub portraits: Images,
103 pub portrait_group: Vec<Images>,
104}
105
106#[derive(Debug, Clone, Default)]
107pub struct Biographies(pub Vec<Biography>);
108
109impl_deref_wrapped!(Biographies, Vec<Biography>);
110
111#[derive(Debug, Clone)]
112pub enum ActivityPeriod {
113 Timespan {
114 start_year: u16,
115 end_year: Option<u16>,
116 },
117 Decade(u16),
118}
119
120#[derive(Debug, Clone, Default)]
121pub struct ActivityPeriods(pub Vec<ActivityPeriod>);
122
123impl_deref_wrapped!(ActivityPeriods, Vec<ActivityPeriod>);
124
125impl CountryTopTracks {
126 pub fn for_country(&self, country: &str) -> Tracks {
127 if let Some(country) = self.0.iter().find(|top_track| top_track.country == country) {
128 return country.tracks.clone();
129 }
130
131 if let Some(global) = self.0.iter().find(|top_track| top_track.country.is_empty()) {
132 return global.tracks.clone();
133 }
134
135 Tracks(vec![]) }
137}
138
139impl Artist {
140 pub fn albums_current(&self) -> impl Iterator<Item = &SpotifyId> {
144 self.albums.current_releases()
145 }
146
147 pub fn singles_current(&self) -> impl Iterator<Item = &SpotifyId> {
151 self.singles.current_releases()
152 }
153
154 pub fn compilations_current(&self) -> impl Iterator<Item = &SpotifyId> {
159 self.compilations.current_releases()
160 }
161
162 pub fn appears_on_albums_current(&self) -> impl Iterator<Item = &SpotifyId> {
166 self.appears_on_albums.current_releases()
167 }
168}
169
170#[async_trait]
171impl Metadata for Artist {
172 type Message = protocol::metadata::Artist;
173
174 async fn request(session: &Session, artist_id: &SpotifyId) -> RequestResult {
175 session.spclient().get_artist_metadata(artist_id).await
176 }
177
178 fn parse(msg: &Self::Message, _: &SpotifyId) -> Result<Self, Error> {
179 Self::try_from(msg)
180 }
181}
182
183impl TryFrom<&<Self as Metadata>::Message> for Artist {
184 type Error = librespot_core::Error;
185 fn try_from(artist: &<Self as Metadata>::Message) -> Result<Self, Self::Error> {
186 Ok(Self {
187 id: artist.try_into()?,
188 name: artist.name().to_owned(),
189 popularity: artist.popularity(),
190 top_tracks: artist.top_track.as_slice().try_into()?,
191 albums: artist.album_group.as_slice().try_into()?,
192 singles: artist.single_group.as_slice().try_into()?,
193 compilations: artist.compilation_group.as_slice().try_into()?,
194 appears_on_albums: artist.appears_on_group.as_slice().try_into()?,
195 external_ids: artist.external_id.as_slice().into(),
196 portraits: artist.portrait.as_slice().into(),
197 biographies: artist.biography.as_slice().into(),
198 activity_periods: artist.activity_period.as_slice().try_into()?,
199 restrictions: artist.restriction.as_slice().into(),
200 related: artist.related.as_slice().try_into()?,
201 is_portrait_album_cover: artist.is_portrait_album_cover(),
202 portrait_group: artist
203 .portrait_group
204 .get_or_default()
205 .image
206 .as_slice()
207 .into(),
208 sales_periods: artist.sale_period.as_slice().try_into()?,
209 availabilities: artist.availability.as_slice().try_into()?,
210 })
211 }
212}
213
214impl_try_from_repeated!(<Artist as Metadata>::Message, Artists);
215
216impl TryFrom<&ArtistWithRoleMessage> for ArtistWithRole {
217 type Error = librespot_core::Error;
218 fn try_from(artist_with_role: &ArtistWithRoleMessage) -> Result<Self, Self::Error> {
219 Ok(Self {
220 id: artist_with_role.try_into()?,
221 name: artist_with_role.artist_name().to_owned(),
222 role: artist_with_role.role(),
223 })
224 }
225}
226
227impl_try_from_repeated!(ArtistWithRoleMessage, ArtistsWithRole);
228
229impl TryFrom<&TopTracksMessage> for TopTracks {
230 type Error = librespot_core::Error;
231 fn try_from(top_tracks: &TopTracksMessage) -> Result<Self, Self::Error> {
232 Ok(Self {
233 country: top_tracks.country().to_owned(),
234 tracks: top_tracks.track.as_slice().try_into()?,
235 })
236 }
237}
238
239impl_try_from_repeated!(TopTracksMessage, CountryTopTracks);
240
241impl TryFrom<&AlbumGroupMessage> for AlbumGroup {
242 type Error = librespot_core::Error;
243 fn try_from(album_groups: &AlbumGroupMessage) -> Result<Self, Self::Error> {
244 Ok(Self(album_groups.album.as_slice().try_into()?))
245 }
246}
247
248impl AlbumGroups {
249 pub fn current_releases(&self) -> impl Iterator<Item = &SpotifyId> {
253 self.iter().filter_map(|agrp| agrp.first())
254 }
255}
256
257impl_try_from_repeated!(AlbumGroupMessage, AlbumGroups);
258
259impl From<&BiographyMessage> for Biography {
260 fn from(biography: &BiographyMessage) -> Self {
261 let portrait_group = biography
262 .portrait_group
263 .iter()
264 .map(|it| it.image.as_slice().into())
265 .collect();
266
267 Self {
268 text: biography.text().to_owned(),
269 portraits: biography.portrait.as_slice().into(),
270 portrait_group,
271 }
272 }
273}
274
275impl_from_repeated!(BiographyMessage, Biographies);
276
277impl TryFrom<&ActivityPeriodMessage> for ActivityPeriod {
278 type Error = librespot_core::Error;
279
280 fn try_from(period: &ActivityPeriodMessage) -> Result<Self, Self::Error> {
281 let activity_period = match (
282 period.has_decade(),
283 period.has_start_year(),
284 period.has_end_year(),
285 ) {
286 (true, false, false) => Self::Decade(period.decade().try_into()?),
288 (false, true, closed_period) => Self::Timespan {
289 start_year: period.start_year().try_into()?,
290 end_year: closed_period
291 .then(|| period.end_year().try_into())
292 .transpose()?,
293 },
294 _ => {
295 return Err(librespot_core::Error::failed_precondition(
296 "ActivityPeriod is expected to be either a decade or timespan",
297 ));
298 }
299 };
300 Ok(activity_period)
301 }
302}
303
304impl_try_from_repeated!(ActivityPeriodMessage, ActivityPeriods);