249 lines
8.1 KiB
Rust
249 lines
8.1 KiB
Rust
//! Export des objets Qobuz en format DIDL-Lite
|
|
//!
|
|
//! Ce module permet de convertir les structures Qobuz (Album, Track, etc.)
|
|
//! en objets DIDL-Lite compatibles avec UPnP/DLNA.
|
|
|
|
use crate::error::{QobuzError, Result};
|
|
use crate::models::{Album, Playlist, Track};
|
|
use pmodidl::{Container, Item, Resource};
|
|
|
|
/// Trait pour convertir un objet Qobuz en DIDL-Lite
|
|
pub trait ToDIDL {
|
|
/// Convertit l'objet en Container DIDL
|
|
fn to_didl_container(&self, parent_id: &str) -> Result<Container>;
|
|
|
|
/// Convertit l'objet en Item DIDL
|
|
fn to_didl_item(&self, parent_id: &str) -> Result<Item>;
|
|
}
|
|
|
|
impl ToDIDL for Album {
|
|
/// Convertit un album en Container DIDL
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `parent_id` - ID du container parent
|
|
///
|
|
/// # Exemple
|
|
///
|
|
/// ```rust,ignore
|
|
/// let album = client.get_album("12345").await?;
|
|
/// let container = album.to_didl_container("0$qobuz$albums")?;
|
|
/// ```
|
|
fn to_didl_container(&self, parent_id: &str) -> Result<Container> {
|
|
let id = format!("0$qobuz$album${}", self.id);
|
|
|
|
Ok(Container {
|
|
id,
|
|
parent_id: parent_id.to_string(),
|
|
restricted: Some("1".to_string()),
|
|
child_count: self.tracks_count.map(|c| c.to_string()),
|
|
searchable: Some("1".to_string()),
|
|
title: self.formatted_title(),
|
|
class: "object.container.album.musicAlbum".to_string(),
|
|
containers: Vec::new(),
|
|
items: Vec::new(),
|
|
})
|
|
}
|
|
|
|
/// Un album ne peut pas être converti directement en Item
|
|
fn to_didl_item(&self, _parent_id: &str) -> Result<Item> {
|
|
Err(QobuzError::DidlExport(
|
|
"Album cannot be converted to Item, use to_didl_container instead".to_string(),
|
|
))
|
|
}
|
|
}
|
|
|
|
impl ToDIDL for Track {
|
|
/// Une track ne peut pas être convertie en Container
|
|
fn to_didl_container(&self, _parent_id: &str) -> Result<Container> {
|
|
Err(QobuzError::DidlExport(
|
|
"Track cannot be converted to Container, use to_didl_item instead".to_string(),
|
|
))
|
|
}
|
|
|
|
/// Convertit une track en Item DIDL
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `parent_id` - ID du container parent
|
|
///
|
|
/// # Exemple
|
|
///
|
|
/// ```rust,ignore
|
|
/// let track = client.get_track("98765").await?;
|
|
/// let item = track.to_didl_item("0$qobuz$album$12345")?;
|
|
/// ```
|
|
fn to_didl_item(&self, parent_id: &str) -> Result<Item> {
|
|
let id = format!("0$qobuz$track${}", self.id);
|
|
|
|
// Déterminer l'artiste à afficher
|
|
let artist_name = self
|
|
.display_artist()
|
|
.map(|a| a.name.clone())
|
|
.or_else(|| self.album.as_ref().map(|a| a.artist.name.clone()));
|
|
|
|
// Déterminer l'album
|
|
let album_name = self.album_name().map(|s| s.to_string());
|
|
|
|
// Déterminer l'image de couverture
|
|
let album_art = self
|
|
.album
|
|
.as_ref()
|
|
.and_then(|a| a.image_cached.clone().or_else(|| a.image.clone()));
|
|
|
|
// Créer la ressource (URL de streaming)
|
|
// Note: L'URL sera remplie plus tard via get_stream_url
|
|
let resource = Resource {
|
|
protocol_info: format!(
|
|
"http-get:*:{}:*",
|
|
self.mime_type.as_deref().unwrap_or("audio/flac")
|
|
),
|
|
bits_per_sample: self.bit_depth.map(|b| b.to_string()),
|
|
sample_frequency: self.sample_rate.map(|r| r.to_string()),
|
|
nr_audio_channels: self.channels.map(|c| c.to_string()),
|
|
duration: Some(format_duration(self.duration)),
|
|
url: format!("qobuz://track/{}", self.id), // URL symbolique
|
|
};
|
|
|
|
Ok(Item {
|
|
id,
|
|
parent_id: parent_id.to_string(),
|
|
restricted: Some("1".to_string()),
|
|
title: self.title.clone(),
|
|
creator: artist_name.clone(),
|
|
class: "object.item.audioItem.musicTrack".to_string(),
|
|
artist: artist_name,
|
|
album: album_name,
|
|
genre: None, // Qobuz ne fournit pas le genre au niveau track
|
|
album_art,
|
|
album_art_pk: None,
|
|
date: self.album.as_ref().and_then(|a| a.release_date.clone()),
|
|
original_track_number: Some(self.track_number.to_string()),
|
|
resources: vec![resource],
|
|
descriptions: Vec::new(),
|
|
})
|
|
}
|
|
}
|
|
|
|
impl ToDIDL for Playlist {
|
|
/// Convertit une playlist en Container DIDL
|
|
fn to_didl_container(&self, parent_id: &str) -> Result<Container> {
|
|
let id = format!("0$qobuz$playlist${}", self.id);
|
|
|
|
Ok(Container {
|
|
id,
|
|
parent_id: parent_id.to_string(),
|
|
restricted: Some("1".to_string()),
|
|
child_count: self.tracks_count.map(|c| c.to_string()),
|
|
searchable: Some("1".to_string()),
|
|
title: self.name.clone(),
|
|
class: "object.container.playlistContainer".to_string(),
|
|
containers: Vec::new(),
|
|
items: Vec::new(),
|
|
})
|
|
}
|
|
|
|
/// Une playlist ne peut pas être convertie en Item
|
|
fn to_didl_item(&self, _parent_id: &str) -> Result<Item> {
|
|
Err(QobuzError::DidlExport(
|
|
"Playlist cannot be converted to Item, use to_didl_container instead".to_string(),
|
|
))
|
|
}
|
|
}
|
|
|
|
/// Formate une durée en secondes au format HH:MM:SS
|
|
fn format_duration(seconds: u32) -> String {
|
|
let hours = seconds / 3600;
|
|
let minutes = (seconds % 3600) / 60;
|
|
let secs = seconds % 60;
|
|
format!("{:02}:{:02}:{:02}", hours, minutes, secs)
|
|
}
|
|
|
|
/// Convertit une liste de tracks en items DIDL
|
|
pub fn tracks_to_didl_items(tracks: &[Track], parent_id: &str) -> Result<Vec<Item>> {
|
|
tracks
|
|
.iter()
|
|
.map(|track| track.to_didl_item(parent_id))
|
|
.collect()
|
|
}
|
|
|
|
/// Convertit une liste d'albums en containers DIDL
|
|
pub fn albums_to_didl_containers(albums: &[Album], parent_id: &str) -> Result<Vec<Container>> {
|
|
albums
|
|
.iter()
|
|
.map(|album| album.to_didl_container(parent_id))
|
|
.collect()
|
|
}
|
|
|
|
/// Convertit une liste de playlists en containers DIDL
|
|
pub fn playlists_to_didl_containers(
|
|
playlists: &[Playlist],
|
|
parent_id: &str,
|
|
) -> Result<Vec<Container>> {
|
|
playlists
|
|
.iter()
|
|
.map(|playlist| playlist.to_didl_container(parent_id))
|
|
.collect()
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::models::{Album, Artist, Track};
|
|
|
|
#[test]
|
|
fn test_album_to_didl_container() {
|
|
let album = Album {
|
|
id: "123".to_string(),
|
|
title: "Test Album".to_string(),
|
|
artist: Artist::new("456", "Test Artist"),
|
|
tracks_count: Some(10),
|
|
duration: Some(3000),
|
|
release_date: Some("2024-01-01".to_string()),
|
|
image: None,
|
|
image_cached: None,
|
|
streamable: true,
|
|
description: None,
|
|
maximum_sampling_rate: Some(96000.0),
|
|
maximum_bit_depth: Some(24),
|
|
genres: vec![],
|
|
label: None,
|
|
};
|
|
|
|
let container = album.to_didl_container("parent").unwrap();
|
|
assert_eq!(container.id, "0$qobuz$album$123");
|
|
assert_eq!(container.parent_id, "parent");
|
|
assert!(container.title.contains("Test Album"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_track_to_didl_item() {
|
|
let track = Track {
|
|
id: "789".to_string(),
|
|
title: "Test Track".to_string(),
|
|
performer: Some(Artist::new("456", "Test Artist")),
|
|
album: None,
|
|
duration: 180,
|
|
track_number: 1,
|
|
media_number: 1,
|
|
streamable: true,
|
|
mime_type: Some("audio/flac".to_string()),
|
|
sample_rate: Some(44100),
|
|
bit_depth: Some(16),
|
|
channels: Some(2),
|
|
};
|
|
|
|
let item = track.to_didl_item("parent").unwrap();
|
|
assert_eq!(item.id, "0$qobuz$track$789");
|
|
assert_eq!(item.parent_id, "parent");
|
|
assert_eq!(item.title, "Test Track");
|
|
}
|
|
|
|
#[test]
|
|
fn test_format_duration() {
|
|
assert_eq!(format_duration(0), "00:00:00");
|
|
assert_eq!(format_duration(90), "00:01:30");
|
|
assert_eq!(format_duration(3665), "01:01:05");
|
|
}
|
|
}
|