mirror of
https://github.com/GuillemCastro/spotify-dl.git
synced 2024-11-22 10:20:26 +01:00
add option to download and keep playlist order
This commit is contained in:
parent
961d975540
commit
2387c64fe6
1 changed files with 67 additions and 26 deletions
93
src/main.rs
93
src/main.rs
|
@ -2,25 +2,28 @@ mod file_sink;
|
||||||
|
|
||||||
extern crate rpassword;
|
extern crate rpassword;
|
||||||
|
|
||||||
use std::{path::PathBuf};
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use librespot::{core::authentication::Credentials, metadata::Playlist};
|
|
||||||
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::playback::config::PlayerConfig;
|
use librespot::playback::config::PlayerConfig;
|
||||||
|
use librespot::{core::authentication::Credentials, metadata::Playlist};
|
||||||
|
|
||||||
|
use librespot::playback::audio_backend::Open;
|
||||||
use librespot::playback::player::Player;
|
use librespot::playback::player::Player;
|
||||||
use librespot::playback::audio_backend::{Open};
|
|
||||||
|
|
||||||
use librespot::metadata::{Track, Artist, Metadata, Album};
|
use librespot::metadata::{Album, Artist, Metadata, Track};
|
||||||
|
|
||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
|
|
||||||
use indicatif::{ProgressBar, ProgressStyle};
|
use indicatif::{ProgressBar, ProgressStyle};
|
||||||
|
|
||||||
#[derive(Debug, StructOpt)]
|
#[derive(Debug, StructOpt)]
|
||||||
#[structopt(name = "spotify-dl", about = "A commandline utility to download music directly from Spotify")]
|
#[structopt(
|
||||||
|
name = "spotify-dl",
|
||||||
|
about = "A commandline utility to download music directly from Spotify"
|
||||||
|
)]
|
||||||
struct Opt {
|
struct Opt {
|
||||||
#[structopt(help = "A list of Spotify URIs (songs, podcasts or playlists)")]
|
#[structopt(help = "A list of Spotify URIs (songs, podcasts or playlists)")]
|
||||||
tracks: Vec<String>,
|
tracks: Vec<String>,
|
||||||
|
@ -28,8 +31,19 @@ struct Opt {
|
||||||
username: String,
|
username: String,
|
||||||
#[structopt(short = "p", long = "password", help = "Your Spotify password")]
|
#[structopt(short = "p", long = "password", help = "Your Spotify password")]
|
||||||
password: Option<String>,
|
password: Option<String>,
|
||||||
#[structopt(short = "d", long = "destination", default_value = ".", help = "The directory where the songs will be downloaded")]
|
#[structopt(
|
||||||
destination: String
|
short = "d",
|
||||||
|
long = "destination",
|
||||||
|
default_value = ".",
|
||||||
|
help = "The directory where the songs will be downloaded"
|
||||||
|
)]
|
||||||
|
destination: String,
|
||||||
|
#[structopt(
|
||||||
|
short = "o",
|
||||||
|
long = "ordered",
|
||||||
|
help = "Download songs in the order they are in the playlist, prfixing the filename with the track number"
|
||||||
|
)]
|
||||||
|
ordered: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -41,11 +55,18 @@ pub struct TrackMetadata {
|
||||||
|
|
||||||
async fn create_session(credentials: Credentials) -> Session {
|
async fn create_session(credentials: Credentials) -> Session {
|
||||||
let session_config = SessionConfig::default();
|
let session_config = SessionConfig::default();
|
||||||
let session = Session::connect(session_config, credentials, None).await.unwrap();
|
let session = Session::connect(session_config, credentials, None)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
session
|
session
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn download_tracks(session: &Session, destination: PathBuf, tracks: Vec<SpotifyId>) {
|
async fn download_tracks(
|
||||||
|
session: &Session,
|
||||||
|
destination: PathBuf,
|
||||||
|
tracks: Vec<SpotifyId>,
|
||||||
|
ordered: bool,
|
||||||
|
) {
|
||||||
let player_config = PlayerConfig::default();
|
let player_config = PlayerConfig::default();
|
||||||
let bar_style = ProgressStyle::default_bar()
|
let bar_style = ProgressStyle::default_bar()
|
||||||
.template("[{elapsed_precise}] {bar:40.cyan/blue} {pos:>7}/{len:7} (ETA: {eta}) {msg}")
|
.template("[{elapsed_precise}] {bar:40.cyan/blue} {pos:>7}/{len:7} (ETA: {eta}) {msg}")
|
||||||
|
@ -54,14 +75,14 @@ async fn download_tracks(session: &Session, destination: PathBuf, tracks: Vec<Sp
|
||||||
bar.set_style(bar_style);
|
bar.set_style(bar_style);
|
||||||
bar.enable_steady_tick(500);
|
bar.enable_steady_tick(500);
|
||||||
|
|
||||||
for track in tracks {
|
for (i, track) in tracks.iter().enumerate() {
|
||||||
let track_item = Track::get(&session, track).await.unwrap();
|
let track_item = Track::get(&session, *track).await.unwrap();
|
||||||
let artist_name: String;
|
let artist_name: String;
|
||||||
|
|
||||||
let mut metadata = TrackMetadata {
|
let mut metadata = TrackMetadata {
|
||||||
artists: Vec::new(),
|
artists: Vec::new(),
|
||||||
track_name: track_item.name,
|
track_name: track_item.name,
|
||||||
album: Album::get(session, track_item.album).await.unwrap().name
|
album: Album::get(session, track_item.album).await.unwrap().name,
|
||||||
};
|
};
|
||||||
if track_item.artists.len() > 1 {
|
if track_item.artists.len() > 1 {
|
||||||
let mut tmp: String = String::new();
|
let mut tmp: String = String::new();
|
||||||
|
@ -72,22 +93,33 @@ async fn download_tracks(session: &Session, destination: PathBuf, tracks: Vec<Sp
|
||||||
tmp.push_str(", ");
|
tmp.push_str(", ");
|
||||||
}
|
}
|
||||||
artist_name = String::from(tmp.trim_end_matches(", "));
|
artist_name = String::from(tmp.trim_end_matches(", "));
|
||||||
}
|
} else {
|
||||||
else {
|
artist_name = Artist::get(&session, track_item.artists[0])
|
||||||
artist_name = Artist::get(&session, track_item.artists[0]).await.unwrap().name;
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.name;
|
||||||
metadata.artists.push(artist_name.clone());
|
metadata.artists.push(artist_name.clone());
|
||||||
}
|
}
|
||||||
let full_track_name = format!("{} - {}", artist_name, metadata.track_name);
|
let full_track_name = format!("{} - {}", artist_name, metadata.track_name);
|
||||||
let filename = format!("{}.flac", full_track_name);
|
let filename: String;
|
||||||
|
if ordered {
|
||||||
|
filename = format!("{:02} - {}.flac", i + 1, full_track_name);
|
||||||
|
} else {
|
||||||
|
filename = format!("{}.flac", full_track_name);
|
||||||
|
}
|
||||||
let joined_path = destination.join(&filename);
|
let joined_path = destination.join(&filename);
|
||||||
let path = joined_path.to_str().unwrap();
|
let path = joined_path.to_str().unwrap();
|
||||||
bar.set_message(full_track_name.as_str());
|
bar.set_message(full_track_name.as_str());
|
||||||
let mut file_sink = file_sink::FileSink::open(Some(path.to_owned()), librespot::playback::config::AudioFormat::S16);
|
let mut file_sink = file_sink::FileSink::open(
|
||||||
|
Some(path.to_owned()),
|
||||||
|
librespot::playback::config::AudioFormat::S16,
|
||||||
|
);
|
||||||
file_sink.add_metadata(metadata);
|
file_sink.add_metadata(metadata);
|
||||||
let (mut player, _) = Player::new(player_config.clone(), session.clone(), None, move || {
|
let (mut player, _) =
|
||||||
Box::new(file_sink)
|
Player::new(player_config.clone(), session.clone(), None, move || {
|
||||||
});
|
Box::new(file_sink)
|
||||||
player.load(track, true, 0);
|
});
|
||||||
|
player.load(*track, true, 0);
|
||||||
player.await_end_of_track().await;
|
player.await_end_of_track().await;
|
||||||
player.stop();
|
player.stop();
|
||||||
bar.inc(1);
|
bar.inc(1);
|
||||||
|
@ -97,11 +129,12 @@ async fn download_tracks(session: &Session, destination: PathBuf, tracks: Vec<Sp
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
|
|
||||||
let opt = Opt::from_args();
|
let opt = Opt::from_args();
|
||||||
|
|
||||||
let username = opt.username;
|
let username = opt.username;
|
||||||
let password = opt.password.unwrap_or_else(|| rpassword::read_password_from_tty(Some("Password: ")).unwrap());
|
let password = opt
|
||||||
|
.password
|
||||||
|
.unwrap_or_else(|| rpassword::read_password_from_tty(Some("Password: ")).unwrap());
|
||||||
let credentials = Credentials::with_password(username, password);
|
let credentials = Credentials::with_password(username, password);
|
||||||
|
|
||||||
let session = create_session(credentials.clone()).await;
|
let session = create_session(credentials.clone()).await;
|
||||||
|
@ -120,7 +153,10 @@ async fn main() {
|
||||||
librespot::core::spotify_id::SpotifyAudioType::NonPlayable => {
|
librespot::core::spotify_id::SpotifyAudioType::NonPlayable => {
|
||||||
match Playlist::get(&session, track).await {
|
match Playlist::get(&session, track).await {
|
||||||
Ok(mut playlist) => {
|
Ok(mut playlist) => {
|
||||||
println!("Adding all songs from playlist {} (by {}) to the queue", &playlist.name, &playlist.user);
|
println!(
|
||||||
|
"Adding all songs from playlist {} (by {}) to the queue",
|
||||||
|
&playlist.name, &playlist.user
|
||||||
|
);
|
||||||
tracks.append(&mut playlist.tracks);
|
tracks.append(&mut playlist.tracks);
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
|
@ -131,6 +167,11 @@ async fn main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
download_tracks(&session, PathBuf::from(opt.destination), tracks).await;
|
download_tracks(
|
||||||
|
&session,
|
||||||
|
PathBuf::from(opt.destination),
|
||||||
|
tracks,
|
||||||
|
opt.ordered,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue