fix: Use forked master branch of librespot to fix downloads

This commit is contained in:
oSumAtrIX 2023-11-26 01:30:24 +01:00
parent 287695e1a0
commit 3887369735
No known key found for this signature in database
GPG key ID: A9B3094ACDB604B4
9 changed files with 1008 additions and 1376 deletions

47
.vscode/launch.json vendored
View file

@ -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

File diff suppressed because it is too large Load diff

View file

@ -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"

View file

@ -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

View 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-----

View file

@ -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,
}
}
}

View file

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

View file

@ -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]

View file

@ -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