mirror of
https://github.com/GuillemCastro/spotify-dl.git
synced 2024-11-22 10:20:26 +01:00
Merge pull request #1 from Obito1903/master
keep Cargo.lock since building an executable
This commit is contained in:
commit
e4ee89fd55
6 changed files with 2989 additions and 40 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,13 +1,14 @@
|
||||||
/target
|
/target
|
||||||
.vscode
|
.vscode
|
||||||
*.flac
|
*.flac
|
||||||
|
bin
|
||||||
|
|
||||||
debug/
|
debug/
|
||||||
target/
|
target/
|
||||||
|
|
||||||
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||||
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
||||||
Cargo.lock
|
# Cargo.lock
|
||||||
|
|
||||||
# These are backup files generated by rustfmt
|
# These are backup files generated by rustfmt
|
||||||
**/*.rs.bk
|
**/*.rs.bk
|
2906
Cargo.lock
generated
Normal file
2906
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
|
@ -6,16 +6,16 @@ edition = "2018"
|
||||||
links = "FLAC"
|
links = "FLAC"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
librespot = { path = "./librespot" }
|
|
||||||
tokio-core = "0.1.17"
|
tokio-core = "0.1.17"
|
||||||
futures = "0.1"
|
futures = "0.1"
|
||||||
futures-state-stream = "0.1"
|
futures-state-stream = "0.1"
|
||||||
structopt = { version = "0.3", default-features = false }
|
structopt = { version = "0.3", default-features = false }
|
||||||
rpassword = "5.0"
|
rpassword = "5.0"
|
||||||
indicatif = "0.15.0"
|
indicatif = "0.15.0"
|
||||||
|
librespot = "0.3.1"
|
||||||
[dependencies.flac-bound]
|
tokio = { version = "1.18.2", features = ["rt"] }
|
||||||
version = "0.2.0"
|
flac-bound = { version = "0.2.0" }
|
||||||
|
audiotags = "0.2.7182"
|
||||||
|
|
||||||
[package.metadata.deb]
|
[package.metadata.deb]
|
||||||
depends="libflac-dev"
|
depends="libflac-dev"
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
Subproject commit 1b1c22b6bc1e330fef731f5b3bc3b130d52a5bff
|
|
|
@ -1,22 +1,34 @@
|
||||||
use librespot::playback::audio_backend::{Open, Sink};
|
use std::path::Path;
|
||||||
use std::io::{self};
|
|
||||||
|
|
||||||
extern crate flac_bound;
|
use audiotags::{Tag, TagType};
|
||||||
|
use librespot::{playback::{audio_backend::{Open, Sink, SinkError}, config::AudioFormat, decoder::AudioPacket, convert::Converter}};
|
||||||
|
|
||||||
|
// extern crate flac_bound;
|
||||||
|
|
||||||
use flac_bound::{FlacEncoder};
|
use flac_bound::{FlacEncoder};
|
||||||
|
|
||||||
|
use crate::TrackMetadata;
|
||||||
|
|
||||||
pub struct FileSink {
|
pub struct FileSink {
|
||||||
sink: String,
|
sink: String,
|
||||||
content: Vec<i32>
|
content: Vec<i32>,
|
||||||
|
metadata: Option<TrackMetadata>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FileSink {
|
||||||
|
pub fn add_metadata(&mut self, meta: TrackMetadata) {
|
||||||
|
self.metadata = Some(meta);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Open for FileSink {
|
impl Open for FileSink {
|
||||||
fn open(path: Option<String>) -> Self {
|
fn open(path: Option<String>, _audio_format: AudioFormat) -> Self {
|
||||||
if let Some(path) = path {
|
if let Some(path) = path {
|
||||||
let file = path;
|
let file = path;
|
||||||
FileSink {
|
FileSink {
|
||||||
sink: file,
|
sink: file,
|
||||||
content: Vec::new()
|
content: Vec::new(),
|
||||||
|
metadata: None
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
panic!();
|
panic!();
|
||||||
|
@ -25,20 +37,35 @@ impl Open for FileSink {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Sink for FileSink {
|
impl Sink for FileSink {
|
||||||
fn start(&mut self) -> io::Result<()> {
|
fn start(&mut self) -> Result<(), SinkError> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stop(&mut self) -> io::Result<()> {
|
fn stop(&mut self) -> Result<(), SinkError> {
|
||||||
let mut encoder = FlacEncoder::new().unwrap().channels(2).bits_per_sample(16).compression_level(0).init_file(&self.sink).unwrap();
|
let mut encoder = FlacEncoder::new().unwrap().channels(2).bits_per_sample(16).compression_level(4).init_file(&self.sink).unwrap();
|
||||||
encoder.process_interleaved(self.content.as_slice(), (self.content.len()/2) as u32).unwrap();
|
encoder.process_interleaved(self.content.as_slice(), (self.content.len()/2) as u32).unwrap();
|
||||||
encoder.finish().unwrap();
|
encoder.finish().unwrap();
|
||||||
|
|
||||||
|
match &self.metadata {
|
||||||
|
Some(meta) => {
|
||||||
|
let mut tag = Tag::new().with_tag_type(TagType::Flac).read_from_path(Path::new(&self.sink)).unwrap();
|
||||||
|
|
||||||
|
tag.set_album_title(&meta.album);
|
||||||
|
for artist in &meta.artists {
|
||||||
|
tag.add_artist(artist);
|
||||||
|
}
|
||||||
|
tag.set_title(&meta.track_name);
|
||||||
|
tag.write_to_path(&self.sink).expect("Failed to write metadata");
|
||||||
|
},
|
||||||
|
None => (),
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write(&mut self, data: &[i16]) -> io::Result<()> {
|
fn write(&mut self, packet: &AudioPacket, converter: &mut Converter) -> Result<(), SinkError> {
|
||||||
let mut input: Vec<i32> = data.iter().map(|el| i32::from(*el)).collect();
|
let data = converter.f64_to_s16(packet.samples().unwrap());
|
||||||
self.content.append(&mut input);
|
let mut data32: Vec<i32> = data.iter().map(|el| i32::from(*el)).collect();
|
||||||
|
self.content.append(&mut data32);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
54
src/main.rs
54
src/main.rs
|
@ -3,7 +3,6 @@ mod file_sink;
|
||||||
extern crate rpassword;
|
extern crate rpassword;
|
||||||
|
|
||||||
use std::{path::PathBuf};
|
use std::{path::PathBuf};
|
||||||
use tokio_core::reactor::Core;
|
|
||||||
|
|
||||||
use librespot::{core::authentication::Credentials, metadata::Playlist};
|
use librespot::{core::authentication::Credentials, metadata::Playlist};
|
||||||
use librespot::core::config::SessionConfig;
|
use librespot::core::config::SessionConfig;
|
||||||
|
@ -14,7 +13,7 @@ use librespot::playback::config::PlayerConfig;
|
||||||
use librespot::playback::player::Player;
|
use librespot::playback::player::Player;
|
||||||
use librespot::playback::audio_backend::{Open};
|
use librespot::playback::audio_backend::{Open};
|
||||||
|
|
||||||
use librespot::metadata::{Track, Artist, Metadata};
|
use librespot::metadata::{Track, Artist, Metadata, Album};
|
||||||
|
|
||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
|
|
||||||
|
@ -27,18 +26,26 @@ struct Opt {
|
||||||
tracks: Vec<String>,
|
tracks: Vec<String>,
|
||||||
#[structopt(short = "u", long = "username", help = "Your Spotify username")]
|
#[structopt(short = "u", long = "username", help = "Your Spotify username")]
|
||||||
username: String,
|
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")]
|
#[structopt(short = "d", long = "destination", default_value = ".", help = "The directory where the songs will be downloaded")]
|
||||||
destination: String
|
destination: String
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_session(core: &mut Core, credentials: Credentials) -> Session {
|
#[derive(Clone)]
|
||||||
|
pub struct TrackMetadata {
|
||||||
|
artists: Vec<String>,
|
||||||
|
track_name: String,
|
||||||
|
album: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn create_session(credentials: Credentials) -> Session {
|
||||||
let session_config = SessionConfig::default();
|
let session_config = SessionConfig::default();
|
||||||
let session = core.run(Session::connect(session_config, credentials, None, core.handle()))
|
let session = Session::connect(session_config, credentials, None).await.unwrap();
|
||||||
.unwrap();
|
|
||||||
session
|
session
|
||||||
}
|
}
|
||||||
|
|
||||||
fn download_tracks(core: &mut Core, session: &Session, destination: PathBuf, tracks: Vec<SpotifyId>) {
|
async fn download_tracks(session: &Session, destination: PathBuf, tracks: Vec<SpotifyId>) {
|
||||||
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}")
|
||||||
|
@ -46,49 +53,58 @@ fn download_tracks(core: &mut Core, session: &Session, destination: PathBuf, tra
|
||||||
let bar = ProgressBar::new(tracks.len() as u64);
|
let bar = ProgressBar::new(tracks.len() as u64);
|
||||||
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 track in tracks {
|
||||||
let track_item = core.run(Track::get(&session, track)).unwrap();
|
let track_item = Track::get(&session, track).await.unwrap();
|
||||||
let artist_name: String;
|
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
|
||||||
|
};
|
||||||
if track_item.artists.len() > 1 {
|
if track_item.artists.len() > 1 {
|
||||||
let mut tmp: String = String::new();
|
let mut tmp: String = String::new();
|
||||||
for artist in track_item.artists {
|
for artist in track_item.artists {
|
||||||
let artist_item = core.run(Artist::get(&session, artist)).unwrap();
|
let artist_item = Artist::get(&session, artist).await.unwrap();
|
||||||
|
metadata.artists.push(artist_item.name.clone());
|
||||||
tmp.push_str(artist_item.name.as_str());
|
tmp.push_str(artist_item.name.as_str());
|
||||||
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 = core.run(Artist::get(&session, track_item.artists[0])).unwrap().name;
|
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, track_item.name);
|
let full_track_name = format!("{} - {}", artist_name, metadata.track_name);
|
||||||
let filename = format!("{}.flac", full_track_name);
|
let 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 file_sink = file_sink::FileSink::open(Some(path.to_owned()));
|
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 || {
|
let (mut player, _) = Player::new(player_config.clone(), session.clone(), None, move || {
|
||||||
Box::new(file_sink)
|
Box::new(file_sink)
|
||||||
});
|
});
|
||||||
player.load(track, true, 0);
|
player.load(track, true, 0);
|
||||||
core.run(player.get_end_of_track_future()).unwrap();
|
player.await_end_of_track().await;
|
||||||
player.stop();
|
player.stop();
|
||||||
bar.inc(1);
|
bar.inc(1);
|
||||||
}
|
}
|
||||||
bar.finish();
|
bar.finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
|
||||||
let opt = Opt::from_args();
|
let opt = Opt::from_args();
|
||||||
|
|
||||||
let mut core = Core::new().unwrap();
|
|
||||||
|
|
||||||
let username = opt.username;
|
let username = opt.username;
|
||||||
let password = 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(&mut core, credentials.clone());
|
let session = create_session(credentials.clone()).await;
|
||||||
|
|
||||||
let mut tracks: Vec<SpotifyId> = Vec::new();
|
let mut tracks: Vec<SpotifyId> = Vec::new();
|
||||||
|
|
||||||
|
@ -102,7 +118,7 @@ fn main() {
|
||||||
tracks.push(track);
|
tracks.push(track);
|
||||||
}
|
}
|
||||||
librespot::core::spotify_id::SpotifyAudioType::NonPlayable => {
|
librespot::core::spotify_id::SpotifyAudioType::NonPlayable => {
|
||||||
match core.run(Playlist::get(&session, track)) {
|
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);
|
||||||
|
@ -115,6 +131,6 @@ fn main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
download_tracks(&mut core, &session, PathBuf::from(opt.destination), tracks);
|
download_tracks(&session, PathBuf::from(opt.destination), tracks).await;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue