add option to download and keep playlist order

This commit is contained in:
obito1903 2023-01-25 16:13:39 +01:00
parent 961d975540
commit 2387c64fe6

View file

@ -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;
} }