mirror of
https://github.com/librespot-org/librespot.git
synced 2024-12-18 17:11:53 +00:00
Add basic support for playlists
This commit is contained in:
parent
abd6ad6b25
commit
9faaaae6d2
3 changed files with 74 additions and 65 deletions
|
@ -1,10 +1,13 @@
|
||||||
|
#[macro_use]
|
||||||
extern crate log;
|
extern crate log;
|
||||||
extern crate env_logger;
|
extern crate env_logger;
|
||||||
|
|
||||||
extern crate librespot;
|
extern crate librespot;
|
||||||
extern crate tokio_core;
|
extern crate tokio_core;
|
||||||
|
//extern crate tokio_fs;
|
||||||
extern crate tokio_io;
|
extern crate tokio_io;
|
||||||
extern crate futures;
|
extern crate futures;
|
||||||
|
//extern crate futures_cpupool;
|
||||||
|
|
||||||
use std::env;
|
use std::env;
|
||||||
use tokio_core::reactor::Core;
|
use tokio_core::reactor::Core;
|
||||||
|
@ -13,7 +16,39 @@ use librespot::core::authentication::Credentials;
|
||||||
use librespot::core::config::SessionConfig;
|
use librespot::core::config::SessionConfig;
|
||||||
use librespot::core::session::Session;
|
use librespot::core::session::Session;
|
||||||
use librespot::core::spotify_id::SpotifyId;
|
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() {
|
fn main() {
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
|
@ -21,16 +56,20 @@ fn main() {
|
||||||
let handle = core.handle();
|
let handle = core.handle();
|
||||||
|
|
||||||
let session_config = SessionConfig::default();
|
let session_config = SessionConfig::default();
|
||||||
|
let mut player_config = PlayerConfig::default();
|
||||||
|
player_config.bitrate = Bitrate::Bitrate320;
|
||||||
|
|
||||||
let args: Vec<_> = env::args().collect();
|
let args: Vec<_> = env::args().collect();
|
||||||
if args.len() != 4 {
|
if args.len() != 5 {
|
||||||
println!("Usage: {} USERNAME PASSWORD PLAYLIST", args[0]);
|
println!("Usage: {} USERNAME PASSWORD PLAYLIST PLISTUSER", args[0]);
|
||||||
}
|
}
|
||||||
let username = args[1].to_owned();
|
let username = args[1].to_owned();
|
||||||
let password = args[2].to_owned();
|
let password = args[2].to_owned();
|
||||||
let credentials = Credentials::with_password(username, password);
|
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();
|
let uri_parts: Vec<&str> = uri_split.collect();
|
||||||
println!("{}, {}, {}",uri_parts[0], uri_parts[1], uri_parts[2]);
|
println!("{}, {}, {}",uri_parts[0], uri_parts[1], uri_parts[2]);
|
||||||
|
|
||||||
|
@ -40,7 +79,7 @@ fn main() {
|
||||||
.run(Session::connect(session_config, credentials, None, handle))
|
.run(Session::connect(session_config, credentials, None, handle))
|
||||||
.unwrap();
|
.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);
|
println!("{:?}",plist);
|
||||||
for track_id in plist.tracks {
|
for track_id in plist.tracks {
|
||||||
let plist_track = core.run(Track::get(&session, track_id)).unwrap();
|
let plist_track = core.run(Track::get(&session, track_id)).unwrap();
|
||||||
|
|
|
@ -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)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Track {
|
pub struct Track {
|
||||||
pub id: SpotifyId,
|
pub id: SpotifyId,
|
||||||
|
@ -192,7 +215,7 @@ pub struct Show {
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Playlist {
|
pub struct Playlist {
|
||||||
pub user: String,
|
//pub id: SpotifyId,
|
||||||
pub length: i32,
|
pub length: i32,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub tracks: Vec<SpotifyId>,
|
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;
|
type Message = protocol::playlist4changes::SelectedListContent;
|
||||||
|
|
||||||
fn request_url(id: SpotifyId) -> String {
|
fn base_url() -> &'static str {
|
||||||
format!("hm://playlist/v2/playlist/{}", id.to_base62())
|
"hm://playlist/'?from=' + from + '&length=' + length"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn parse(msg: &Self::Message, _: &Session) -> Self {
|
fn parse(msg: &Self::Message, _: &Session) -> Self {
|
||||||
|
|
||||||
let tracks = msg
|
let tracks = msg
|
||||||
|
@ -321,7 +345,6 @@ impl Metadata for Playlist {
|
||||||
name: msg.get_attributes().get_name().to_owned(),
|
name: msg.get_attributes().get_name().to_owned(),
|
||||||
length: msg.get_length(),
|
length: msg.get_length(),
|
||||||
tracks: tracks,
|
tracks: tracks,
|
||||||
user: msg.get_owner_username().to_string(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2759,7 +2759,6 @@ pub struct SelectedListContent {
|
||||||
resolveAction: ::protobuf::RepeatedField<super::playlist4issues::ClientResolveAction>,
|
resolveAction: ::protobuf::RepeatedField<super::playlist4issues::ClientResolveAction>,
|
||||||
issues: ::protobuf::RepeatedField<super::playlist4issues::ClientIssue>,
|
issues: ::protobuf::RepeatedField<super::playlist4issues::ClientIssue>,
|
||||||
nonces: ::std::vec::Vec<i32>,
|
nonces: ::std::vec::Vec<i32>,
|
||||||
owner_username: ::protobuf::SingularField<::std::string::String>,
|
|
||||||
// special fields
|
// special fields
|
||||||
pub unknown_fields: ::protobuf::UnknownFields,
|
pub unknown_fields: ::protobuf::UnknownFields,
|
||||||
pub cached_size: ::protobuf::CachedSize,
|
pub cached_size: ::protobuf::CachedSize,
|
||||||
|
@ -3133,42 +3132,6 @@ impl SelectedListContent {
|
||||||
pub fn take_nonces(&mut self) -> ::std::vec::Vec<i32> {
|
pub fn take_nonces(&mut self) -> ::std::vec::Vec<i32> {
|
||||||
::std::mem::replace(&mut self.nonces, ::std::vec::Vec::new())
|
::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 {
|
impl ::protobuf::Message for SelectedListContent {
|
||||||
|
@ -3266,9 +3229,6 @@ impl ::protobuf::Message for SelectedListContent {
|
||||||
14 => {
|
14 => {
|
||||||
::protobuf::rt::read_repeated_int32_into(wire_type, is, &mut self.nonces)?;
|
::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())?;
|
::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 {
|
for value in &self.nonces {
|
||||||
my_size += ::protobuf::rt::value_size(14, *value, ::protobuf::wire_format::WireTypeVarint);
|
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());
|
my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
|
||||||
self.cached_size.set(my_size);
|
self.cached_size.set(my_size);
|
||||||
my_size
|
my_size
|
||||||
|
@ -3389,9 +3346,6 @@ impl ::protobuf::Message for SelectedListContent {
|
||||||
for v in &self.nonces {
|
for v in &self.nonces {
|
||||||
os.write_int32(14, *v)?;
|
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())?;
|
os.write_unknown_fields(self.get_unknown_fields())?;
|
||||||
::std::result::Result::Ok(())
|
::std::result::Result::Ok(())
|
||||||
}
|
}
|
||||||
|
@ -3499,11 +3453,6 @@ impl ::protobuf::Message for SelectedListContent {
|
||||||
|m: &SelectedListContent| { &m.nonces },
|
|m: &SelectedListContent| { &m.nonces },
|
||||||
|m: &mut SelectedListContent| { &mut 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>(
|
::protobuf::reflect::MessageDescriptor::new::<SelectedListContent>(
|
||||||
"SelectedListContent",
|
"SelectedListContent",
|
||||||
fields,
|
fields,
|
||||||
|
@ -3539,7 +3488,6 @@ impl ::protobuf::Clear for SelectedListContent {
|
||||||
self.resolveAction.clear();
|
self.resolveAction.clear();
|
||||||
self.issues.clear();
|
self.issues.clear();
|
||||||
self.nonces.clear();
|
self.nonces.clear();
|
||||||
self.owner_username.clear();
|
|
||||||
self.unknown_fields.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\
|
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\
|
\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(\
|
\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\
|
\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\
|
\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\
|
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\
|
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\
|
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\
|
\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\
|
\x06nonces\x18\x0e\x20\x03(\x05B\0:\0B\0b\x06proto2\
|
||||||
\x20\x01(\tB\0:\0B\0b\x06proto2\
|
|
||||||
";
|
";
|
||||||
|
|
||||||
static mut file_descriptor_proto_lazy: ::protobuf::lazy::Lazy<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::lazy::Lazy {
|
static mut file_descriptor_proto_lazy: ::protobuf::lazy::Lazy<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::lazy::Lazy {
|
||||||
|
|
Loading…
Reference in a new issue