From 2387c64fe63fab77d64a3f0b643e0620bdc05cd1 Mon Sep 17 00:00:00 2001 From: obito1903 Date: Wed, 25 Jan 2023 16:13:39 +0100 Subject: [PATCH 1/3] add option to download and keep playlist order --- src/main.rs | 93 ++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 67 insertions(+), 26 deletions(-) diff --git a/src/main.rs b/src/main.rs index c04a4b1..614a27d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,25 +2,28 @@ mod file_sink; 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::session::Session; use librespot::core::spotify_id::SpotifyId; 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::audio_backend::{Open}; -use librespot::metadata::{Track, Artist, Metadata, Album}; +use librespot::metadata::{Album, Artist, Metadata, Track}; use structopt::StructOpt; use indicatif::{ProgressBar, ProgressStyle}; #[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 { #[structopt(help = "A list of Spotify URIs (songs, podcasts or playlists)")] tracks: Vec, @@ -28,8 +31,19 @@ struct Opt { username: String, #[structopt(short = "p", long = "password", help = "Your Spotify password")] password: Option, - #[structopt(short = "d", long = "destination", default_value = ".", help = "The directory where the songs will be downloaded")] - destination: String + #[structopt( + 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)] @@ -41,11 +55,18 @@ pub struct TrackMetadata { async fn create_session(credentials: Credentials) -> Session { 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 } -async fn download_tracks(session: &Session, destination: PathBuf, tracks: Vec) { +async fn download_tracks( + session: &Session, + destination: PathBuf, + tracks: Vec, + ordered: bool, +) { let player_config = PlayerConfig::default(); let bar_style = ProgressStyle::default_bar() .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 1 { let mut tmp: String = String::new(); @@ -72,22 +93,33 @@ async fn download_tracks(session: &Session, destination: PathBuf, tracks: Vec { match Playlist::get(&session, track).await { 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); } 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; } From 58125236e13c38d5d13f1325b92f87d9a3b6edc3 Mon Sep 17 00:00:00 2001 From: obito1903 Date: Wed, 25 Jan 2023 17:07:52 +0100 Subject: [PATCH 2/3] Added spotify URL parsing --- Cargo.lock | 20 ++++++++++++++++---- Cargo.toml | 1 + README.md | 6 ++++-- src/main.rs | 14 +++++++++++++- 4 files changed, 34 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5530781..0a51969 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -51,6 +51,15 @@ dependencies = [ "opaque-debug", ] +[[package]] +name = "aho-corasick" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +dependencies = [ + "memchr", +] + [[package]] name = "alsa" version = "0.6.0" @@ -1700,18 +1709,20 @@ dependencies = [ [[package]] name = "regex" -version = "1.5.6" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1" +checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" dependencies = [ + "aho-corasick", + "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" -version = "0.6.26" +version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" [[package]] name = "remove_dir_all" @@ -1901,6 +1912,7 @@ dependencies = [ "indicatif", "librespot", "pkg-config", + "regex", "rpassword", "structopt", "tokio", diff --git a/Cargo.toml b/Cargo.toml index 6294b03..70ffc32 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ librespot = "0.3.1" tokio = { version = "1.18.2", features = ["rt"] } flac-bound = { version = "0.2.0" } audiotags = "0.2.7182" +regex = "1.7.1" [package.metadata.deb] depends="libflac-dev" diff --git a/README.md b/README.md index 4c51e11..be0e129 100644 --- a/README.md +++ b/README.md @@ -32,21 +32,23 @@ spotify-dl 0.1.0 A commandline utility to download music directly from Spotify USAGE: - spotify-dl [OPTIONS] --username [tracks]... + spotify-dl [FLAGS] [OPTIONS] --username [tracks]... FLAGS: -h, --help Prints help information + -o, --ordered Download songs in the order they are in the playlist, prfixing the filename with the track number -V, --version Prints version information OPTIONS: -d, --destination The directory where the songs will be downloaded [default: .] + -p, --password Your Spotify password -u, --username Your Spotify username ARGS: ... A list of Spotify URIs (songs, podcasts or playlists) ``` -Songs and playlists must be passed as Spotify URIs (e.g. `spotify:track:123456789abcdefghABCDEF` for songs and `spotify:playlist:123456789abcdefghABCDEF` for playlists). +Songs and playlists must be passed as Spotify URIs or URLs (e.g. `spotify:track:123456789abcdefghABCDEF` for songs and `spotify:playlist:123456789abcdefghABCDEF` for playlists or `https://open.spotify.com/playlist/123456789abcdefghABCDEF?si=1234567890`). ## Disclaimer diff --git a/src/main.rs b/src/main.rs index 614a27d..a08c03b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,6 +15,7 @@ use librespot::playback::player::Player; use librespot::metadata::{Album, Artist, Metadata, Track}; +use regex::Regex; use structopt::StructOpt; use indicatif::{ProgressBar, ProgressStyle}; @@ -142,7 +143,18 @@ async fn main() { let mut tracks: Vec = Vec::new(); for track_url in opt.tracks { - let track = SpotifyId::from_uri(track_url.as_str()).unwrap(); + let track = SpotifyId::from_uri(track_url.as_str()).unwrap_or_else(|_| { + let regex = Regex::new(r"https://open.spotify.com/(\w+)/(.*)\?").unwrap(); + + let results = regex.captures(track_url.as_str()).unwrap(); + let uri = format!( + "spotify:{}:{}", + results.get(1).unwrap().as_str(), + results.get(2).unwrap().as_str() + ); + + SpotifyId::from_uri(&uri).unwrap() + }); match &track.audio_type { librespot::core::spotify_id::SpotifyAudioType::Track => { tracks.push(track); From a001184117586684738f7f8783d635de835f9da6 Mon Sep 17 00:00:00 2001 From: obito1903 Date: Wed, 25 Jan 2023 17:17:43 +0100 Subject: [PATCH 3/3] typo + more digits for track number --- src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index a08c03b..754c28c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -42,7 +42,7 @@ struct Opt { #[structopt( short = "o", long = "ordered", - help = "Download songs in the order they are in the playlist, prfixing the filename with the track number" + help = "Prefixing the filename with its index in the playlist" )] ordered: bool, } @@ -104,7 +104,7 @@ async fn download_tracks( let full_track_name = format!("{} - {}", artist_name, metadata.track_name); let filename: String; if ordered { - filename = format!("{:02} - {}.flac", i + 1, full_track_name); + filename = format!("{:03} - {}.flac", i + 1, full_track_name); } else { filename = format!("{}.flac", full_track_name); }