Add basic support for playlists

This commit is contained in:
HEnquist 2019-08-05 23:42:16 +02:00 committed by henenq
parent abd6ad6b25
commit 9faaaae6d2
3 changed files with 74 additions and 65 deletions

View file

@ -1,10 +1,13 @@
#[macro_use]
extern crate log;
extern crate env_logger;
extern crate librespot;
extern crate tokio_core;
//extern crate tokio_fs;
extern crate tokio_io;
extern crate futures;
//extern crate futures_cpupool;
use std::env;
use tokio_core::reactor::Core;
@ -13,7 +16,39 @@ use librespot::core::authentication::Credentials;
use librespot::core::config::SessionConfig;
use librespot::core::session::Session;
use librespot::core::spotify_id::SpotifyId;
use librespot::metadata::{Metadata, Track, Playlist};
use librespot::playback::config::PlayerConfig;
use librespot::playback::config::Bitrate;
use librespot::metadata::{FileFormat, Metadata, PlaylistMeta, Track, Album, Artist, Playlist};
/*
fn make_list_playlist(core: &mut Core, session: &Session, uri: &str) -> TrackList {
let mut tracks = Vec::new();
let mut fnames = Vec::new();
let plist_uri = SpotifyId::from_base62(&uri).unwrap();
let plist = core.run(Playlist::get(&session, plist_uri)).unwrap();
println!("album name: {}",plist.name);
let plist_name = &plist.name;
for (i, track_id) in plist.tracks.iter().enumerate() {
let plist_track = core.run(Track::get(&session, *track_id)).unwrap();
//println!("album track: {} - {}",i+1, alb_track.name);
let artist = core.run(Artist::get(&session, plist_track.artists[0])).unwrap();
println!("track artist: {}",artist.name);
tracks.push(plist_track.id);
let filename = format!("{} - {}.ogg",&artist.name, alb_track.name);
fnames.push(filename);
}
let ntr = plist.tracks.len();
let folder = format!("{}",plist_name);
let mut tlist = TrackList::new(ntr, folder, tracks, fnames);
tlist
}
*/
fn main() {
env_logger::init();
@ -21,16 +56,20 @@ fn main() {
let handle = core.handle();
let session_config = SessionConfig::default();
let mut player_config = PlayerConfig::default();
player_config.bitrate = Bitrate::Bitrate320;
let args: Vec<_> = env::args().collect();
if args.len() != 4 {
println!("Usage: {} USERNAME PASSWORD PLAYLIST", args[0]);
if args.len() != 5 {
println!("Usage: {} USERNAME PASSWORD PLAYLIST PLISTUSER", args[0]);
}
let username = args[1].to_owned();
let password = args[2].to_owned();
let credentials = Credentials::with_password(username, password);
let uri_split = args[3].split(":");
let plist_owner = args[4].to_string();
let mut uri_split = args[3].split(":");
let uri_parts: Vec<&str> = uri_split.collect();
println!("{}, {}, {}",uri_parts[0], uri_parts[1], uri_parts[2]);
@ -40,7 +79,7 @@ fn main() {
.run(Session::connect(session_config, credentials, None, handle))
.unwrap();
let plist = core.run(Playlist::get(&session, plist_uri)).unwrap();
let plist = core.run(Playlist::get(&session, plist_uri, plist_owner, 0, 100)).unwrap();
println!("{:?}",plist);
for track_id in plist.tracks {
let plist_track = core.run(Track::get(&session, track_id)).unwrap();

View file

@ -146,6 +146,29 @@ pub trait Metadata: Send + Sized + 'static {
}
}
pub trait PlaylistMeta: 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, user: String, start: i32, len: i32) -> Box<Future<Item = Self, Error = MercuryError>> {
//let uri = format!("hm://playlist/{}?from={}&length={}",id.to_base62(), 0, 100);
let uri = format!("hm://playlist/user/{}/playlist/{}?from={}&length={}", user, id.to_base62(), start, len);
println!("request uri: {}", uri);
let request = session.mercury().get(uri);
println!("a");
let session = session.clone();
Box::new(request.and_then(move |response| {
println!("{:?}", response);
let data = response.payload.first().expect("Empty payload");
let msg: Self::Message = protobuf::parse_from_bytes(data).unwrap();
println!("{:?}", msg);
Ok(Self::parse(&msg, &session))
}))
}
}
#[derive(Debug, Clone)]
pub struct Track {
pub id: SpotifyId,
@ -192,7 +215,7 @@ pub struct Show {
#[derive(Debug, Clone)]
pub struct Playlist {
pub user: String,
//pub id: SpotifyId,
pub length: i32,
pub name: String,
pub tracks: Vec<SpotifyId>,
@ -295,13 +318,14 @@ impl Metadata for Album {
}
}
impl Metadata for Playlist {
impl PlaylistMeta for Playlist {
type Message = protocol::playlist4changes::SelectedListContent;
fn request_url(id: SpotifyId) -> String {
format!("hm://playlist/v2/playlist/{}", id.to_base62())
fn base_url() -> &'static str {
"hm://playlist/'?from=' + from + '&length=' + length"
}
fn parse(msg: &Self::Message, _: &Session) -> Self {
let tracks = msg
@ -321,7 +345,6 @@ impl Metadata for Playlist {
name: msg.get_attributes().get_name().to_owned(),
length: msg.get_length(),
tracks: tracks,
user: msg.get_owner_username().to_string(),
}
}
}

View file

@ -2759,7 +2759,6 @@ pub struct SelectedListContent {
resolveAction: ::protobuf::RepeatedField<super::playlist4issues::ClientResolveAction>,
issues: ::protobuf::RepeatedField<super::playlist4issues::ClientIssue>,
nonces: ::std::vec::Vec<i32>,
owner_username: ::protobuf::SingularField<::std::string::String>,
// special fields
pub unknown_fields: ::protobuf::UnknownFields,
pub cached_size: ::protobuf::CachedSize,
@ -3133,42 +3132,6 @@ impl SelectedListContent {
pub fn take_nonces(&mut self) -> ::std::vec::Vec<i32> {
::std::mem::replace(&mut self.nonces, ::std::vec::Vec::new())
}
// optional string owner_username = 16;
pub fn get_owner_username(&self) -> &str {
match self.owner_username.as_ref() {
Some(v) => &v,
None => "",
}
}
pub fn clear_owner_username(&mut self) {
self.owner_username.clear();
}
pub fn has_owner_username(&self) -> bool {
self.owner_username.is_some()
}
// Param is passed by value, moved
pub fn set_owner_username(&mut self, v: ::std::string::String) {
self.owner_username = ::protobuf::SingularField::some(v);
}
// Mutable pointer to the field.
// If field is not initialized, it is initialized with default value first.
pub fn mut_owner_username(&mut self) -> &mut ::std::string::String {
if self.owner_username.is_none() {
self.owner_username.set_default();
}
self.owner_username.as_mut().unwrap()
}
// Take field
pub fn take_owner_username(&mut self) -> ::std::string::String {
self.owner_username.take().unwrap_or_else(|| ::std::string::String::new())
}
}
impl ::protobuf::Message for SelectedListContent {
@ -3266,9 +3229,6 @@ impl ::protobuf::Message for SelectedListContent {
14 => {
::protobuf::rt::read_repeated_int32_into(wire_type, is, &mut self.nonces)?;
},
16 => {
::protobuf::rt::read_singular_string_into(wire_type, is, &mut self.owner_username)?;
},
_ => {
::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
},
@ -3327,9 +3287,6 @@ impl ::protobuf::Message for SelectedListContent {
for value in &self.nonces {
my_size += ::protobuf::rt::value_size(14, *value, ::protobuf::wire_format::WireTypeVarint);
};
if let Some(ref v) = self.owner_username.as_ref() {
my_size += ::protobuf::rt::string_size(16, &v);
}
my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
self.cached_size.set(my_size);
my_size
@ -3389,9 +3346,6 @@ impl ::protobuf::Message for SelectedListContent {
for v in &self.nonces {
os.write_int32(14, *v)?;
};
if let Some(ref v) = self.owner_username.as_ref() {
os.write_string(16, &v)?;
}
os.write_unknown_fields(self.get_unknown_fields())?;
::std::result::Result::Ok(())
}
@ -3499,11 +3453,6 @@ impl ::protobuf::Message for SelectedListContent {
|m: &SelectedListContent| { &m.nonces },
|m: &mut SelectedListContent| { &mut m.nonces },
));
fields.push(::protobuf::reflect::accessor::make_singular_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
"owner_username",
|m: &SelectedListContent| { &m.owner_username },
|m: &mut SelectedListContent| { &mut m.owner_username },
));
::protobuf::reflect::MessageDescriptor::new::<SelectedListContent>(
"SelectedListContent",
fields,
@ -3539,7 +3488,6 @@ impl ::protobuf::Clear for SelectedListContent {
self.resolveAction.clear();
self.issues.clear();
self.nonces.clear();
self.owner_username.clear();
self.unknown_fields.clear();
}
}
@ -3586,7 +3534,7 @@ static file_descriptor_proto_data: &'static [u8] = b"\
eltaB\0\x12\x20\n\x16wantResultingRevisions\x18\x03\x20\x01(\x08B\0\x12\
\x18\n\x0ewantSyncResult\x18\x04\x20\x01(\x08B\0\x12\x19\n\x04dump\x18\
\x05\x20\x01(\x0b2\t.ListDumpB\0\x12\x10\n\x06nonces\x18\x06\x20\x03(\
\x05B\0:\0\"\xa1\x03\n\x13SelectedListContent\x12\x12\n\x08revision\x18\
\x05B\0:\0\"\x87\x03\n\x13SelectedListContent\x12\x12\n\x08revision\x18\
\x01\x20\x01(\x0cB\0\x12\x10\n\x06length\x18\x02\x20\x01(\x05B\0\x12%\n\
\nattributes\x18\x03\x20\x01(\x0b2\x0f.ListAttributesB\0\x12!\n\x08check\
sum\x18\x04\x20\x01(\x0b2\r.ListChecksumB\0\x12\x1e\n\x08contents\x18\
@ -3596,8 +3544,7 @@ static file_descriptor_proto_data: &'static [u8] = b"\
ipleHeads\x18\t\x20\x01(\x08B\0\x12\x12\n\x08upToDate\x18\n\x20\x01(\x08\
B\0\x12-\n\rresolveAction\x18\x0c\x20\x03(\x0b2\x14.ClientResolveActionB\
\0\x12\x1e\n\x06issues\x18\r\x20\x03(\x0b2\x0c.ClientIssueB\0\x12\x10\n\
\x06nonces\x18\x0e\x20\x03(\x05B\0\x12\x18\n\x0eowner_username\x18\x10\
\x20\x01(\tB\0:\0B\0b\x06proto2\
\x06nonces\x18\x0e\x20\x03(\x05B\0:\0B\0b\x06proto2\
";
static mut file_descriptor_proto_lazy: ::protobuf::lazy::Lazy<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::lazy::Lazy {