Première tentative d'une crate pmoqobuz
This commit is contained in:
298
pmoqobuz/src/models.rs
Normal file
298
pmoqobuz/src/models.rs
Normal file
@@ -0,0 +1,298 @@
|
||||
//! Structures de données pour représenter les objets Qobuz
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Représente un artiste Qobuz
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct Artist {
|
||||
/// Identifiant unique de l'artiste
|
||||
pub id: String,
|
||||
/// Nom de l'artiste
|
||||
pub name: String,
|
||||
/// URL de l'image de l'artiste (optionnelle)
|
||||
#[serde(default)]
|
||||
pub image: Option<String>,
|
||||
/// URL de l'image cachée localement (via pmocovers)
|
||||
#[serde(skip)]
|
||||
pub image_cached: Option<String>,
|
||||
}
|
||||
|
||||
/// Représente un album Qobuz
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Album {
|
||||
/// Identifiant unique de l'album
|
||||
pub id: String,
|
||||
/// Titre de l'album
|
||||
pub title: String,
|
||||
/// Artiste principal de l'album
|
||||
pub artist: Artist,
|
||||
/// Nombre de pistes
|
||||
#[serde(default)]
|
||||
pub tracks_count: Option<u32>,
|
||||
/// Durée totale en secondes
|
||||
#[serde(default)]
|
||||
pub duration: Option<u32>,
|
||||
/// Date de sortie (format ISO 8601)
|
||||
#[serde(default)]
|
||||
pub release_date: Option<String>,
|
||||
/// URL de l'image de couverture
|
||||
#[serde(default)]
|
||||
pub image: Option<String>,
|
||||
/// URL de l'image cachée localement (via pmocovers)
|
||||
#[serde(skip)]
|
||||
pub image_cached: Option<String>,
|
||||
/// Indique si l'album est disponible pour le streaming
|
||||
#[serde(default = "default_true")]
|
||||
pub streamable: bool,
|
||||
/// Description de l'album
|
||||
#[serde(default)]
|
||||
pub description: Option<String>,
|
||||
/// Taux d'échantillonnage maximum (Hz)
|
||||
#[serde(default)]
|
||||
pub maximum_sampling_rate: Option<f64>,
|
||||
/// Profondeur de bits maximale
|
||||
#[serde(default)]
|
||||
pub maximum_bit_depth: Option<u32>,
|
||||
/// Genre(s) de l'album
|
||||
#[serde(default)]
|
||||
pub genres: Vec<String>,
|
||||
/// Label de l'album
|
||||
#[serde(default)]
|
||||
pub label: Option<String>,
|
||||
}
|
||||
|
||||
/// Représente une piste (track) Qobuz
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Track {
|
||||
/// Identifiant unique de la piste
|
||||
pub id: String,
|
||||
/// Titre de la piste
|
||||
pub title: String,
|
||||
/// Artiste de la piste (peut différer de l'artiste de l'album)
|
||||
pub performer: Option<Artist>,
|
||||
/// Album contenant la piste
|
||||
pub album: Option<Album>,
|
||||
/// Durée en secondes
|
||||
pub duration: u32,
|
||||
/// Numéro de piste
|
||||
pub track_number: u32,
|
||||
/// Numéro de disque (pour les albums multi-disques)
|
||||
pub media_number: u32,
|
||||
/// Indique si la piste est disponible pour le streaming
|
||||
#[serde(default = "default_true")]
|
||||
pub streamable: bool,
|
||||
/// Type MIME du fichier audio (déterminé après obtention de l'URL)
|
||||
#[serde(skip)]
|
||||
pub mime_type: Option<String>,
|
||||
/// Fréquence d'échantillonnage (Hz)
|
||||
#[serde(skip)]
|
||||
pub sample_rate: Option<u32>,
|
||||
/// Profondeur de bits
|
||||
#[serde(skip)]
|
||||
pub bit_depth: Option<u32>,
|
||||
/// Nombre de canaux audio
|
||||
#[serde(skip)]
|
||||
pub channels: Option<u8>,
|
||||
}
|
||||
|
||||
/// Représente une playlist Qobuz
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Playlist {
|
||||
/// Identifiant unique de la playlist
|
||||
pub id: String,
|
||||
/// Nom de la playlist
|
||||
pub name: String,
|
||||
/// Description de la playlist
|
||||
#[serde(default)]
|
||||
pub description: Option<String>,
|
||||
/// Nombre de pistes
|
||||
#[serde(default)]
|
||||
pub tracks_count: Option<u32>,
|
||||
/// Durée totale en secondes
|
||||
#[serde(default)]
|
||||
pub duration: Option<u32>,
|
||||
/// URL de l'image de la playlist
|
||||
#[serde(default)]
|
||||
pub image: Option<String>,
|
||||
/// URL de l'image cachée localement
|
||||
#[serde(skip)]
|
||||
pub image_cached: Option<String>,
|
||||
/// Indique si c'est une playlist publique
|
||||
#[serde(default)]
|
||||
pub is_public: bool,
|
||||
/// Propriétaire de la playlist
|
||||
#[serde(default)]
|
||||
pub owner: Option<PlaylistOwner>,
|
||||
}
|
||||
|
||||
/// Propriétaire d'une playlist
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PlaylistOwner {
|
||||
/// Identifiant de l'utilisateur
|
||||
pub id: u64,
|
||||
/// Nom de l'utilisateur
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
/// Représente un genre musical
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct Genre {
|
||||
/// Identifiant du genre (peut être None pour "All Genres")
|
||||
pub id: Option<u32>,
|
||||
/// Nom du genre
|
||||
pub name: String,
|
||||
/// Genres enfants
|
||||
#[serde(default)]
|
||||
pub children: Vec<Genre>,
|
||||
}
|
||||
|
||||
/// Résultats de recherche
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct SearchResult {
|
||||
/// Albums trouvés
|
||||
#[serde(default)]
|
||||
pub albums: Vec<Album>,
|
||||
/// Artistes trouvés
|
||||
#[serde(default)]
|
||||
pub artists: Vec<Artist>,
|
||||
/// Pistes trouvées
|
||||
#[serde(default)]
|
||||
pub tracks: Vec<Track>,
|
||||
/// Playlists trouvées
|
||||
#[serde(default)]
|
||||
pub playlists: Vec<Playlist>,
|
||||
}
|
||||
|
||||
/// Informations sur un fichier de streaming
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct StreamInfo {
|
||||
/// URL de streaming
|
||||
pub url: String,
|
||||
/// Type MIME
|
||||
pub mime_type: String,
|
||||
/// Fréquence d'échantillonnage (Hz)
|
||||
pub sampling_rate: u32,
|
||||
/// Profondeur de bits
|
||||
pub bit_depth: u32,
|
||||
/// Format ID Qobuz
|
||||
pub format_id: u8,
|
||||
/// Date d'expiration de l'URL
|
||||
#[serde(skip)]
|
||||
pub expires_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
/// Format audio demandé pour le streaming
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[repr(u8)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum AudioFormat {
|
||||
/// MP3 320 kbps
|
||||
Mp3_320 = 5,
|
||||
/// FLAC 16 bit / 44.1 kHz (CD Quality)
|
||||
Flac_Lossless = 6,
|
||||
/// FLAC 24 bit / jusqu'à 96 kHz (Hi-Res)
|
||||
Flac_HiRes_96 = 7,
|
||||
/// FLAC 24 bit / jusqu'à 192 kHz (Hi-Res+)
|
||||
Flac_HiRes_192 = 27,
|
||||
}
|
||||
|
||||
impl AudioFormat {
|
||||
/// Retourne l'ID du format pour l'API Qobuz
|
||||
pub fn id(&self) -> u8 {
|
||||
*self as u8
|
||||
}
|
||||
|
||||
/// Retourne une description lisible du format
|
||||
pub fn description(&self) -> &'static str {
|
||||
match self {
|
||||
AudioFormat::Mp3_320 => "MP3 320 kbps",
|
||||
AudioFormat::Flac_Lossless => "FLAC 16 bit / 44.1 kHz",
|
||||
AudioFormat::Flac_HiRes_96 => "FLAC 24 bit / up to 96 kHz",
|
||||
AudioFormat::Flac_HiRes_192 => "FLAC 24 bit / up to 192 kHz",
|
||||
}
|
||||
}
|
||||
|
||||
/// Retourne le type MIME associé
|
||||
pub fn mime_type(&self) -> &'static str {
|
||||
match self {
|
||||
AudioFormat::Mp3_320 => "audio/mpeg",
|
||||
_ => "audio/flac",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AudioFormat {
|
||||
fn default() -> Self {
|
||||
AudioFormat::Flac_Lossless
|
||||
}
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
fn default_true() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
impl Artist {
|
||||
/// Crée un nouvel artiste avec un ID et un nom
|
||||
pub fn new(id: impl Into<String>, name: impl Into<String>) -> Self {
|
||||
Self {
|
||||
id: id.into(),
|
||||
name: name.into(),
|
||||
image: None,
|
||||
image_cached: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Album {
|
||||
/// Retourne un titre formaté avec les informations audio si disponibles
|
||||
pub fn formatted_title(&self) -> String {
|
||||
if let (Some(rate), Some(depth)) = (self.maximum_sampling_rate, self.maximum_bit_depth) {
|
||||
format!("{} ({:.0}/{} bit)", self.title, rate / 1000.0, depth)
|
||||
} else {
|
||||
self.title.clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// Vérifie si l'album est disponible pour le streaming
|
||||
pub fn is_available(&self) -> bool {
|
||||
self.streamable
|
||||
}
|
||||
}
|
||||
|
||||
impl Track {
|
||||
/// Retourne l'artiste à afficher (performer ou artiste de l'album)
|
||||
pub fn display_artist(&self) -> Option<&Artist> {
|
||||
self.performer
|
||||
.as_ref()
|
||||
.or_else(|| self.album.as_ref().map(|a| &a.artist))
|
||||
}
|
||||
|
||||
/// Retourne le nom de l'album si disponible
|
||||
pub fn album_name(&self) -> Option<&str> {
|
||||
self.album.as_ref().map(|a| a.title.as_str())
|
||||
}
|
||||
|
||||
/// Vérifie si la piste est disponible pour le streaming
|
||||
pub fn is_available(&self) -> bool {
|
||||
self.streamable
|
||||
}
|
||||
}
|
||||
|
||||
impl SearchResult {
|
||||
/// Crée un résultat de recherche vide
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Retourne le nombre total de résultats
|
||||
pub fn total_count(&self) -> usize {
|
||||
self.albums.len() + self.artists.len() + self.tracks.len() + self.playlists.len()
|
||||
}
|
||||
|
||||
/// Vérifie si la recherche n'a retourné aucun résultat
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.total_count() == 0
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user