forked from
rocksky.app/rocksky
A decentralized music tracking and discovery platform built on AT Protocol 馃幍
1use serde::{Deserialize, Serialize};
2
3use crate::{
4 musicbrainz::{self, normalize_date},
5 spotify, xata,
6};
7
8#[derive(Debug, Deserialize, Clone)]
9pub struct Scrobble {
10 pub artist: String,
11 pub track: String,
12 pub timestamp: u64,
13 pub album: Option<String>,
14 pub context: Option<String>,
15 pub stream_id: Option<String>,
16 pub chosen_by_user: Option<u8>,
17 pub track_number: Option<u32>,
18 pub mbid: Option<String>,
19 pub album_artist: Option<String>,
20 pub duration: Option<u32>,
21 pub ignored: Option<bool>,
22}
23
24#[derive(Debug, Serialize, Deserialize, Default)]
25#[serde(rename_all = "camelCase")]
26pub struct Track {
27 pub title: String,
28 pub album: String,
29 pub artist: String,
30 pub album_artist: Option<String>,
31 pub duration: u32,
32 pub mbid: Option<String>,
33 pub track_number: u32,
34 pub release_date: Option<String>,
35 pub year: Option<u32>,
36 pub disc_number: u32,
37 pub album_art: Option<String>,
38 pub spotify_link: Option<String>,
39 pub label: Option<String>,
40 pub artist_picture: Option<String>,
41 pub timestamp: Option<u64>,
42 pub genres: Option<Vec<String>>,
43}
44
45impl From<xata::track::Track> for Track {
46 fn from(track: xata::track::Track) -> Self {
47 Track {
48 title: track.title,
49 album: track.album,
50 artist: track.artist,
51 album_artist: Some(track.album_artist),
52 album_art: track.album_art,
53 spotify_link: track.spotify_link,
54 label: track.label,
55 artist_picture: None,
56 timestamp: None,
57 duration: track.duration as u32,
58 mbid: track.mb_id,
59 track_number: track.track_number as u32,
60 disc_number: track.disc_number as u32,
61 year: None,
62 release_date: None,
63 genres: None,
64 }
65 }
66}
67
68impl From<musicbrainz::recording::Recording> for Track {
69 fn from(recording: musicbrainz::recording::Recording) -> Self {
70 let artist_credit = recording
71 .artist_credit
72 .unwrap_or_default()
73 .first()
74 .map(|credit| credit.name.clone())
75 .unwrap_or_default();
76 let releases = recording.releases.unwrap_or_default();
77 let album_artist = releases.first().and_then(|release| {
78 let credits = release.artist_credit.clone().unwrap_or_default();
79 credits.first().map(|credit| credit.name.clone())
80 });
81 let album = releases
82 .first()
83 .map(|release| release.title.clone())
84 .unwrap_or_default();
85 let release_date = releases
86 .first()
87 .and_then(|release| release.date.clone())
88 .and_then(|date| normalize_date(Some(&date)).unwrap_or(None));
89 tracing::info!(release_date = ?release_date, "Normalized release date:");
90 Track {
91 title: recording.title.clone(),
92 album,
93 artist: artist_credit,
94 album_artist,
95 duration: recording.length.unwrap_or_default(),
96 year: release_date
97 .as_ref()
98 .and_then(|date| date.split('-').next())
99 .and_then(|year| year.parse::<u32>().ok()),
100 release_date: release_date.clone(),
101 track_number: releases
102 .first()
103 .and_then(|release| {
104 release
105 .media
106 .as_ref()
107 .and_then(|media| media.first())
108 .and_then(|media| {
109 media
110 .track
111 .as_ref()
112 .and_then(|tracks| tracks.first())
113 .map(|track| track.number.parse::<u32>().unwrap())
114 })
115 })
116 .unwrap_or_default(),
117 disc_number: releases
118 .first()
119 .and_then(|release| {
120 release
121 .media
122 .as_ref()
123 .and_then(|media| media.first())
124 .map(|media| media.position.unwrap_or(1) as u32)
125 })
126 .unwrap_or_default(),
127 ..Default::default()
128 }
129 }
130}
131
132impl From<&spotify::types::Track> for Track {
133 fn from(track: &spotify::types::Track) -> Self {
134 Track {
135 title: track.name.clone(),
136 album: track.album.name.clone(),
137 artist: track
138 .artists
139 .iter()
140 .map(|artist| artist.name.clone())
141 .collect::<Vec<_>>()
142 .join(", "),
143 album_artist: track
144 .album
145 .artists
146 .first()
147 .map(|artist| artist.name.clone()),
148 duration: track.duration_ms as u32,
149 album_art: track.album.images.first().map(|image| image.url.clone()),
150 spotify_link: Some(track.external_urls.spotify.clone()),
151 artist_picture: track.album.artists.first().and_then(|artist| {
152 artist
153 .images
154 .as_ref()
155 .and_then(|images| images.first().map(|image| image.url.clone()))
156 }),
157 track_number: track.track_number,
158 disc_number: track.disc_number,
159 release_date: match track.album.release_date_precision.as_str() {
160 "day" => Some(track.album.release_date.clone()),
161 _ => None,
162 },
163 year: match track.album.release_date_precision.as_str() {
164 "day" => Some(
165 track
166 .album
167 .release_date
168 .split('-')
169 .next()
170 .unwrap()
171 .parse::<u32>()
172 .unwrap(),
173 ),
174 "year" => Some(track.album.release_date.parse::<u32>().unwrap()),
175 _ => None,
176 },
177 label: track.album.label.clone(),
178 genres: track
179 .album
180 .artists
181 .first()
182 .and_then(|artist| artist.genres.clone()),
183 ..Default::default()
184 }
185 }
186}
187
188impl From<spotify::types::Track> for Track {
189 fn from(track: spotify::types::Track) -> Self {
190 Track::from(&track)
191 }
192}