From 9faaaae6d2afbd5ebfd7eb1df8b5a6548df8aaf3 Mon Sep 17 00:00:00 2001 From: HEnquist Date: Mon, 5 Aug 2019 23:42:16 +0200 Subject: [PATCH] Add basic support for playlists --- examples/playlist_tracks.rs | 49 ++++++++++++++++++++++++--- metadata/src/lib.rs | 33 +++++++++++++++--- protocol/src/playlist4changes.rs | 57 ++------------------------------ 3 files changed, 74 insertions(+), 65 deletions(-) diff --git a/examples/playlist_tracks.rs b/examples/playlist_tracks.rs index 5d6aec59..42bb0f54 100644 --- a/examples/playlist_tracks.rs +++ b/examples/playlist_tracks.rs @@ -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(); diff --git a/metadata/src/lib.rs b/metadata/src/lib.rs index b0a57fba..94a3512f 100644 --- a/metadata/src/lib.rs +++ b/metadata/src/lib.rs @@ -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> { + //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, @@ -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(), } } } diff --git a/protocol/src/playlist4changes.rs b/protocol/src/playlist4changes.rs index d2d55bbd..55fc9062 100644 --- a/protocol/src/playlist4changes.rs +++ b/protocol/src/playlist4changes.rs @@ -2759,7 +2759,6 @@ pub struct SelectedListContent { resolveAction: ::protobuf::RepeatedField, issues: ::protobuf::RepeatedField, nonces: ::std::vec::Vec, - 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 { ::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", 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 {