Streamline and refactor Podcast support,

<Metadata>
  Add publisher to `Show`
  Add `ALLOWED` to `PassthroughEnum`
This commit is contained in:
ashthespy 2019-10-08 00:27:26 +02:00
parent 0cb7a3f7c8
commit 8eb51e9b55
6 changed files with 135 additions and 88 deletions

View file

@ -2,7 +2,7 @@ use std;
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SpotifyTrackType {
pub enum SpotifyAudioType {
Track,
Podcast,
}
@ -10,7 +10,7 @@ pub enum SpotifyTrackType {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct SpotifyId {
pub id: u128,
pub track_type: SpotifyTrackType,
pub audio_type: SpotifyAudioType,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
@ -23,7 +23,7 @@ impl SpotifyId {
fn as_track(n: u128) -> SpotifyId {
SpotifyId {
id: n.to_owned(),
track_type: SpotifyTrackType::Track,
audio_type: SpotifyAudioType::Track,
}
}
@ -73,7 +73,7 @@ impl SpotifyId {
let parts = uri.split(":").collect::<Vec<&str>>();
if uri.contains(":show:") || uri.contains(":episode:") {
let mut spotify_id = SpotifyId::from_base62(parts[2]).unwrap();
spotify_id.track_type = SpotifyTrackType::Podcast;
let _ = std::mem::replace(&mut spotify_id.audio_type, SpotifyAudioType::Podcast);
Ok(spotify_id)
} else {
SpotifyId::from_base62(parts[2])

View file

@ -13,7 +13,7 @@ use linear_map::LinearMap;
use librespot_core::mercury::MercuryError;
use librespot_core::session::Session;
use librespot_core::spotify_id::{FileId, SpotifyId};
use librespot_core::spotify_id::{FileId, SpotifyAudioType, SpotifyId};
pub use protocol::metadata::AudioFile_Format as FileFormat;
@ -52,13 +52,78 @@ where
&& (!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: LinearMap<FileFormat, FileId>,
pub name: String,
pub available: bool,
pub alternatives: Option<Vec<SpotifyId>>,
}
impl AudioItem {
pub fn get_audio_item(
session: &Session,
id: SpotifyId,
) -> Box<dyn Future<Item = AudioItem, Error = MercuryError>> {
match id.audio_type {
SpotifyAudioType::Track => Track::get_audio_item(session, id),
SpotifyAudioType::Podcast => Episode::get_audio_item(session, id),
}
}
}
trait AudioFiles {
fn get_audio_item(
session: &Session,
id: SpotifyId,
) -> Box<dyn Future<Item = AudioItem, Error = MercuryError>>;
}
impl AudioFiles for Track {
fn get_audio_item(
session: &Session,
id: SpotifyId,
) -> Box<dyn Future<Item = AudioItem, Error = MercuryError>> {
Box::new(Self::get(session, id).and_then(move |item| {
Ok(AudioItem {
id: id,
uri: format!("spotify:track:{}", id.to_base62()),
files: item.files,
name: item.name,
available: item.available,
alternatives: Some(item.alternatives),
})
}))
}
}
impl AudioFiles for Episode {
fn get_audio_item(
session: &Session,
id: SpotifyId,
) -> Box<dyn Future<Item = AudioItem, Error = MercuryError>> {
Box::new(Self::get(session, id).and_then(move |item| {
Ok(AudioItem {
id: id,
uri: format!("spotify:episode:{}", id.to_base62()),
files: item.files,
name: item.name,
available: item.available,
alternatives: None,
})
}))
}
}
pub trait Metadata: Send + Sized + 'static {
type Message: protobuf::Message;
fn base_url() -> &'static str;
fn parse(msg: &Self::Message, session: &Session) -> Self;
fn get(session: &Session, id: SpotifyId) -> Box<Future<Item = Self, Error = MercuryError>> {
fn get(session: &Session, id: SpotifyId) -> Box<dyn Future<Item = Self, Error = MercuryError>> {
let uri = format!("{}/{}", Self::base_url(), id.to_base16());
let request = session.mercury().get(uri);
@ -111,6 +176,7 @@ pub struct Episode {
pub struct Show {
pub id: SpotifyId,
pub name: String,
pub publisher: String,
pub episodes: Vec<SpotifyId>,
pub covers: Vec<FileId>,
}
@ -323,6 +389,7 @@ impl Metadata for Show {
Show {
id: SpotifyId::from_raw(msg.get_gid()).unwrap(),
name: msg.get_name().to_owned(),
publisher: msg.get_publisher().to_owned(),
episodes: episodes,
covers: covers,
}

View file

@ -12,12 +12,12 @@ use std::time::Duration;
use config::{Bitrate, PlayerConfig};
use librespot_core::session::Session;
use librespot_core::spotify_id::{FileId, SpotifyId, SpotifyTrackType};
use librespot_core::spotify_id::SpotifyId;
use audio::{AudioDecrypt, AudioFile};
use audio::{VorbisDecoder, VorbisPacket};
use audio_backend::Sink;
use metadata::{Episode, FileFormat, Metadata, Track};
use metadata::{AudioItem, FileFormat};
use mixer::AudioFilter;
pub struct Player {
@ -512,87 +512,65 @@ impl PlayerInternal {
let _ = self.event_sender.unbounded_send(event.clone());
}
fn find_available_alternative<'a>(&self, track: &'a Track) -> Option<Cow<'a, Track>> {
if track.available {
Some(Cow::Borrowed(track))
fn find_available_alternative<'a>(&self, audio: &'a AudioItem) -> Option<Cow<'a, AudioItem>> {
if audio.available {
Some(Cow::Borrowed(audio))
} else {
let alternatives = track
.alternatives
.iter()
.map(|alt_id| Track::get(&self.session, *alt_id));
let alternatives = future::join_all(alternatives).wait().unwrap();
alternatives.into_iter().find(|alt| alt.available).map(Cow::Owned)
if let Some(alternatives) = &audio.alternatives {
let alternatives = alternatives
.iter()
.map(|alt_id| AudioItem::get_audio_item(&self.session, *alt_id));
let alternatives = future::join_all(alternatives).wait().unwrap();
alternatives.into_iter().find(|alt| alt.available).map(Cow::Owned)
} else {
None
}
}
}
fn get_file_id(&self, spotify_id: SpotifyId) -> Option<FileId> {
let file_id = match spotify_id.track_type {
SpotifyTrackType::Track => {
let track = Track::get(&self.session, spotify_id).wait().unwrap();
fn load_track(&self, spotify_id: SpotifyId, position: i64) -> Option<(Decoder, f32)> {
let audio = AudioItem::get_audio_item(&self.session, spotify_id)
.wait()
.unwrap();
info!("Loading <{}> with Spotify URI <{}>", audio.name, audio.uri);
info!(
"Loading track \"{}\" with Spotify URI \"spotify:track:{}\"",
track.name,
spotify_id.to_base62()
);
let track = match self.find_available_alternative(&track) {
Some(track) => track,
None => {
warn!("Track \"{}\" is not available", track.name);
return None;
}
};
let format = match self.config.bitrate {
Bitrate::Bitrate96 => FileFormat::OGG_VORBIS_96,
Bitrate::Bitrate160 => FileFormat::OGG_VORBIS_160,
Bitrate::Bitrate320 => FileFormat::OGG_VORBIS_320,
};
match track.files.get(&format) {
Some(&file_id) => file_id,
None => {
warn!("Track \"{}\" is not available in format {:?}", track.name, format);
return None;
}
}
}
// This should be refactored!
SpotifyTrackType::Podcast => {
let episode = Episode::get(&self.session, spotify_id).wait().unwrap();
info!("Episode {:?}", episode);
info!(
"Loading episode \"{}\" with Spotify URI \"spotify:episode:{}\"",
episode.name,
spotify_id.to_base62()
);
// Podcasts seem to have only 96 OGG_VORBIS support, other filetypes indicate
// AAC_24, MP4_128, MP4_128_DUAL, MP3_96 among others
let format = match self.config.bitrate {
_ => FileFormat::OGG_VORBIS_96,
};
match episode.files.get(&format) {
Some(&file_id) => file_id,
None => {
warn!(
"Episode \"{}\" is not available in format {:?}",
episode.name, format
);
return None;
}
}
let audio = match self.find_available_alternative(&audio) {
Some(audio) => audio,
None => {
warn!("<{}> is not available", audio.uri);
return None;
}
};
return Some(file_id);
}
// (Most) podcasts seem to support only 96 bit Vorbis, so fall back to it
let formats = match self.config.bitrate {
Bitrate::Bitrate96 => [
FileFormat::OGG_VORBIS_96,
FileFormat::OGG_VORBIS_160,
FileFormat::OGG_VORBIS_320,
],
Bitrate::Bitrate160 => [
FileFormat::OGG_VORBIS_160,
FileFormat::OGG_VORBIS_96,
FileFormat::OGG_VORBIS_320,
],
Bitrate::Bitrate320 => [
FileFormat::OGG_VORBIS_320,
FileFormat::OGG_VORBIS_160,
FileFormat::OGG_VORBIS_96,
],
};
let format = formats
.iter()
.find(|format| audio.files.contains_key(format))
.unwrap();
fn load_track(&self, spotify_id: SpotifyId, position: i64) -> Option<(Decoder, f32)> {
let file_id = self.get_file_id(spotify_id).unwrap();
info!("{:?} -> {:?}", spotify_id, file_id);
let file_id = match audio.files.get(&format) {
Some(&file_id) => file_id,
None => {
warn!("<{}> in not available in format {:?}", audio.name, format);
return None;
}
};
let key = self.session.audio_key().request(spotify_id, file_id);
let encrypted_file = AudioFile::open(&self.session, file_id);
@ -619,9 +597,7 @@ impl PlayerInternal {
Err(err) => error!("Vorbis error: {:?}", err),
}
}
// info!("Track \"{}\" loaded", track.name);
info!("<{}> loaded", audio.name);
Some((decoder, normalisation_factor))
}
}

View file

@ -3,7 +3,7 @@ pub const FILES: &'static [(&'static str, u32)] = &[
("proto/authentication.proto", 2098196376),
("proto/keyexchange.proto", 451735664),
("proto/mercury.proto", 709993906),
("proto/metadata.proto", 1409162985),
("proto/metadata.proto", 334477813),
("proto/pubsub.proto", 2686584829),
("proto/spirc.proto", 1587493382),
];

View file

@ -207,6 +207,7 @@ enum MediaType {
enum PassthroughEnum {
UNKNOWN = 0;
NONE = 1;
ALLOWED = 2;
}
message Episode {

View file

@ -8896,6 +8896,7 @@ impl ::protobuf::reflect::ProtobufValue for MediaType {
pub enum PassthroughEnum {
UNKNOWN = 0,
NONE = 1,
ALLOWED = 2,
}
impl ::protobuf::ProtobufEnum for PassthroughEnum {
@ -8907,6 +8908,7 @@ impl ::protobuf::ProtobufEnum for PassthroughEnum {
match value {
0 => ::std::option::Option::Some(PassthroughEnum::UNKNOWN),
1 => ::std::option::Option::Some(PassthroughEnum::NONE),
2 => ::std::option::Option::Some(PassthroughEnum::ALLOWED),
_ => ::std::option::Option::None
}
}
@ -8915,6 +8917,7 @@ impl ::protobuf::ProtobufEnum for PassthroughEnum {
static values: &'static [PassthroughEnum] = &[
PassthroughEnum::UNKNOWN,
PassthroughEnum::NONE,
PassthroughEnum::ALLOWED,
];
values
}
@ -9057,9 +9060,9 @@ static file_descriptor_proto_data: &'static [u8] = b"\
\rOriginalAudio\x12\x0e\n\x04uuid\x18\x01\x20\x01(\x0cB\0:\0*>\n\x10Cons\
umptionOrder\x12\x0e\n\nSEQUENTIAL\x10\x01\x12\x0c\n\x08EPISODIC\x10\x02\
\x12\n\n\x06RECENT\x10\x03\x1a\0*.\n\tMediaType\x12\t\n\x05MIXED\x10\0\
\x12\t\n\x05AUDIO\x10\x01\x12\t\n\x05VIDEO\x10\x02\x1a\0**\n\x0fPassthro\
ughEnum\x12\x0b\n\x07UNKNOWN\x10\0\x12\x08\n\x04NONE\x10\x01\x1a\0B\0b\
\x06proto2\
\x12\t\n\x05AUDIO\x10\x01\x12\t\n\x05VIDEO\x10\x02\x1a\0*7\n\x0fPassthro\
ughEnum\x12\x0b\n\x07UNKNOWN\x10\0\x12\x08\n\x04NONE\x10\x01\x12\x0b\n\
\x07ALLOWED\x10\x02\x1a\0B\0b\x06proto2\
";
static mut file_descriptor_proto_lazy: ::protobuf::lazy::Lazy<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::lazy::Lazy {