mirror of
https://github.com/GuillemCastro/spotify-dl.git
synced 2024-11-21 18:00:26 +01:00
Merge pull request #3 from Obito1903/master
Keep playlist order + Spotify URL parsing
This commit is contained in:
commit
5c842b6a2e
4 changed files with 101 additions and 33 deletions
20
Cargo.lock
generated
20
Cargo.lock
generated
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -32,21 +32,23 @@ spotify-dl 0.1.0
|
|||
A commandline utility to download music directly from Spotify
|
||||
|
||||
USAGE:
|
||||
spotify-dl [OPTIONS] --username <username> [tracks]...
|
||||
spotify-dl [FLAGS] [OPTIONS] --username <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 <destination> The directory where the songs will be downloaded [default: .]
|
||||
-p, --password <password> Your Spotify password
|
||||
-u, --username <username> Your Spotify username
|
||||
|
||||
ARGS:
|
||||
<tracks>... 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
|
||||
|
||||
|
|
107
src/main.rs
107
src/main.rs
|
@ -2,25 +2,29 @@ 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 regex::Regex;
|
||||
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<String>,
|
||||
|
@ -28,8 +32,19 @@ struct Opt {
|
|||
username: String,
|
||||
#[structopt(short = "p", long = "password", help = "Your Spotify password")]
|
||||
password: Option<String>,
|
||||
#[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 = "Prefixing the filename with its index in the playlist"
|
||||
)]
|
||||
ordered: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -41,11 +56,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<SpotifyId>) {
|
||||
async fn download_tracks(
|
||||
session: &Session,
|
||||
destination: PathBuf,
|
||||
tracks: Vec<SpotifyId>,
|
||||
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 +76,14 @@ async fn download_tracks(session: &Session, destination: PathBuf, tracks: Vec<Sp
|
|||
bar.set_style(bar_style);
|
||||
bar.enable_steady_tick(500);
|
||||
|
||||
for track in tracks {
|
||||
let track_item = Track::get(&session, track).await.unwrap();
|
||||
for (i, track) in tracks.iter().enumerate() {
|
||||
let track_item = Track::get(&session, *track).await.unwrap();
|
||||
let artist_name: String;
|
||||
|
||||
let mut metadata = TrackMetadata {
|
||||
artists: Vec::new(),
|
||||
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 {
|
||||
let mut tmp: String = String::new();
|
||||
|
@ -72,22 +94,33 @@ async fn download_tracks(session: &Session, destination: PathBuf, tracks: Vec<Sp
|
|||
tmp.push_str(", ");
|
||||
}
|
||||
artist_name = String::from(tmp.trim_end_matches(", "));
|
||||
}
|
||||
else {
|
||||
artist_name = Artist::get(&session, track_item.artists[0]).await.unwrap().name;
|
||||
} else {
|
||||
artist_name = Artist::get(&session, track_item.artists[0])
|
||||
.await
|
||||
.unwrap()
|
||||
.name;
|
||||
metadata.artists.push(artist_name.clone());
|
||||
}
|
||||
let full_track_name = format!("{} - {}", artist_name, metadata.track_name);
|
||||
let filename = format!("{}.flac", full_track_name);
|
||||
let filename: String;
|
||||
if ordered {
|
||||
filename = format!("{:03} - {}.flac", i + 1, full_track_name);
|
||||
} else {
|
||||
filename = format!("{}.flac", full_track_name);
|
||||
}
|
||||
let joined_path = destination.join(&filename);
|
||||
let path = joined_path.to_str().unwrap();
|
||||
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);
|
||||
let (mut player, _) = Player::new(player_config.clone(), session.clone(), None, move || {
|
||||
Box::new(file_sink)
|
||||
});
|
||||
player.load(track, true, 0);
|
||||
let (mut player, _) =
|
||||
Player::new(player_config.clone(), session.clone(), None, move || {
|
||||
Box::new(file_sink)
|
||||
});
|
||||
player.load(*track, true, 0);
|
||||
player.await_end_of_track().await;
|
||||
player.stop();
|
||||
bar.inc(1);
|
||||
|
@ -97,11 +130,12 @@ async fn download_tracks(session: &Session, destination: PathBuf, tracks: Vec<Sp
|
|||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
|
||||
let opt = Opt::from_args();
|
||||
|
||||
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 session = create_session(credentials.clone()).await;
|
||||
|
@ -109,7 +143,18 @@ async fn main() {
|
|||
let mut tracks: Vec<SpotifyId> = 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);
|
||||
|
@ -120,7 +165,10 @@ async fn main() {
|
|||
librespot::core::spotify_id::SpotifyAudioType::NonPlayable => {
|
||||
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 +179,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