librespot/metadata/src/lib.rs

523 lines
14 KiB
Rust
Raw Normal View History

2021-01-21 21:07:16 +00:00
#![allow(clippy::unused_io_amount)]
2019-09-22 19:21:44 +00:00
#[macro_use]
extern crate log;
2021-01-21 21:07:16 +00:00
#[macro_use]
extern crate async_trait;
2017-08-03 19:10:48 +00:00
pub mod cover;
use std::collections::HashMap;
2015-06-23 14:38:29 +00:00
2019-09-16 19:00:09 +00:00
use librespot_core::mercury::MercuryError;
use librespot_core::session::Session;
use librespot_core::spclient::SpClientError;
use librespot_core::spotify_id::{FileId, SpotifyAudioType, SpotifyId};
use librespot_protocol as protocol;
use protobuf::{Message, ProtobufError};
use thiserror::Error;
2015-06-23 14:38:29 +00:00
pub use crate::protocol::metadata::AudioFile_Format as FileFormat;
2016-01-02 02:30:24 +00:00
fn countrylist_contains(list: &str, country: &str) -> bool {
list.chunks(2).any(|cc| cc == country)
}
fn parse_restrictions<'s, I>(restrictions: I, country: &str, catalogue: &str) -> bool
2018-02-12 20:02:27 +00:00
where
I: IntoIterator<Item = &'s protocol::metadata::Restriction>,
2016-01-02 15:19:39 +00:00
{
let mut forbidden = "".to_string();
let mut has_forbidden = false;
let mut allowed = "".to_string();
let mut has_allowed = false;
2018-02-12 20:02:27 +00:00
let rs = restrictions
.into_iter()
.filter(|r| r.get_catalogue_str().contains(&catalogue.to_owned()));
for r in rs {
if r.has_countries_forbidden() {
forbidden.push_str(r.get_countries_forbidden());
has_forbidden = true;
}
if r.has_countries_allowed() {
allowed.push_str(r.get_countries_allowed());
has_allowed = true;
}
}
!(has_forbidden && countrylist_contains(forbidden.as_str(), country)
|| has_allowed && !countrylist_contains(allowed.as_str(), country))
}
// A wrapper with fields the player needs
#[derive(Debug, Clone)]
pub struct AudioItem {
pub id: SpotifyId,
pub uri: String,
pub files: HashMap<FileFormat, FileId>,
pub name: String,
pub duration: i32,
pub available: bool,
pub alternatives: Option<Vec<SpotifyId>>,
}
impl AudioItem {
pub async fn get_audio_item(session: &Session, id: SpotifyId) -> Result<Self, MetadataError> {
match id.audio_type {
2021-01-21 21:07:16 +00:00
SpotifyAudioType::Track => Track::get_audio_item(session, id).await,
SpotifyAudioType::Podcast => Episode::get_audio_item(session, id).await,
SpotifyAudioType::NonPlayable => Err(MetadataError::NonPlayable),
}
}
}
pub type AudioItemResult = Result<AudioItem, MetadataError>;
2021-01-21 21:07:16 +00:00
#[async_trait]
trait AudioFiles {
async fn get_audio_item(session: &Session, id: SpotifyId) -> AudioItemResult;
}
2021-01-21 21:07:16 +00:00
#[async_trait]
impl AudioFiles for Track {
async fn get_audio_item(session: &Session, id: SpotifyId) -> AudioItemResult {
2021-01-21 21:07:16 +00:00
let item = Self::get(session, id).await?;
let alternatives = {
if item.alternatives.is_empty() {
None
} else {
Some(item.alternatives)
}
};
2021-01-21 21:07:16 +00:00
Ok(AudioItem {
2021-03-10 21:39:01 +00:00
id,
2021-01-21 21:07:16 +00:00
uri: format!("spotify:track:{}", id.to_base62()),
files: item.files,
name: item.name,
duration: item.duration,
available: item.available,
alternatives,
2021-01-21 21:07:16 +00:00
})
}
}
2021-01-21 21:07:16 +00:00
#[async_trait]
impl AudioFiles for Episode {
async fn get_audio_item(session: &Session, id: SpotifyId) -> AudioItemResult {
2021-01-21 21:07:16 +00:00
let item = Self::get(session, id).await?;
Ok(AudioItem {
2021-03-10 21:39:01 +00:00
id,
2021-01-21 21:07:16 +00:00
uri: format!("spotify:episode:{}", id.to_base62()),
files: item.files,
name: item.name,
duration: item.duration,
available: item.available,
alternatives: None,
})
}
}
2021-01-21 21:07:16 +00:00
#[derive(Debug, Error)]
pub enum MetadataError {
#[error("could not get metadata over HTTP: {0}")]
Http(#[from] SpClientError),
#[error("could not get metadata over Mercury: {0}")]
Mercury(#[from] MercuryError),
#[error("could not parse metadata: {0}")]
Parsing(#[from] ProtobufError),
#[error("response was empty")]
Empty,
#[error("audio item is non-playable")]
NonPlayable,
}
pub type MetadataResult = Result<bytes::Bytes, MetadataError>;
2021-01-21 21:07:16 +00:00
#[async_trait]
2018-02-12 20:02:27 +00:00
pub trait Metadata: Send + Sized + 'static {
type Message: protobuf::Message;
async fn request(session: &Session, id: SpotifyId) -> MetadataResult;
fn parse(msg: &Self::Message, session: &Session) -> Self;
async fn get(session: &Session, id: SpotifyId) -> Result<Self, MetadataError> {
let response = Self::request(session, id).await?;
let msg = Self::Message::parse_from_bytes(&response)?;
Ok(Self::parse(&msg, session))
}
2015-06-23 14:38:29 +00:00
}
// TODO: expose more fields available in the protobufs
#[derive(Debug, Clone)]
2015-06-23 14:38:29 +00:00
pub struct Track {
pub id: SpotifyId,
2015-06-23 14:38:29 +00:00
pub name: String,
2018-02-06 02:20:21 +00:00
pub duration: i32,
2015-06-23 14:38:29 +00:00
pub album: SpotifyId,
2016-01-25 09:44:48 +00:00
pub artists: Vec<SpotifyId>,
pub files: HashMap<FileFormat, FileId>,
pub alternatives: Vec<SpotifyId>,
pub available: bool,
}
#[derive(Debug, Clone)]
pub struct Album {
pub id: SpotifyId,
pub name: String,
pub artists: Vec<SpotifyId>,
2016-01-25 09:44:48 +00:00
pub tracks: Vec<SpotifyId>,
2016-01-02 15:19:39 +00:00
pub covers: Vec<FileId>,
2015-06-23 14:38:29 +00:00
}
2018-09-28 18:10:22 +00:00
#[derive(Debug, Clone)]
pub struct Episode {
pub id: SpotifyId,
pub name: String,
pub external_url: String,
pub duration: i32,
pub language: String,
pub show: SpotifyId,
pub files: HashMap<FileFormat, FileId>,
2018-09-28 18:10:22 +00:00
pub covers: Vec<FileId>,
pub available: bool,
pub explicit: bool,
}
#[derive(Debug, Clone)]
pub struct Show {
pub id: SpotifyId,
pub name: String,
pub publisher: String,
2018-09-28 18:10:22 +00:00
pub episodes: Vec<SpotifyId>,
pub covers: Vec<FileId>,
}
2019-08-05 21:42:16 +00:00
#[derive(Debug, Clone)]
pub struct Playlist {
2019-09-22 19:21:44 +00:00
pub revision: Vec<u8>,
2019-08-06 19:58:50 +00:00
pub user: String,
2019-08-05 21:42:16 +00:00
pub name: String,
pub tracks: Vec<SpotifyId>,
}
#[derive(Debug, Clone)]
pub struct Artist {
pub id: SpotifyId,
pub name: String,
2016-01-25 09:44:48 +00:00
pub top_tracks: Vec<SpotifyId>,
}
#[async_trait]
impl Metadata for Track {
2015-06-23 14:38:29 +00:00
type Message = protocol::metadata::Track;
async fn request(session: &Session, track_id: SpotifyId) -> MetadataResult {
session
.spclient()
.get_track_metadata(track_id)
.await
.map_err(MetadataError::Http)
}
fn parse(msg: &Self::Message, session: &Session) -> Self {
debug!("MESSAGE: {:?}", msg);
let country = session.country();
2016-01-25 09:44:48 +00:00
2018-07-03 11:08:42 +00:00
let artists = msg
.get_artist()
2018-02-12 20:02:27 +00:00
.iter()
.filter(|artist| artist.has_gid())
2018-02-25 02:04:07 +00:00
.map(|artist| SpotifyId::from_raw(artist.get_gid()).unwrap())
2018-02-12 20:02:27 +00:00
.collect::<Vec<_>>();
2016-01-25 09:44:48 +00:00
2018-07-03 11:08:42 +00:00
let files = msg
.get_file()
2018-02-12 20:02:27 +00:00
.iter()
.filter(|file| file.has_file_id())
.map(|file| {
let mut dst = [0u8; 20];
dst.clone_from_slice(file.get_file_id());
(file.get_format(), FileId(dst))
})
.collect();
2016-01-25 09:44:48 +00:00
2015-06-23 14:38:29 +00:00
Track {
2018-02-25 02:04:07 +00:00
id: SpotifyId::from_raw(msg.get_gid()).unwrap(),
2015-09-01 11:20:37 +00:00
name: msg.get_name().to_owned(),
2018-02-06 02:20:21 +00:00
duration: msg.get_duration(),
2018-02-25 02:04:07 +00:00
album: SpotifyId::from_raw(msg.get_album().get_gid()).unwrap(),
2021-03-10 21:39:01 +00:00
artists,
files,
2018-07-03 11:08:42 +00:00
alternatives: msg
.get_alternative()
2018-02-12 20:02:27 +00:00
.iter()
2018-02-25 02:04:07 +00:00
.map(|alt| SpotifyId::from_raw(alt.get_gid()).unwrap())
2018-02-12 20:02:27 +00:00
.collect(),
available: parse_restrictions(msg.get_restriction(), &country, "premium"),
2015-06-23 14:38:29 +00:00
}
}
}
#[async_trait]
impl Metadata for Album {
2015-06-23 14:38:29 +00:00
type Message = protocol::metadata::Album;
async fn request(session: &Session, album_id: SpotifyId) -> MetadataResult {
session
.spclient()
.get_album_metadata(album_id)
.await
.map_err(MetadataError::Http)
}
fn parse(msg: &Self::Message, _: &Session) -> Self {
2018-07-03 11:08:42 +00:00
let artists = msg
.get_artist()
2018-02-12 20:02:27 +00:00
.iter()
.filter(|artist| artist.has_gid())
2018-02-25 02:04:07 +00:00
.map(|artist| SpotifyId::from_raw(artist.get_gid()).unwrap())
2018-02-12 20:02:27 +00:00
.collect::<Vec<_>>();
2016-01-25 09:44:48 +00:00
2018-07-03 11:08:42 +00:00
let tracks = msg
.get_disc()
2018-02-12 20:02:27 +00:00
.iter()
.flat_map(|disc| disc.get_track())
.filter(|track| track.has_gid())
2018-02-25 02:04:07 +00:00
.map(|track| SpotifyId::from_raw(track.get_gid()).unwrap())
2018-02-12 20:02:27 +00:00
.collect::<Vec<_>>();
2016-01-25 09:44:48 +00:00
2018-07-03 11:08:42 +00:00
let covers = msg
.get_cover_group()
2018-02-12 20:02:27 +00:00
.get_image()
.iter()
.filter(|image| image.has_file_id())
.map(|image| {
let mut dst = [0u8; 20];
dst.clone_from_slice(image.get_file_id());
FileId(dst)
})
.collect::<Vec<_>>();
2016-01-25 09:44:48 +00:00
2015-06-23 14:38:29 +00:00
Album {
2018-02-25 02:04:07 +00:00
id: SpotifyId::from_raw(msg.get_gid()).unwrap(),
2015-09-01 11:20:37 +00:00
name: msg.get_name().to_owned(),
2021-03-10 21:39:01 +00:00
artists,
tracks,
covers,
2015-06-23 14:38:29 +00:00
}
}
}
#[async_trait]
2019-08-06 19:58:50 +00:00
impl Metadata for Playlist {
2019-08-05 21:42:16 +00:00
type Message = protocol::playlist4changes::SelectedListContent;
// TODO:
// * Add PlaylistAnnotate3 annotations.
// * Find spclient endpoint and upgrade to that.
async fn request(session: &Session, playlist_id: SpotifyId) -> MetadataResult {
let uri = format!("hm://playlist/v2/playlist/{}", playlist_id.to_base62());
let response = session.mercury().get(uri).await?;
match response.payload.first() {
Some(data) => Ok(data.to_vec().into()),
None => Err(MetadataError::Empty),
}
2019-08-06 19:58:50 +00:00
}
2019-08-05 21:42:16 +00:00
2019-08-05 21:42:16 +00:00
fn parse(msg: &Self::Message, _: &Session) -> Self {
let tracks = msg
.get_contents()
.get_items()
.iter()
.map(|item| {
2021-03-01 02:37:22 +00:00
let uri_split = item.get_uri().split(':');
2019-08-05 21:42:16 +00:00
let uri_parts: Vec<&str> = uri_split.collect();
SpotifyId::from_base62(uri_parts[2]).unwrap()
})
.collect::<Vec<_>>();
2020-01-17 17:11:07 +00:00
2019-09-22 19:21:44 +00:00
if tracks.len() != msg.get_length() as usize {
2020-01-17 17:11:07 +00:00
warn!(
"Got {} tracks, but the playlist should contain {} tracks.",
tracks.len(),
msg.get_length()
);
2019-09-22 19:21:44 +00:00
}
2019-08-05 21:42:16 +00:00
Playlist {
2019-09-22 19:21:44 +00:00
revision: msg.get_revision().to_vec(),
2019-08-05 21:42:16 +00:00
name: msg.get_attributes().get_name().to_owned(),
2021-03-10 21:39:01 +00:00
tracks,
2019-08-06 19:58:50 +00:00
user: msg.get_owner_username().to_string(),
2019-08-05 21:42:16 +00:00
}
}
}
#[async_trait]
impl Metadata for Artist {
2015-06-23 14:38:29 +00:00
type Message = protocol::metadata::Artist;
async fn request(session: &Session, artist_id: SpotifyId) -> MetadataResult {
session
.spclient()
.get_artist_metadata(artist_id)
.await
.map_err(MetadataError::Http)
2015-06-23 14:38:29 +00:00
}
2016-01-25 09:44:48 +00:00
fn parse(msg: &Self::Message, session: &Session) -> Self {
let country = session.country();
2016-01-25 09:44:48 +00:00
2018-07-03 11:08:42 +00:00
let top_tracks: Vec<SpotifyId> = match msg
.get_top_track()
2018-02-12 20:02:27 +00:00
.iter()
.find(|tt| !tt.has_country() || countrylist_contains(tt.get_country(), &country))
{
Some(tracks) => tracks
.get_track()
.iter()
.filter(|track| track.has_gid())
2018-02-25 02:04:07 +00:00
.map(|track| SpotifyId::from_raw(track.get_gid()).unwrap())
2018-02-12 20:02:27 +00:00
.collect::<Vec<_>>(),
None => Vec::new(),
};
2016-01-25 09:44:48 +00:00
Artist {
2018-02-25 02:04:07 +00:00
id: SpotifyId::from_raw(msg.get_gid()).unwrap(),
name: msg.get_name().to_owned(),
2021-03-10 21:39:01 +00:00
top_tracks,
2015-06-23 14:38:29 +00:00
}
}
}
2018-02-13 07:33:50 +00:00
2018-09-28 18:10:22 +00:00
// Podcast
#[async_trait]
2018-09-28 18:10:22 +00:00
impl Metadata for Episode {
type Message = protocol::metadata::Episode;
async fn request(session: &Session, episode_id: SpotifyId) -> MetadataResult {
session
.spclient()
.get_album_metadata(episode_id)
.await
.map_err(MetadataError::Http)
2018-09-28 18:10:22 +00:00
}
fn parse(msg: &Self::Message, session: &Session) -> Self {
let country = session.country();
let files = msg
.get_audio()
2018-09-28 18:10:22 +00:00
.iter()
.filter(|file| file.has_file_id())
.map(|file| {
let mut dst = [0u8; 20];
dst.clone_from_slice(file.get_file_id());
(file.get_format(), FileId(dst))
})
.collect();
let covers = msg
.get_cover_image()
2018-09-28 18:10:22 +00:00
.get_image()
.iter()
.filter(|image| image.has_file_id())
.map(|image| {
let mut dst = [0u8; 20];
dst.clone_from_slice(image.get_file_id());
FileId(dst)
})
.collect::<Vec<_>>();
Episode {
id: SpotifyId::from_raw(msg.get_gid()).unwrap(),
name: msg.get_name().to_owned(),
external_url: msg.get_external_url().to_owned(),
duration: msg.get_duration().to_owned(),
language: msg.get_language().to_owned(),
show: SpotifyId::from_raw(msg.get_show().get_gid()).unwrap(),
2021-03-10 21:39:01 +00:00
covers,
files,
2018-09-28 18:10:22 +00:00
available: parse_restrictions(msg.get_restriction(), &country, "premium"),
explicit: msg.get_explicit().to_owned(),
}
}
}
#[async_trait]
2018-09-28 18:10:22 +00:00
impl Metadata for Show {
type Message = protocol::metadata::Show;
async fn request(session: &Session, show_id: SpotifyId) -> MetadataResult {
session
.spclient()
.get_show_metadata(show_id)
.await
.map_err(MetadataError::Http)
2018-09-28 18:10:22 +00:00
}
fn parse(msg: &Self::Message, _: &Session) -> Self {
let episodes = msg
.get_episode()
.iter()
.filter(|episode| episode.has_gid())
.map(|episode| SpotifyId::from_raw(episode.get_gid()).unwrap())
.collect::<Vec<_>>();
let covers = msg
.get_cover_image()
2018-09-28 18:10:22 +00:00
.get_image()
.iter()
.filter(|image| image.has_file_id())
.map(|image| {
let mut dst = [0u8; 20];
dst.clone_from_slice(image.get_file_id());
FileId(dst)
})
.collect::<Vec<_>>();
Show {
id: SpotifyId::from_raw(msg.get_gid()).unwrap(),
name: msg.get_name().to_owned(),
publisher: msg.get_publisher().to_owned(),
2021-03-10 21:39:01 +00:00
episodes,
covers,
2018-09-28 18:10:22 +00:00
}
}
}
2018-02-13 07:33:50 +00:00
struct StrChunks<'s>(&'s str, usize);
trait StrChunksExt {
fn chunks(&self, size: usize) -> StrChunks;
}
impl StrChunksExt for str {
fn chunks(&self, size: usize) -> StrChunks {
StrChunks(self, size)
}
}
impl<'s> Iterator for StrChunks<'s> {
type Item = &'s str;
fn next(&mut self) -> Option<&'s str> {
let &mut StrChunks(data, size) = self;
if data.is_empty() {
None
} else {
let ret = Some(&data[..size]);
self.0 = &data[size..];
ret
}
}
}