updated to 2021 toolchain + metadata is know saved

This commit is contained in:
obito1903 2022-05-12 00:18:58 +02:00
parent 227eeeb11b
commit d8e06c79e6
6 changed files with 1484 additions and 927 deletions

1
.gitignore vendored
View file

@ -1,6 +1,7 @@
/target /target
.vscode .vscode
*.flac *.flac
bin
debug/ debug/
target/ target/

2296
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -6,19 +6,19 @@ 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"
[build-dependencies] [build-dependencies]
pkg-config = "0.3.16" pkg-config = "0.3.16"

@ -1 +0,0 @@
Subproject commit 1b1c22b6bc1e330fef731f5b3bc3b130d52a5bff

View file

@ -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(())
} }
} }

View file

@ -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;
@ -33,14 +32,20 @@ struct Opt {
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}")
@ -48,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 = 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(&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();
@ -104,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);
@ -117,6 +131,6 @@ fn main() {
} }
} }
download_tracks(&mut core, &session, PathBuf::from(opt.destination), tracks); download_tracks(&session, PathBuf::from(opt.destination), tracks).await;
} }