fix: Use forked master branch of librespot to fix downloads
This commit is contained in:
parent
287695e1a0
commit
3887369735
9 changed files with 1008 additions and 1376 deletions
47
.vscode/launch.json
vendored
47
.vscode/launch.json
vendored
|
|
@ -1,47 +0,0 @@
|
|||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Debug executable 'down_on_spot'",
|
||||
"cargo": {
|
||||
"args": [
|
||||
"build",
|
||||
"--bin=down_on_spot",
|
||||
"--package=down_on_spot"
|
||||
],
|
||||
"filter": {
|
||||
"name": "down_on_spot",
|
||||
"kind": "bin"
|
||||
}
|
||||
},
|
||||
"args": [
|
||||
"https://open.spotify.com/track/2Ju1xUOXSS1C6GOvlTHXUp?si=60e759a084d2470d"
|
||||
],
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Debug unit tests in executable 'down_on_spot'",
|
||||
"cargo": {
|
||||
"args": [
|
||||
"test",
|
||||
"--no-run",
|
||||
"--bin=down_on_spot",
|
||||
"--package=down_on_spot"
|
||||
],
|
||||
"filter": {
|
||||
"name": "down_on_spot",
|
||||
"kind": "bin"
|
||||
}
|
||||
},
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}"
|
||||
}
|
||||
]
|
||||
}
|
||||
2149
Cargo.lock
generated
2149
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -7,7 +7,7 @@ panic = "abort"
|
|||
|
||||
[package]
|
||||
name = "down_on_spot"
|
||||
version = "0.2.4"
|
||||
version = "0.2.5"
|
||||
edition = "2021"
|
||||
authors = ["exttex", "oSumAtrIX"]
|
||||
build = "build.rs"
|
||||
|
|
@ -29,12 +29,12 @@ reqwest = "0.11"
|
|||
colored = "2"
|
||||
lame = "0.1"
|
||||
aspotify = "0.7.1"
|
||||
librespot = { git = "https://github.com/librespot-org/librespot.git", rev = "4d402e690c67457ca2d462670db39330bbceb4cf" }
|
||||
librespot = { git = "ssh://git@github.com/oSumAtrIX/free-librespot.git" }
|
||||
async-std = { version = "1.12", features = ["attributes", "tokio1"] }
|
||||
serde_json = "1.0"
|
||||
async-stream = "0.3"
|
||||
oggvorbismeta = "0.1"
|
||||
sanitize-filename = "0.4"
|
||||
sanitize-filename = "0.5.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
tokio = { version = "1.20", features = ["fs"] }
|
||||
|
||||
|
|
@ -42,4 +42,4 @@ tokio = { version = "1.20", features = ["fs"] }
|
|||
OriginalFilename = "DownOnSpot.exe"
|
||||
FileDescription = "Download songs from Spotify with Rust"
|
||||
ProductName = "DownOnSpot"
|
||||
ProductVersion = "0.2.4"
|
||||
ProductVersion = "0.2.4"
|
||||
|
|
|
|||
42
README.md
42
README.md
|
|
@ -38,21 +38,36 @@ I am not responsible in any way for the usage of the source code.
|
|||
|
||||
## Building
|
||||
|
||||
Clone the repository using git and change to the local repository directory:
|
||||
1. Clone the repository using git and change to the local repository directory:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/oSumAtrIX/DownOnSpot.git
|
||||
cd DownOnSpot
|
||||
```
|
||||
```bash
|
||||
git clone https://github.com/oSumAtrIX/DownOnSpot.git
|
||||
cd DownOnSpot
|
||||
```
|
||||
|
||||
`Nightly Rust` is required to build this project. Install it by following [rustup.rs](https://rustup.rs) instructions.
|
||||
2. Build
|
||||
|
||||
```bash
|
||||
cargo build --release
|
||||
```
|
||||
```bash
|
||||
cargo build --release
|
||||
```
|
||||
|
||||
If you get a linker error, you might need to download the [standard libmp3lame](https://www.rarewares.org/mp3-lame-libraries.php#libmp3lame) library.
|
||||
On OS X, it should be enough to just run `brew install lame`, provided you have [Homebrew](https://brew.sh/) installed.
|
||||
> [!WARNING]
|
||||
> You need [this private SSH key](assets/free_librespot_private_key) to clone a dependency of DownOnSpot in order to use it with a free Spotify account.
|
||||
> Follow [this answer by DopeGhoti on stackexchange.com](https://unix.stackexchange.com/a/494485) to set up SSH with the private key.
|
||||
> A sample `~/.ssh/config` file could look like this:
|
||||
>
|
||||
> ```text
|
||||
> Host github.com
|
||||
> IdentityFile ~/.ssh/free_librespot_private_key
|
||||
> ```
|
||||
|
||||
> [!NOTE]
|
||||
> If you do not want to use `free-librespot` (i.e. if you are using a paid Spotify account), then remove the git dependency of `free-librespot`.
|
||||
> For that, replace `git = "ssh://git@github.com/oSumAtrIX/free-librespot.git"` with `librespot = "0.4.2"` inside the `Cargo.toml` file.
|
||||
|
||||
> [!WARNING]
|
||||
> If you get a linker error, you might need to download the [standard libmp3lame](https://www.rarewares.org/mp3-lame-libraries.php#libmp3lame) library.
|
||||
> On Mac OS, it should be enough to just run `brew install lame`, provided you have [Homebrew](https://brew.sh/) installed.
|
||||
|
||||
## Usage/ Examples
|
||||
|
||||
|
|
@ -68,12 +83,13 @@ Usage:
|
|||
down_on_spot.exe (search_term | track_url | album_url | playlist_url | artist_url)
|
||||
```
|
||||
|
||||
On OS X, the `settings.json` file is created globally for the logged in user and is located in `~/.config/down_on_spot/settings.json`.
|
||||
On Mac OS, the `settings.json` file is created globally for the logged in user and is located in `~/.config/down_on_spot/settings.json`.
|
||||
|
||||
Apart from your Spotify username and password, you will need to login in to the Spotify developer dashboard and [create a new private application](https://developer.spotify.com/dashboard/applications). Fill in the `client_id` and `client_secret` in your `settings.json` from your newly created app.
|
||||
All the other settings should be self-explanatory, conversion from Ogg to MP3 is disabled by default.
|
||||
|
||||
> **Note**: If you're on a free Spotify account, remember to set down the quality to 160 ("Q320" -> "Q160"). Otherwise, you'll get an "Audio Key Error" since the free account can't exceed 160kbit/s.
|
||||
> [!NOTE]
|
||||
> If you're on a free Spotify account, remember to set down the quality to 160kbit/s at maximum ("Q320" -> "Q160"). Otherwise, you'll get an "Audio Key Error" since the free account can't exceed 160kbit/s.
|
||||
|
||||
### Template variables
|
||||
|
||||
|
|
|
|||
27
assets/free_librespot_private_key
Normal file
27
assets/free_librespot_private_key
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpQIBAAKCAQEAywlwuOLmkrNlrN4au+dpgxYeE52pYT+vF9bpH38BthOE5Vg2
|
||||
6QStlKvaED6tXpdvO+3tXrfqLSGNRjYHM02KLb5c2nReM1NyeY0H6Ad+zqs5OdK/
|
||||
wzGXfLEDZnTt8RO3Oi5htWec12jnfvMkiKXZQ6mxvtCGfAuabmqk1DwkfjrLIBKk
|
||||
pob83OJDsDm5qFjsLU9ooI/j7MKKFAK9KrZ0RgA/lR+Kjcmve7OLoysr+cc7Gxqx
|
||||
OQe08qRoju0IjI4WRN+AvpSJSyMUaLj6oi8tDdc8nnLtUamUzZiOW4q6xRKLTldj
|
||||
lvb2GDtHiUV3EddMEhjkUh5v9JmTizPvc2dxswIDAQABAoIBAQCIHncc7LnKRk13
|
||||
HJxtt1JMmOpnGj8xPIHFiMZX4E0kZWemXoLJq+I3lxMl6u9OSFZBplyKMODliuXs
|
||||
2VHweh6WVlZn+rYg0OQhS4SucT/5jF9cPcdkNH4sEXyHFwNiqWYgsnG9LY6/98uj
|
||||
l5y5o/7J1x2cIK7YwOqy9fo1c3C/0rWz9f5h06DtzfTeXt/HMpGVIiBIxkACxdHs
|
||||
BJYhKlZraLrFebQi+U/m7fEe996Wq3X0rFSkC/hMcbuCAqWni7ghc9lqDCIBvWvp
|
||||
q78nrbB9GnrTNtDHpr8L/DYUP3dUXqvBNWSZb35JkYiHdf0Xmnbm5SavHwhus353
|
||||
3Jp38Xa5AoGBAPsulurvFhxLxLvG/dsb3ZjmA3gfD0mhNLfs+UUZD8NR0FlpC+tZ
|
||||
S9Fn/UVy7gey7Ax8DWjKRWtGQx3elIR+SEpNl7gK0ld0ZAFZq6FLUD7mywVpJ7yN
|
||||
NA9tWyiimSdD0E+60uE61/2cuZWib+F5nZBJI1nkRBI0duvX+ckqPuQHAoGBAM7u
|
||||
cBOEGPfRZ+iMQvKb64WrA3MCSeqMrxGfK89IJNaAX1ZuPWl4CTQmnTnETXJoRt7A
|
||||
IhroZsw1WYxv1WXPI9vveRU+7v6YZmuKNBuoQy+yQVYkmlIQM2jAdSItvqwcfS3P
|
||||
EdUq22/flwH+PgWGSEwBwXuytOj7knoMFkkOJ1H1AoGBAKb8XSaIlelv0bWIYCG3
|
||||
T8WjPLEx7OblPff8meb+90TBckLOnDb1IXtwp6bw9h+2KklOe1esCSR9DIeh/phq
|
||||
n8KN/QxEXXv4i7d+FqTfPdTjeBr7wogOU4riB5VnK2w4IBexOxqzFP6sn5BD8h8y
|
||||
y7YteUv8feigamL2LUA4TqEnAoGAQ6r3HXqqC4f/2azcS6evKgPVWeuarkeZDEMn
|
||||
uV3XJgnOo13jWTT2lYY3Defih5IP8tGTlYb/7rFXejQr7WICUO6LguYUk07U1z/X
|
||||
cF/isJszAG9xw3omxY+B/k5FNtUOHkfDhhjAr2HHaNByCa5yv+AnHENBI5r4uE5x
|
||||
jyxAsW0CgYEAyY+GRveaZsEAtkfP4wzV/HVo+wRvPMWDkJckt46M/b2ufhwwQlCU
|
||||
dkXoJmnTWlk5AETt0sYEoNi/6YmPE2QytsbuHbfTnjI6mnG7LnwV2QinbvFrXifK
|
||||
3dylLnbTN+PoXXRoTWktWVVt0u+tAYhwi82GwkR+RlOszXS8e0IITZM=
|
||||
-----END RSA PRIVATE KEY-----
|
||||
|
|
@ -7,8 +7,7 @@ use librespot::audio::{AudioDecrypt, AudioFile};
|
|||
use librespot::core::audio_key::AudioKey;
|
||||
use librespot::core::session::Session;
|
||||
use librespot::core::spotify_id::SpotifyId;
|
||||
use librespot::metadata::audio::AudioFileFormat;
|
||||
use librespot::metadata::{Metadata, Track};
|
||||
use librespot::metadata::{FileFormat, Metadata, Track};
|
||||
use sanitize_filename::sanitize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io::Read;
|
||||
|
|
@ -475,9 +474,9 @@ impl DownloaderInternal {
|
|||
}
|
||||
|
||||
async fn find_alternative(session: &Session, track: Track) -> Result<Track, SpotifyError> {
|
||||
for alt in track.alternatives.0 {
|
||||
let t = Track::get(&session, &alt).await?;
|
||||
if !t.availability.is_empty() {
|
||||
for alt in track.alternatives {
|
||||
let t = Track::get(&session, alt).await?;
|
||||
if !t.available {
|
||||
return Ok(t);
|
||||
}
|
||||
}
|
||||
|
|
@ -495,12 +494,12 @@ impl DownloaderInternal {
|
|||
job_id: i64,
|
||||
) -> Result<(PathBuf, AudioFormat), SpotifyError> {
|
||||
let id = SpotifyId::from_base62(id)?;
|
||||
let track = Track::get(session, &id).await?;
|
||||
let mut track = Track::get(session, id).await?;
|
||||
|
||||
// TODO: Fallback if unavailable, but no idea how to check if it's available yet
|
||||
//if track.availability.is_empty() {
|
||||
// track = DownloaderInternal::find_alternative(session, track).await?;
|
||||
//}
|
||||
// Fallback if unavailable
|
||||
if !track.available {
|
||||
track = DownloaderInternal::find_alternative(session, track).await?;
|
||||
}
|
||||
|
||||
// Quality fallback
|
||||
let mut quality = config.quality;
|
||||
|
|
@ -542,12 +541,11 @@ impl DownloaderInternal {
|
|||
return Err(SpotifyError::AlreadyDownloaded);
|
||||
}
|
||||
|
||||
|
||||
let path_clone = path.clone();
|
||||
|
||||
let key = session.audio_key().request(track.id, *file_id).await?;
|
||||
let encrypted = AudioFile::open(session, *file_id, 1024 * 1024).await?;
|
||||
let size = encrypted.get_stream_loader_controller()?.len();
|
||||
let encrypted = AudioFile::open(session, *file_id, 1024 * 1024, true).await?;
|
||||
let size = encrypted.get_stream_loader_controller().len();
|
||||
// Download
|
||||
let s = match config.convert_to_mp3 {
|
||||
true => {
|
||||
|
|
@ -596,7 +594,7 @@ impl DownloaderInternal {
|
|||
) -> impl Stream<Item = Result<usize, SpotifyError>> {
|
||||
try_stream! {
|
||||
let mut file = File::create(path).await?;
|
||||
let mut decrypted = AudioDecrypt::new(Some(key), encrypted);
|
||||
let mut decrypted = AudioDecrypt::new(key, encrypted);
|
||||
// Skip (i guess encrypted shit)
|
||||
let mut skip: [u8; 0xa7] = [0; 0xa7];
|
||||
let mut decrypted = tokio::task::spawn_blocking(move || {
|
||||
|
|
@ -634,7 +632,7 @@ impl DownloaderInternal {
|
|||
) -> impl Stream<Item = Result<usize, SpotifyError>> {
|
||||
try_stream! {
|
||||
let mut file = File::create(path).await?;
|
||||
let mut decrypted = AudioDecrypt::new(Some(key), encrypted);
|
||||
let mut decrypted = AudioDecrypt::new(key, encrypted);
|
||||
// Skip (i guess encrypted shit)
|
||||
let mut skip: [u8; 0xa7] = [0; 0xa7];
|
||||
let decrypted = tokio::task::spawn_blocking(move || {
|
||||
|
|
@ -674,7 +672,8 @@ pub enum AudioFormat {
|
|||
Ogg,
|
||||
Aac,
|
||||
Mp3,
|
||||
Flac,
|
||||
Mp4,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl AudioFormat {
|
||||
|
|
@ -684,48 +683,50 @@ impl AudioFormat {
|
|||
AudioFormat::Ogg => "ogg",
|
||||
AudioFormat::Aac => "m4a",
|
||||
AudioFormat::Mp3 => "mp3",
|
||||
AudioFormat::Flac => "flac",
|
||||
AudioFormat::Mp4 => "mp4",
|
||||
AudioFormat::Unknown => "",
|
||||
}
|
||||
.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AudioFileFormat> for AudioFormat {
|
||||
fn from(f: AudioFileFormat) -> Self {
|
||||
impl From<FileFormat> for AudioFormat {
|
||||
fn from(f: FileFormat) -> Self {
|
||||
match f {
|
||||
AudioFileFormat::OGG_VORBIS_96 => Self::Ogg,
|
||||
AudioFileFormat::OGG_VORBIS_160 => Self::Ogg,
|
||||
AudioFileFormat::OGG_VORBIS_320 => Self::Ogg,
|
||||
AudioFileFormat::MP3_256 => Self::Mp3,
|
||||
AudioFileFormat::MP3_320 => Self::Mp3,
|
||||
AudioFileFormat::MP3_160 => Self::Mp3,
|
||||
AudioFileFormat::MP3_96 => Self::Mp3,
|
||||
AudioFileFormat::MP3_160_ENC => Self::Mp3,
|
||||
AudioFileFormat::AAC_24 => Self::Aac,
|
||||
AudioFileFormat::AAC_48 => Self::Aac,
|
||||
AudioFileFormat::FLAC_FLAC => Self::Flac,
|
||||
FileFormat::OGG_VORBIS_96 => Self::Ogg,
|
||||
FileFormat::OGG_VORBIS_160 => Self::Ogg,
|
||||
FileFormat::OGG_VORBIS_320 => Self::Ogg,
|
||||
FileFormat::MP3_256 => Self::Mp3,
|
||||
FileFormat::MP3_320 => Self::Mp3,
|
||||
FileFormat::MP3_160 => Self::Mp3,
|
||||
FileFormat::MP3_96 => Self::Mp3,
|
||||
FileFormat::MP3_160_ENC => Self::Mp3,
|
||||
FileFormat::MP4_128_DUAL => Self::Mp4,
|
||||
FileFormat::OTHER3 => Self::Unknown,
|
||||
FileFormat::AAC_160 => Self::Aac,
|
||||
FileFormat::AAC_320 => Self::Aac,
|
||||
FileFormat::MP4_128 => Self::Mp4,
|
||||
FileFormat::OTHER5 => Self::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Quality {
|
||||
/// Get librespot AudioFileFormat
|
||||
pub fn get_file_formats(&self) -> Vec<AudioFileFormat> {
|
||||
pub fn get_file_formats(&self) -> Vec<FileFormat> {
|
||||
match self {
|
||||
Self::Q320 => vec![
|
||||
AudioFileFormat::OGG_VORBIS_320,
|
||||
AudioFileFormat::MP3_320,
|
||||
AudioFileFormat::FLAC_FLAC,
|
||||
FileFormat::OGG_VORBIS_320,
|
||||
FileFormat::AAC_320,
|
||||
FileFormat::MP3_320,
|
||||
],
|
||||
Self::Q256 => vec![AudioFileFormat::MP3_256],
|
||||
Self::Q256 => vec![FileFormat::MP3_256],
|
||||
Self::Q160 => vec![
|
||||
AudioFileFormat::OGG_VORBIS_160,
|
||||
AudioFileFormat::MP3_160,
|
||||
AudioFileFormat::MP3_160_ENC,
|
||||
AudioFileFormat::AAC_24,
|
||||
AudioFileFormat::AAC_48,
|
||||
FileFormat::OGG_VORBIS_160,
|
||||
FileFormat::AAC_160,
|
||||
FileFormat::MP3_160,
|
||||
],
|
||||
Self::Q96 => vec![AudioFileFormat::OGG_VORBIS_96, AudioFileFormat::MP3_96],
|
||||
Self::Q96 => vec![FileFormat::OGG_VORBIS_96, FileFormat::MP3_96],
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -884,7 +885,7 @@ impl DownloaderConfig {
|
|||
id3v24: true,
|
||||
convert_to_mp3: false,
|
||||
separator: ", ".to_string(),
|
||||
skip_existing: true
|
||||
skip_existing: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
11
src/error.rs
11
src/error.rs
|
|
@ -1,4 +1,3 @@
|
|||
use librespot::core::Error;
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
@ -23,12 +22,6 @@ pub enum SpotifyError {
|
|||
AlreadyDownloaded,
|
||||
}
|
||||
|
||||
impl From<Error> for SpotifyError {
|
||||
fn from(e: Error) -> Self {
|
||||
Self::Error(e.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for SpotifyError {}
|
||||
impl fmt::Display for SpotifyError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
|
|
@ -50,7 +43,7 @@ impl fmt::Display for SpotifyError {
|
|||
SpotifyError::ID3Error(k, e) => write!(f, "ID3 Error: {} {}", k, e),
|
||||
SpotifyError::Reqwest(e) => write!(f, "Reqwest Error: {}", e),
|
||||
SpotifyError::InvalidFormat => write!(f, "Invalid Format!"),
|
||||
SpotifyError::AlreadyDownloaded => write!(f, "Already Downloaded")
|
||||
SpotifyError::AlreadyDownloaded => write!(f, "Already Downloaded"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -78,8 +71,6 @@ impl From<librespot::core::session::SessionError> for SpotifyError {
|
|||
librespot::core::session::SessionError::AuthenticationError(_) => {
|
||||
SpotifyError::AuthenticationError
|
||||
}
|
||||
librespot::core::session::SessionError::NotConnected => SpotifyError::Error("Connection error".to_string()),
|
||||
librespot::core::session::SessionError::Packet(_) => SpotifyError::Error("Packet Error".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,10 +13,8 @@ use colored::Colorize;
|
|||
use downloader::{DownloadState, Downloader};
|
||||
use settings::Settings;
|
||||
use spotify::Spotify;
|
||||
use std::{
|
||||
env,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use std::env;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
#[cfg(not(windows))]
|
||||
#[tokio::main]
|
||||
|
|
|
|||
|
|
@ -28,13 +28,12 @@ impl Spotify {
|
|||
) -> Result<Spotify, SpotifyError> {
|
||||
// librespot
|
||||
let credentials = Credentials::with_password(username, password);
|
||||
|
||||
let session = Session::new(
|
||||
let (session, _) = Session::connect(
|
||||
SessionConfig::default(),
|
||||
credentials,
|
||||
Some(Cache::new(Some(Path::new("credentials_cache")), None, None, None).unwrap()),
|
||||
);
|
||||
|
||||
session.connect(credentials, true)
|
||||
true,
|
||||
)
|
||||
.await?;
|
||||
|
||||
//aspotify
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue