A decentralized music tracking and discovery platform built on AT Protocol 馃幍
at main 192 lines 6.6 kB view raw
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}