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, SpotifyUri};
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: SpotifyUri,
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: SpotifyUri,
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 = &SpotifyUri> {
144 self.albums.current_releases()
145 }
146
147 pub fn singles_current(&self) -> impl Iterator<Item = &SpotifyUri> {
151 self.singles.current_releases()
152 }
153
154 pub fn compilations_current(&self) -> impl Iterator<Item = &SpotifyUri> {
159 self.compilations.current_releases()
160 }
161
162 pub fn appears_on_albums_current(&self) -> impl Iterator<Item = &SpotifyUri> {
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_uri: &SpotifyUri) -> RequestResult {
175 let SpotifyUri::Artist { .. } = artist_uri else {
176 return Err(Error::invalid_argument("artist_uri"));
177 };
178
179 session.spclient().get_artist_metadata(artist_uri).await
180 }
181
182 fn parse(msg: &Self::Message, _: &SpotifyUri) -> Result<Self, Error> {
183 Self::try_from(msg)
184 }
185}
186
187impl TryFrom<&<Self as Metadata>::Message> for Artist {
188 type Error = librespot_core::Error;
189 fn try_from(artist: &<Self as Metadata>::Message) -> Result<Self, Self::Error> {
190 Ok(Self {
191 id: artist.try_into()?,
192 name: artist.name().to_owned(),
193 popularity: artist.popularity(),
194 top_tracks: artist.top_track.as_slice().try_into()?,
195 albums: artist.album_group.as_slice().try_into()?,
196 singles: artist.single_group.as_slice().try_into()?,
197 compilations: artist.compilation_group.as_slice().try_into()?,
198 appears_on_albums: artist.appears_on_group.as_slice().try_into()?,
199 external_ids: artist.external_id.as_slice().into(),
200 portraits: artist.portrait.as_slice().into(),
201 biographies: artist.biography.as_slice().into(),
202 activity_periods: artist.activity_period.as_slice().try_into()?,
203 restrictions: artist.restriction.as_slice().into(),
204 related: artist.related.as_slice().try_into()?,
205 is_portrait_album_cover: artist.is_portrait_album_cover(),
206 portrait_group: artist
207 .portrait_group
208 .get_or_default()
209 .image
210 .as_slice()
211 .into(),
212 sales_periods: artist.sale_period.as_slice().try_into()?,
213 availabilities: artist.availability.as_slice().try_into()?,
214 })
215 }
216}
217
218impl_try_from_repeated!(<Artist as Metadata>::Message, Artists);
219
220impl TryFrom<&ArtistWithRoleMessage> for ArtistWithRole {
221 type Error = librespot_core::Error;
222 fn try_from(artist_with_role: &ArtistWithRoleMessage) -> Result<Self, Self::Error> {
223 Ok(Self {
224 id: artist_with_role.try_into()?,
225 name: artist_with_role.artist_name().to_owned(),
226 role: artist_with_role.role(),
227 })
228 }
229}
230
231impl_try_from_repeated!(ArtistWithRoleMessage, ArtistsWithRole);
232
233impl TryFrom<&TopTracksMessage> for TopTracks {
234 type Error = librespot_core::Error;
235 fn try_from(top_tracks: &TopTracksMessage) -> Result<Self, Self::Error> {
236 Ok(Self {
237 country: top_tracks.country().to_owned(),
238 tracks: top_tracks.track.as_slice().try_into()?,
239 })
240 }
241}
242
243impl_try_from_repeated!(TopTracksMessage, CountryTopTracks);
244
245impl TryFrom<&AlbumGroupMessage> for AlbumGroup {
246 type Error = librespot_core::Error;
247 fn try_from(album_groups: &AlbumGroupMessage) -> Result<Self, Self::Error> {
248 Ok(Self(album_groups.album.as_slice().try_into()?))
249 }
250}
251
252impl AlbumGroups {
253 pub fn current_releases(&self) -> impl Iterator<Item = &SpotifyUri> {
257 self.iter().filter_map(|agrp| agrp.first())
258 }
259}
260
261impl_try_from_repeated!(AlbumGroupMessage, AlbumGroups);
262
263impl From<&BiographyMessage> for Biography {
264 fn from(biography: &BiographyMessage) -> Self {
265 let portrait_group = biography
266 .portrait_group
267 .iter()
268 .map(|it| it.image.as_slice().into())
269 .collect();
270
271 Self {
272 text: biography.text().to_owned(),
273 portraits: biography.portrait.as_slice().into(),
274 portrait_group,
275 }
276 }
277}
278
279impl_from_repeated!(BiographyMessage, Biographies);
280
281impl TryFrom<&ActivityPeriodMessage> for ActivityPeriod {
282 type Error = librespot_core::Error;
283
284 fn try_from(period: &ActivityPeriodMessage) -> Result<Self, Self::Error> {
285 let activity_period = match (
286 period.has_decade(),
287 period.has_start_year(),
288 period.has_end_year(),
289 ) {
290 (true, false, false) => Self::Decade(period.decade().try_into()?),
292 (false, true, closed_period) => Self::Timespan {
293 start_year: period.start_year().try_into()?,
294 end_year: closed_period
295 .then(|| period.end_year().try_into())
296 .transpose()?,
297 },
298 _ => {
299 return Err(librespot_core::Error::failed_precondition(
300 "ActivityPeriod is expected to be either a decade or timespan",
301 ));
302 }
303 };
304 Ok(activity_period)
305 }
306}
307
308impl_try_from_repeated!(ActivityPeriodMessage, ActivityPeriods);