Add search function and code quality improvements
This commit is contained in:
parent
a96279b190
commit
c523a3a18c
8 changed files with 1026 additions and 941 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
|
@ -662,7 +662,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "down_on_spot"
|
name = "down_on_spot"
|
||||||
version = "0.0.1"
|
version = "0.1.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aspotify",
|
"aspotify",
|
||||||
"async-std",
|
"async-std",
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ panic = "abort"
|
||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "down_on_spot"
|
name = "down_on_spot"
|
||||||
version = "0.0.1"
|
version = "0.1.1"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
authors = ["exttex", "oSumAtrIX"]
|
authors = ["exttex", "oSumAtrIX"]
|
||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
|
|
@ -44,4 +44,4 @@ tokio = { version = "1.12", features = ["fs"] }
|
||||||
OriginalFilename = "DownOnSpot.exe"
|
OriginalFilename = "DownOnSpot.exe"
|
||||||
FileDescription = "Download songs from Spotify with Rust"
|
FileDescription = "Download songs from Spotify with Rust"
|
||||||
ProductName = "DownOnSpot"
|
ProductName = "DownOnSpot"
|
||||||
ProductVersion = "0.0.1"
|
ProductVersion = "0.1.1"
|
||||||
|
|
@ -31,6 +31,7 @@ I am not responsible in any way for the usage of the source code.
|
||||||
- Works with free Spotify accounts (if using free-librespot fork)
|
- Works with free Spotify accounts (if using free-librespot fork)
|
||||||
- Download 96, 160kbit/s audio with a free, 256 and 320 kbit/s audio with a premium account from Spotify, directly
|
- Download 96, 160kbit/s audio with a free, 256 and 320 kbit/s audio with a premium account from Spotify, directly
|
||||||
- Multi-threaded
|
- Multi-threaded
|
||||||
|
- Search for tracks
|
||||||
- Download tracks, playlists, albums and artists
|
- Download tracks, playlists, albums and artists
|
||||||
- Convert to mp3
|
- Convert to mp3
|
||||||
- Metadata tagging
|
- Metadata tagging
|
||||||
|
|
@ -76,7 +77,7 @@ Settings could not be loaded, because of the following error: IO: NotFound No su
|
||||||
|
|
||||||
$ down_on_spot.exe
|
$ down_on_spot.exe
|
||||||
Usage:
|
Usage:
|
||||||
down_on_spot.exe (track_url | album_url | playlist_url | artist_url)
|
down_on_spot.exe (search_term | track_url | album_url | playlist_url | artist_url)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Template variables
|
### Template variables
|
||||||
|
|
|
||||||
1444
src/downloader.rs
1444
src/downloader.rs
File diff suppressed because it is too large
Load diff
156
src/main.rs
156
src/main.rs
|
|
@ -8,8 +8,6 @@ use settings::Settings;
|
||||||
use spotify::Spotify;
|
use spotify::Spotify;
|
||||||
use std::{
|
use std::{
|
||||||
env,
|
env,
|
||||||
ffi::OsStr,
|
|
||||||
path::Path,
|
|
||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -29,14 +27,14 @@ async fn main() {
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
use colored::control;
|
use colored::control;
|
||||||
|
|
||||||
//backwards compatibility.
|
//backwards compatibility.
|
||||||
match control::set_virtual_terminal(true) {
|
match control::set_virtual_terminal(true) {
|
||||||
Ok(_) => {},
|
Ok(_) => {}
|
||||||
Err(_) => {}
|
Err(_) => {}
|
||||||
};
|
};
|
||||||
|
|
||||||
start().await;
|
start().await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -45,7 +43,7 @@ async fn start() {
|
||||||
Ok(settings) => {
|
Ok(settings) => {
|
||||||
println!(
|
println!(
|
||||||
"{} {}.",
|
"{} {}.",
|
||||||
"Settings successfully loaded. Continuing with spotify account:".green(),
|
"Settings successfully loaded.\nContinuing with spotify account:".green(),
|
||||||
settings.username
|
settings.username
|
||||||
);
|
);
|
||||||
settings
|
settings
|
||||||
|
|
@ -56,8 +54,7 @@ async fn start() {
|
||||||
"Settings could not be loaded, because of the following error:".red(),
|
"Settings could not be loaded, because of the following error:".red(),
|
||||||
e
|
e
|
||||||
);
|
);
|
||||||
let default_settings =
|
let default_settings = Settings::new("username", "password", "client_id", "secret");
|
||||||
Settings::new("username", "password", "client_id", "secret").unwrap();
|
|
||||||
match default_settings.save().await {
|
match default_settings.save().await {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
println!(
|
println!(
|
||||||
|
|
@ -103,75 +100,108 @@ async fn start() {
|
||||||
|
|
||||||
let downloader = Downloader::new(settings.downloader, spotify);
|
let downloader = Downloader::new(settings.downloader, spotify);
|
||||||
|
|
||||||
match downloader.add_uri(&args[1]).await {
|
match downloader.handle_input(&args[1]).await {
|
||||||
Ok(_) => {}
|
Ok(search_results) => {
|
||||||
Err(e) => {
|
if let Some(search_results) = search_results {
|
||||||
error!("{} {}", "Adding url failed:".red(), e)
|
print!("{esc}[2J{esc}[1;1H", esc = 27 as char);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let refresh = Duration::from_secs(settings.refresh_ui_seconds);
|
for (i, track) in search_results.iter().enumerate() {
|
||||||
let now = Instant::now();
|
println!("{}: {} - {}", i + 1, track.author, track.title);
|
||||||
let mut timeelapsed: u64;
|
}
|
||||||
|
println!("{}", "Select the track (default: 1): ".green());
|
||||||
|
|
||||||
'outer: loop {
|
let mut selection;
|
||||||
print!("{esc}[2J{esc}[1;1H", esc = 27 as char);
|
loop {
|
||||||
let mut exit_flag: i8 = 1;
|
let mut input = String::new();
|
||||||
|
std::io::stdin()
|
||||||
|
.read_line(&mut input)
|
||||||
|
.expect("Failed to read line");
|
||||||
|
|
||||||
for download in downloader.get_downloads().await {
|
selection = input.trim().parse::<usize>().unwrap_or(1) - 1;
|
||||||
let state = download.state;
|
|
||||||
|
|
||||||
let progress: String;
|
if selection < search_results.len() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
println!("{}", "Invalid selection. Try again or quit (CTRL+C):".red());
|
||||||
|
}
|
||||||
|
|
||||||
if state != DownloadState::Done {
|
let track = &search_results[selection];
|
||||||
exit_flag &= 0;
|
|
||||||
progress = match state {
|
if let Err(e) = downloader
|
||||||
DownloadState::Downloading(r, t) => {
|
.add_uri(&format!("spotify:track:{}", track.track_id))
|
||||||
let p = r as f32 / t as f32 * 100.0;
|
.await
|
||||||
if p > 100.0 {
|
{
|
||||||
"100%".to_string()
|
error!(
|
||||||
} else {
|
"{}",
|
||||||
format!("{}%", p as i8)
|
format!(
|
||||||
|
"{}: {}",
|
||||||
|
"Track could not be added to download queue.".red(),
|
||||||
|
e
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
let refresh = Duration::from_secs(settings.refresh_ui_seconds);
|
||||||
|
let now = Instant::now();
|
||||||
|
let mut time_elapsed: u64;
|
||||||
|
|
||||||
|
'outer: loop {
|
||||||
|
print!("{esc}[2J{esc}[1;1H", esc = 27 as char);
|
||||||
|
let mut exit_flag: i8 = 1;
|
||||||
|
|
||||||
|
for download in downloader.get_downloads().await {
|
||||||
|
let state = download.state;
|
||||||
|
|
||||||
|
let progress: String;
|
||||||
|
|
||||||
|
if state != DownloadState::Done {
|
||||||
|
exit_flag &= 0;
|
||||||
|
progress = match state {
|
||||||
|
DownloadState::Downloading(r, t) => {
|
||||||
|
let p = r as f32 / t as f32 * 100.0;
|
||||||
|
if p > 100.0 {
|
||||||
|
"100%".to_string()
|
||||||
|
} else {
|
||||||
|
format!("{}%", p as i8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DownloadState::Post => "Postprocessing... ".to_string(),
|
||||||
|
DownloadState::None => "Preparing... ".to_string(),
|
||||||
|
DownloadState::Lock => "Preparing... ".to_string(),
|
||||||
|
DownloadState::Error(e) => {
|
||||||
|
exit_flag |= 1;
|
||||||
|
format!("{} ", e)
|
||||||
|
}
|
||||||
|
DownloadState::Done => {
|
||||||
|
exit_flag |= 1;
|
||||||
|
"Impossible state".to_string()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
progress = "Done.".to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("{:<19}| {}", progress, download.title);
|
||||||
}
|
}
|
||||||
|
time_elapsed = now.elapsed().as_secs();
|
||||||
|
if exit_flag == 1 {
|
||||||
|
break 'outer;
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("\nElapsed second(s): {}", time_elapsed);
|
||||||
|
task::sleep(refresh).await
|
||||||
}
|
}
|
||||||
DownloadState::Post => "Postprocessing... ".to_string(),
|
println!("Finished download(s) in {} second(s).", time_elapsed);
|
||||||
DownloadState::None => "Preparing... ".to_string(),
|
}
|
||||||
DownloadState::Lock => "Holding... ".to_string(),
|
|
||||||
DownloadState::Error(e) => {
|
|
||||||
exit_flag |= 1;
|
|
||||||
format!("{} ", e)
|
|
||||||
},
|
|
||||||
DownloadState::Done => {
|
|
||||||
exit_flag |= 1;
|
|
||||||
"Impossible state".to_string()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
progress = "Done.".to_string();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("{:<19}| {}", progress, download.title);
|
|
||||||
}
|
}
|
||||||
timeelapsed = now.elapsed().as_secs();
|
Err(e) => {
|
||||||
if exit_flag == 1 {
|
error!("{} {}", "Handling input failed:".red(), e)
|
||||||
break 'outer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("\nElapsed second(s): {}", timeelapsed);
|
|
||||||
task::sleep(refresh).await
|
|
||||||
}
|
}
|
||||||
println!("Finished download(s) in {} second(s).", timeelapsed);
|
|
||||||
} else {
|
} else {
|
||||||
println!(
|
println!(
|
||||||
"Usage:\n{} (track_url | album_url | playlist_url | artist_url )",
|
"Usage:\n{} (track_url | album_url | playlist_url | artist_url )",
|
||||||
env::args()
|
args[0]
|
||||||
.next()
|
|
||||||
.as_ref()
|
|
||||||
.map(Path::new)
|
|
||||||
.and_then(Path::file_name)
|
|
||||||
.and_then(OsStr::to_str)
|
|
||||||
.map(String::from)
|
|
||||||
.unwrap()
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
use crate::downloader::DownloaderConfig;
|
use crate::downloader::DownloaderConfig;
|
||||||
use crate::downloader::Quality;
|
|
||||||
use crate::error::SpotifyError;
|
use crate::error::SpotifyError;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
|
@ -18,30 +17,18 @@ pub struct Settings {
|
||||||
pub refresh_ui_seconds: u64,
|
pub refresh_ui_seconds: u64,
|
||||||
pub downloader: DownloaderConfig,
|
pub downloader: DownloaderConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Settings {
|
impl Settings {
|
||||||
// Create new instance
|
// Create new instance
|
||||||
pub fn new(
|
pub fn new(username: &str, password: &str, client_id: &str, client_secret: &str) -> Settings {
|
||||||
username: &str,
|
Settings {
|
||||||
password: &str,
|
|
||||||
client_id: &str,
|
|
||||||
client_secret: &str,
|
|
||||||
) -> Option<Settings> {
|
|
||||||
Some(Settings {
|
|
||||||
username: username.to_string(),
|
username: username.to_string(),
|
||||||
password: password.to_string(),
|
password: password.to_string(),
|
||||||
client_id: client_id.to_string(),
|
client_id: client_id.to_string(),
|
||||||
client_secret: client_secret.to_string(),
|
client_secret: client_secret.to_string(),
|
||||||
refresh_ui_seconds: 1,
|
refresh_ui_seconds: 1,
|
||||||
downloader: DownloaderConfig {
|
downloader: DownloaderConfig::new()
|
||||||
concurrent_downloads: 4,
|
}
|
||||||
quality: Quality::Q320,
|
|
||||||
path: "downloads".to_string(),
|
|
||||||
filename_template: "%artist% - %title%".to_string(),
|
|
||||||
id3v24: true,
|
|
||||||
convert_to_mp3: false,
|
|
||||||
separator: ", ".to_string(),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serialize the settings to a json file
|
// Serialize the settings to a json file
|
||||||
|
|
|
||||||
325
src/spotify.rs
325
src/spotify.rs
|
|
@ -1,6 +1,4 @@
|
||||||
use aspotify::{
|
use aspotify::{Album, Artist, Client, ClientCredentials, ItemType, Playlist, PlaylistItemType, Track, TrackSimplified};
|
||||||
Album, Artist, Client, ClientCredentials, Playlist, PlaylistItemType, Track, TrackSimplified,
|
|
||||||
};
|
|
||||||
use librespot::core::authentication::Credentials;
|
use librespot::core::authentication::Credentials;
|
||||||
use librespot::core::config::SessionConfig;
|
use librespot::core::config::SessionConfig;
|
||||||
use librespot::core::session::Session;
|
use librespot::core::session::Session;
|
||||||
|
|
@ -10,183 +8,196 @@ use url::Url;
|
||||||
use crate::error::SpotifyError;
|
use crate::error::SpotifyError;
|
||||||
|
|
||||||
pub struct Spotify {
|
pub struct Spotify {
|
||||||
// librespotify sessopm
|
// librespotify sessopm
|
||||||
pub session: Session,
|
pub session: Session,
|
||||||
pub spotify: Client,
|
pub spotify: Client,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Spotify {
|
impl Spotify {
|
||||||
/// Create new instance
|
/// Create new instance
|
||||||
pub async fn new(
|
pub async fn new(
|
||||||
username: &str,
|
username: &str,
|
||||||
password: &str,
|
password: &str,
|
||||||
client_id: &str,
|
client_id: &str,
|
||||||
client_secret: &str,
|
client_secret: &str,
|
||||||
) -> Result<Spotify, SpotifyError> {
|
) -> Result<Spotify, SpotifyError> {
|
||||||
// librespot
|
// librespot
|
||||||
let credentials = Credentials::with_password(username, password);
|
let credentials = Credentials::with_password(username, password);
|
||||||
let session = Session::connect(SessionConfig::default(), credentials, None).await?;
|
let session = Session::connect(SessionConfig::default(), credentials, None).await?;
|
||||||
//aspotify
|
//aspotify
|
||||||
let credentials = ClientCredentials {
|
let credentials = ClientCredentials {
|
||||||
id: client_id.to_string(),
|
id: client_id.to_string(),
|
||||||
secret: client_secret.to_string(),
|
secret: client_secret.to_string(),
|
||||||
};
|
};
|
||||||
let spotify = Client::new(credentials);
|
let spotify = Client::new(credentials);
|
||||||
|
|
||||||
Ok(Spotify { session, spotify })
|
Ok(Spotify { session, spotify })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse URI or URL into URI
|
/// Parse URI or URL into URI
|
||||||
pub fn parse_uri(uri: &str) -> Result<String, SpotifyError> {
|
pub fn parse_uri(uri: &str) -> Result<String, SpotifyError> {
|
||||||
// Already URI
|
// Already URI
|
||||||
if uri.starts_with("spotify:") {
|
if uri.starts_with("spotify:") {
|
||||||
if uri.split(':').count() < 3 {
|
if uri.split(':').count() < 3 {
|
||||||
return Err(SpotifyError::InvalidUri);
|
return Err(SpotifyError::InvalidUri);
|
||||||
}
|
}
|
||||||
return Ok(uri.to_string());
|
return Ok(uri.to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse URL
|
// Parse URL
|
||||||
let url = Url::parse(uri)?;
|
let url = Url::parse(uri)?;
|
||||||
// Spotify Web Player URL
|
// Spotify Web Player URL
|
||||||
if url.host_str() == Some("open.spotify.com") {
|
if url.host_str() == Some("open.spotify.com") {
|
||||||
let path = url
|
let path = url
|
||||||
.path_segments()
|
.path_segments()
|
||||||
.ok_or_else(|| SpotifyError::Error("Missing URL path".into()))?
|
.ok_or_else(|| SpotifyError::Error("Missing URL path".into()))?
|
||||||
.collect::<Vec<&str>>();
|
.collect::<Vec<&str>>();
|
||||||
if path.len() < 2 {
|
if path.len() < 2 {
|
||||||
return Err(SpotifyError::InvalidUri);
|
return Err(SpotifyError::InvalidUri);
|
||||||
}
|
}
|
||||||
return Ok(format!("spotify:{}:{}", path[0], path[1]));
|
return Ok(format!("spotify:{}:{}", path[0], path[1]));
|
||||||
}
|
}
|
||||||
Err(SpotifyError::InvalidUri)
|
Err(SpotifyError::InvalidUri)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fetch data for URI
|
/// Fetch data for URI
|
||||||
pub async fn resolve_uri(&self, uri: &str) -> Result<SpotifyItem, SpotifyError> {
|
pub async fn resolve_uri(&self, uri: &str) -> Result<SpotifyItem, SpotifyError> {
|
||||||
let parts = uri.split(':').skip(1).collect::<Vec<&str>>();
|
let parts = uri.split(':').skip(1).collect::<Vec<&str>>();
|
||||||
let id = parts[1];
|
let id = parts[1];
|
||||||
match parts[0] {
|
match parts[0] {
|
||||||
"track" => {
|
"track" => {
|
||||||
let track = self.spotify.tracks().get_track(id, None).await?;
|
let track = self.spotify.tracks().get_track(id, None).await?;
|
||||||
Ok(SpotifyItem::Track(track.data))
|
Ok(SpotifyItem::Track(track.data))
|
||||||
}
|
}
|
||||||
"playlist" => {
|
"playlist" => {
|
||||||
let playlist = self.spotify.playlists().get_playlist(id, None).await?;
|
let playlist = self.spotify.playlists().get_playlist(id, None).await?;
|
||||||
Ok(SpotifyItem::Playlist(playlist.data))
|
Ok(SpotifyItem::Playlist(playlist.data))
|
||||||
}
|
}
|
||||||
"album" => {
|
"album" => {
|
||||||
let album = self.spotify.albums().get_album(id, None).await?;
|
let album = self.spotify.albums().get_album(id, None).await?;
|
||||||
Ok(SpotifyItem::Album(album.data))
|
Ok(SpotifyItem::Album(album.data))
|
||||||
}
|
}
|
||||||
"artist" => {
|
"artist" => {
|
||||||
let artist = self.spotify.artists().get_artist(id).await?;
|
let artist = self.spotify.artists().get_artist(id).await?;
|
||||||
Ok(SpotifyItem::Artist(artist.data))
|
Ok(SpotifyItem::Artist(artist.data))
|
||||||
}
|
}
|
||||||
// Unsupported / Unimplemented
|
// Unsupported / Unimplemented
|
||||||
_ => Ok(SpotifyItem::Other(uri.to_string())),
|
_ => Ok(SpotifyItem::Other(uri.to_string())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get all tracks from playlist
|
/// Get search results for query
|
||||||
pub async fn full_playlist(&self, id: &str) -> Result<Vec<Track>, SpotifyError> {
|
pub async fn search(&self, query: &str) -> Result<Vec<Track>, SpotifyError> {
|
||||||
let mut items = vec![];
|
Ok(self
|
||||||
let mut offset = 0;
|
.spotify
|
||||||
loop {
|
.search()
|
||||||
let page = self
|
.search(query, [ItemType::Track], true, 50, 0, None)
|
||||||
.spotify
|
.await?
|
||||||
.playlists()
|
.data
|
||||||
.get_playlists_items(id, 100, offset, None)
|
.tracks
|
||||||
.await?;
|
.unwrap()
|
||||||
items.append(
|
.items)
|
||||||
&mut page
|
}
|
||||||
.data
|
|
||||||
.items
|
|
||||||
.iter()
|
|
||||||
.filter_map(|i| -> Option<Track> {
|
|
||||||
if let Some(PlaylistItemType::Track(t)) = &i.item {
|
|
||||||
Some(t.to_owned())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
);
|
|
||||||
|
|
||||||
// End
|
/// Get all tracks from playlist
|
||||||
offset += page.data.items.len();
|
pub async fn full_playlist(&self, id: &str) -> Result<Vec<Track>, SpotifyError> {
|
||||||
if page.data.total == offset {
|
let mut items = vec![];
|
||||||
return Ok(items);
|
let mut offset = 0;
|
||||||
}
|
loop {
|
||||||
}
|
let page = self
|
||||||
}
|
.spotify
|
||||||
|
.playlists()
|
||||||
|
.get_playlists_items(id, 100, offset, None)
|
||||||
|
.await?;
|
||||||
|
items.append(
|
||||||
|
&mut page
|
||||||
|
.data
|
||||||
|
.items
|
||||||
|
.iter()
|
||||||
|
.filter_map(|i| -> Option<Track> {
|
||||||
|
if let Some(PlaylistItemType::Track(t)) = &i.item {
|
||||||
|
Some(t.to_owned())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
);
|
||||||
|
|
||||||
/// Get all tracks from album
|
// End
|
||||||
pub async fn full_album(&self, id: &str) -> Result<Vec<TrackSimplified>, SpotifyError> {
|
offset += page.data.items.len();
|
||||||
let mut items = vec![];
|
if page.data.total == offset {
|
||||||
let mut offset = 0;
|
return Ok(items);
|
||||||
loop {
|
}
|
||||||
let page = self
|
}
|
||||||
.spotify
|
}
|
||||||
.albums()
|
|
||||||
.get_album_tracks(id, 50, offset, None)
|
|
||||||
.await?;
|
|
||||||
items.append(&mut page.data.items.to_vec());
|
|
||||||
|
|
||||||
// End
|
/// Get all tracks from album
|
||||||
offset += page.data.items.len();
|
pub async fn full_album(&self, id: &str) -> Result<Vec<TrackSimplified>, SpotifyError> {
|
||||||
if page.data.total == offset {
|
let mut items = vec![];
|
||||||
return Ok(items);
|
let mut offset = 0;
|
||||||
}
|
loop {
|
||||||
}
|
let page = self
|
||||||
}
|
.spotify
|
||||||
|
.albums()
|
||||||
|
.get_album_tracks(id, 50, offset, None)
|
||||||
|
.await?;
|
||||||
|
items.append(&mut page.data.items.to_vec());
|
||||||
|
|
||||||
/// Get all tracks from artist
|
// End
|
||||||
pub async fn full_artist(&self, id: &str) -> Result<Vec<TrackSimplified>, SpotifyError> {
|
offset += page.data.items.len();
|
||||||
let mut items = vec![];
|
if page.data.total == offset {
|
||||||
let mut offset = 0;
|
return Ok(items);
|
||||||
loop {
|
}
|
||||||
let page = self
|
}
|
||||||
.spotify
|
}
|
||||||
.artists()
|
|
||||||
.get_artist_albums(id, None, 50, offset, None)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
for album in &mut page.data.items.iter() {
|
/// Get all tracks from artist
|
||||||
items.append(&mut self.full_album(&album.id).await?)
|
pub async fn full_artist(&self, id: &str) -> Result<Vec<TrackSimplified>, SpotifyError> {
|
||||||
}
|
let mut items = vec![];
|
||||||
|
let mut offset = 0;
|
||||||
|
loop {
|
||||||
|
let page = self
|
||||||
|
.spotify
|
||||||
|
.artists()
|
||||||
|
.get_artist_albums(id, None, 50, offset, None)
|
||||||
|
.await?;
|
||||||
|
|
||||||
// End
|
for album in &mut page.data.items.iter() {
|
||||||
offset += page.data.items.len();
|
items.append(&mut self.full_album(&album.id).await?)
|
||||||
if page.data.total == offset {
|
}
|
||||||
return Ok(items);
|
|
||||||
}
|
// End
|
||||||
}
|
offset += page.data.items.len();
|
||||||
}
|
if page.data.total == offset {
|
||||||
|
return Ok(items);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Clone for Spotify {
|
impl Clone for Spotify {
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
Self {
|
Self {
|
||||||
session: self.session.clone(),
|
session: self.session.clone(),
|
||||||
spotify: Client::new(self.spotify.credentials.clone()),
|
spotify: Client::new(self.spotify.credentials.clone()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Basic debug implementation so can be used in other structs
|
/// Basic debug implementation so can be used in other structs
|
||||||
impl fmt::Debug for Spotify {
|
impl fmt::Debug for Spotify {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(f, "<Spotify Instance>")
|
write!(f, "<Spotify Instance>")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum SpotifyItem {
|
pub enum SpotifyItem {
|
||||||
Track(Track),
|
Track(Track),
|
||||||
Album(Album),
|
Album(Album),
|
||||||
Playlist(Playlist),
|
Playlist(Playlist),
|
||||||
Artist(Artist),
|
Artist(Artist),
|
||||||
/// Unimplemented
|
/// Unimplemented
|
||||||
Other(String),
|
Other(String),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use chrono::NaiveDate;
|
use chrono::NaiveDate;
|
||||||
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use crate::downloader::AudioFormat;
|
use crate::downloader::AudioFormat;
|
||||||
|
|
@ -16,6 +17,7 @@ pub enum TagWrap {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TagWrap {
|
impl TagWrap {
|
||||||
|
|
||||||
/// Load from file
|
/// Load from file
|
||||||
pub fn new(path: impl AsRef<Path>, format: AudioFormat) -> Result<TagWrap, SpotifyError> {
|
pub fn new(path: impl AsRef<Path>, format: AudioFormat) -> Result<TagWrap, SpotifyError> {
|
||||||
match format {
|
match format {
|
||||||
|
|
@ -27,10 +29,10 @@ impl TagWrap {
|
||||||
|
|
||||||
/// Get Tag trait
|
/// Get Tag trait
|
||||||
pub fn get_tag(&mut self) -> Box<&mut dyn Tag> {
|
pub fn get_tag(&mut self) -> Box<&mut dyn Tag> {
|
||||||
match self {
|
Box::new(match self {
|
||||||
TagWrap::Ogg(tag) => Box::new(tag),
|
TagWrap::Ogg(tag) => tag,
|
||||||
TagWrap::Id3(tag) => Box::new(tag),
|
TagWrap::Id3(tag) => tag,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue