| Crates.io | qobuz-api-rust |
| lib.rs | qobuz-api-rust |
| version | 0.2.0 |
| created_at | 2025-11-19 23:07:07.296116+00 |
| updated_at | 2025-11-24 14:40:09.224685+00 |
| description | A Rust client library for the Qobuz music streaming API |
| homepage | https://github.com/loxoron218/qobuz-api-rust |
| repository | https://github.com/loxoron218/qobuz-api-rust |
| max_upload_size | |
| id | 1940901 |
| size | 764,164 |
An unofficial Rust client library for the Qobuz music streaming API, migrated from the C# implementation by DJDoubleD. This library aims to provide comprehensive access to Qobuz's features, including authentication, content retrieval (albums, artists, tracks, playlists), search functionality, user favorites management, streaming URL generation, and advanced features like web player credential extraction and metadata embedding.
This project is a migration of an existing C# Qobuz API library to Rust. The original C# project is written in .NET Framework 4.8.1, which is not compatible with Linux, making this Rust implementation particularly valuable for cross-platform use. The goal is to leverage Rust's strengths in memory safety, performance, and concurrency while maintaining the full functionality of the original C# version.
The migration from C# to Rust is largely complete in terms of core functionality. Most phases outlined in the migration_plan.md have been successfully implemented:
serde for JSON handling.reqwest and tokio.Remaining areas for development:
embedder.rs and utils.rsinto smaller files to improve maintainability.metadata_test/flac_metadata_report.md and metadata_test/mp3_metadata_report.md for differences).The project utilizes the following key Rust crates:
base64: For Base64 encoding/decoding in credential extraction.lofty: For reading and writing audio metadata (used in track/album downloads).md5: For MD5 hashing used in API request signing.regex: For parsing web player JavaScript bundles.reqwest: Asynchronous HTTP client.serde & serde_json: For efficient JSON serialization and deserialization.thiserror: For ergonomic custom error types.tokio: Asynchronous runtime.url: For URL parsing utilities.The QobuzApiService can be instantiated in two ways:
Dynamic Credential Fetching (Recommended): The service can attempt to fetch app_id and app_secret directly from the Qobuz Web Player.
use std::error::Error;
use qobuz_api_rust::QobuzApiService;
#[main]
async fn main() -> Result<(), Box<dyn Error>> {
let mut service = QobuzApiService::new().await?;
println!("Qobuz API service initialized with app ID: {}", service.app_id);
Ok(())
}
Disclaimer: Fetching credentials from the web player may break at any time due to updates to the Qobuz Web Player.
With Provided Credentials: If you have your app_id and app_secret, you can provide them directly.
use std::error::Error;
use qobuz_api_rust::QobuzApiService;
#[main]
async fn main() -> Result<(), Box<dyn Error>> {
let mut service = QobuzApiService::with_credentials(
Some("YOUR_APP_ID".to_string()),
Some("YOUR_APP_SECRET".to_string()),
).await?;
println!("Qobuz API service initialized with app ID: {}", service.app_id);
Ok(())
}
The library provides flexible authentication options with automatic credential detection. You can authenticate using one of the following methods:
Create a .env file in your project root with one of the following credential combinations:
Token-based authentication (most common):
QOBUZ_USER_ID=your_user_id_here
QOBUZ_USER_AUTH_TOKEN=your_auth_token_here
Email and password authentication:
QOBUZ_EMAIL=your_email@example.com
QOBUZ_PASSWORD=your_md5_hashed_password_here
Username and password authentication:
QOBUZ_USERNAME=your_username_here
QOBUZ_PASSWORD=your_md5_hashed_password_here
Then use the automatic authentication method:
use qobuz_api_rust::QobuzApiService;
// Load environment variables and attempt authentication
let mut service = QobuzApiService::new().await?;
match service.authenticate_with_env().await {
Ok(login_result) => {
println!("Authentication successful!");
println!("User ID: {:?}", login_result.user.and_then(|u| u.id));
}
Err(e) => {
println!("Authentication failed: {}", e);
}
}
Important Authentication Behavior Notes: The Qobuz API seems to validates the user authentication token independently of the user ID. This should mean that as long as your QOBUZ_USER_AUTH_TOKEN is valid, you may still be able to access protected resources (like downloading full tracks) even if the QOBUZ_USER_ID doesn't match the token or is incorrect. The token validity is the primary factor for API access, not the user ID itself.
I haven't been able to test the login with email/password or username/password, as I only have access to user ID and user authentication token.
You can also use the specific login methods directly:
use qobuz_api_rust::QobuzApiService;
let mut service = QobuzApiService::new().await?;
// Token-based authentication
let login_result = service.login_with_token("user_id", "auth_token").await?;
// Email and password authentication (password should be MD5 hashed)
let login_result = service.login("email@example.com", "md5_hashed_password").await?;
// Username and password authentication (password should be MD5 hashed)
let login_result = service.login("username", "md5_hashed_password").await?;
// Or use the automatic authenticate method with explicit parameters
let login_result = service.authenticate(
Some("user_id"), // Optional user ID for token auth
Some("user_auth_token"), // Optional auth token
Some("email"), // Optional email for email auth
Some("password"), // Optional password (MD5 hashed)
Some("username") // Optional username for username auth
).await?;
After successful authentication, you can search for and download content:
// Example: Search for albums
let search_query = "Miles Davis";
let search_results = service.search_albums(search_query, Some(10), None, None).await?;
if let Some(albums) = search_results.albums {
for album in albums.items.unwrap_or_default() {
println!("Album: {} by {}", album.title.unwrap_or_default(), album.artist.and_then(|a| a.name).unwrap_or_default());
}
}
// Example: Search for tracks
let track_search_query = "Kendrick Lamar";
let track_search_results = service.search_tracks(track_search_query, Some(10), None, None).await?;
if let Some(tracks) = track_search_results.tracks {
for (i, track) in tracks.items.unwrap_or_default().iter().enumerate() {
println!("{}) {} - {}",
i + 1,
track.performer.as_ref().and_then(|a| a.name.as_deref()).unwrap_or("Unknown Artist"),
track.title.as_deref().unwrap_or("No title")
);
}
}
// Example: Download a track (assuming you have a track ID and format ID)
// Track format IDs:
// 5 - MP3 320
// 6 - FLAC Lossless
// 7 - FLAC Hi-Res 24 bit <= 96kHz
// 27 - FLAC Hi-Res 24 bit >96 kHz & <= 192 kHz
let track_id = "40128300"; // Example track ID
let format_id = "6"; // FLAC Lossless
let download_path = "downloads/Artist/Album/01. TrackTitle.flac";
service.download_track(track_id, format_id, download_path).await?;
println!("Track downloaded to {}", download_path);
// Example: Download an entire album
let album_id = "12345"; // Example album ID
let quality = "6"; // FLAC Lossless
let album_path = "downloads/Artist/Album/";
service.download_album(album_id, quality, album_path).await?;
println!("Album downloaded to {}", album_path);
For more detailed usage, refer to the source code and the src/main.rs example.
This project is inspired by and built upon the excellent work of DJDoubleD. Special thanks to him and his projects:
This project is licensed under the GNU General Public License v3.0 - see the LICENSE.txt file for details.