fin de l'unification des métadonnées de pmocacheaudio
This commit is contained in:
@@ -5,10 +5,11 @@
|
||||
//! des métadonnées en JSON dans la base de données.
|
||||
|
||||
use anyhow::Result;
|
||||
use crate::metadata_ext::AudioTrackMetadataExt;
|
||||
use pmocache::CacheConfig;
|
||||
use serde_json::{Number, Value};
|
||||
use std::sync::Arc;
|
||||
use pmocache::download::TransformMetadata;
|
||||
use serde_json::Value;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Configuration pour le cache audio
|
||||
pub struct AudioConfig;
|
||||
@@ -57,32 +58,26 @@ pub fn new_cache(dir: &str, limit: usize) -> Result<Cache> {
|
||||
Cache::with_transformer(dir, limit, Some(transformer_factory))
|
||||
}
|
||||
|
||||
fn persist_transform_streaminfo(cache: &Cache, pk: &str, tmeta: &TransformMetadata) {
|
||||
async fn persist_transform_streaminfo(cache: Arc<Cache>, pk: &str, tmeta: &TransformMetadata) {
|
||||
use std::time::Duration;
|
||||
|
||||
let track_meta = cache.track_metadata(pk);
|
||||
let mut meta = track_meta.write().await;
|
||||
|
||||
if let Some(sr) = tmeta.sample_rate {
|
||||
let _ = cache
|
||||
.db
|
||||
.set_a_metadata(pk, "sample_rate", Value::Number(Number::from(sr)));
|
||||
let _ = meta.set_sample_rate(Some(sr)).await;
|
||||
}
|
||||
if let Some(bps) = tmeta.bits_per_sample {
|
||||
let _ = cache
|
||||
.db
|
||||
.set_a_metadata(pk, "bits_per_sample", Value::Number(Number::from(bps)));
|
||||
}
|
||||
if let Some(ch) = tmeta.channels {
|
||||
let _ = cache
|
||||
.db
|
||||
.set_a_metadata(pk, "channels", Value::Number(Number::from(ch)));
|
||||
let _ = meta.set_bits_per_sample(Some(bps)).await;
|
||||
}
|
||||
if let Some(ts) = tmeta.total_samples {
|
||||
let _ = cache
|
||||
.db
|
||||
.set_a_metadata(pk, "total_samples", Value::Number(Number::from(ts)));
|
||||
let _ = meta.set_total_samples(Some(ts)).await;
|
||||
|
||||
// Calculer la durée à partir de total_samples et sample_rate
|
||||
if let Some(sr) = tmeta.sample_rate {
|
||||
if sr > 0 {
|
||||
let secs = (ts as f64 / sr as f64).round() as u64;
|
||||
let _ = cache
|
||||
.db
|
||||
.set_a_metadata(pk, "duration_secs", Value::Number(Number::from(secs)));
|
||||
let _ = meta.set_duration(Some(Duration::from_secs(secs))).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -160,208 +155,108 @@ pub async fn new_cache_with_consolidation(dir: &str, limit: usize) -> Result<Arc
|
||||
/// # }
|
||||
/// ```
|
||||
pub async fn add_with_metadata_extraction(
|
||||
cache: &Cache,
|
||||
cache: Arc<Cache>,
|
||||
url: &str,
|
||||
collection: Option<&str>,
|
||||
) -> Result<String> {
|
||||
use std::time::Duration;
|
||||
|
||||
// Ajouter au cache (déclenche le download et la conversion)
|
||||
let pk = cache.add_from_url(url, collection).await?;
|
||||
|
||||
// Attendre que le fichier soit téléchargé et converti
|
||||
cache.wait_until_finished(&pk).await?;
|
||||
|
||||
// Persister les métadonnées de transformation (taux d'échantillonnage, bits par échantillon, etc.)
|
||||
if let Some(transform) = cache.transform_metadata(&pk).await {
|
||||
persist_transform_streaminfo(cache, &pk, &transform);
|
||||
persist_transform_streaminfo(cache.clone(), &pk, &transform).await;
|
||||
}
|
||||
|
||||
// Lire le fichier FLAC pour extraire les métadonnées
|
||||
let file_path = cache.get_file_path(&pk);
|
||||
let flac_bytes = tokio::fs::read(&file_path).await?;
|
||||
|
||||
// Extraire les métadonnées et persister champ par champ dans la DB (source unique)
|
||||
let mut metadata = crate::metadata::AudioMetadata::from_bytes(&flac_bytes)?;
|
||||
// Extraire les métadonnées depuis le fichier audio
|
||||
let metadata = crate::metadata::AudioMetadata::from_bytes(&flac_bytes)?;
|
||||
|
||||
// Informations techniques issues du flux FLAC
|
||||
// Créer une instance TrackMetadata pour persister via l'interface unifiée
|
||||
let track_meta = cache.clone().track_metadata(&pk);
|
||||
let mut meta = track_meta.write().await;
|
||||
|
||||
// Informations techniques issues du flux FLAC (streaminfo)
|
||||
let streaminfo = parse_flac_streaminfo(&flac_bytes);
|
||||
if let Some(d) = metadata.duration_secs {
|
||||
let _ = cache
|
||||
.db
|
||||
.set_a_metadata(&pk, "duration_secs", Value::Number(Number::from(d)));
|
||||
}
|
||||
if let Some((sr, bps, total_samples)) = streaminfo {
|
||||
let _ = cache
|
||||
.db
|
||||
.set_a_metadata(&pk, "sample_rate", Value::Number(Number::from(sr)));
|
||||
let _ = cache
|
||||
.db
|
||||
.set_a_metadata(&pk, "bits_per_sample", Value::Number(Number::from(bps)));
|
||||
let _ = cache.db.set_a_metadata(
|
||||
&pk,
|
||||
"total_samples",
|
||||
Value::Number(Number::from(total_samples)),
|
||||
);
|
||||
let _ = meta.set_sample_rate(Some(sr)).await;
|
||||
let _ = meta.set_bits_per_sample(Some(bps)).await;
|
||||
let _ = meta.set_total_samples(Some(total_samples)).await;
|
||||
|
||||
// Calculer la durée si elle n'est pas disponible depuis les tags
|
||||
if metadata.duration_secs.is_none() && sr > 0 {
|
||||
let secs = (total_samples as f64 / sr as f64).round() as u64;
|
||||
let _ = cache
|
||||
.db
|
||||
.set_a_metadata(&pk, "duration_secs", Value::Number(Number::from(secs)));
|
||||
metadata.duration_secs = Some(secs);
|
||||
}
|
||||
if metadata.sample_rate.is_none() {
|
||||
metadata.sample_rate = Some(sr);
|
||||
}
|
||||
if metadata.bitrate.is_none() {
|
||||
// Approximate bitrate: sample_rate * bits_per_sample * channels / 1000
|
||||
if let Some(ch) = metadata.channels {
|
||||
let br = (sr as u64 * bps as u64 * ch as u64) / 1000;
|
||||
metadata.bitrate = Some(br as u32);
|
||||
}
|
||||
let _ = meta.set_duration(Some(Duration::from_secs(secs))).await;
|
||||
}
|
||||
}
|
||||
|
||||
// Métadonnées descriptives (tags)
|
||||
if let Some(title) = metadata.title.clone() {
|
||||
let _ = cache
|
||||
.db
|
||||
.set_a_metadata(&pk, "title", Value::String(title));
|
||||
// Déterminer la collection automatique avant de move les valeurs
|
||||
let auto_collection = if collection.is_none() {
|
||||
metadata.collection_key()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Métadonnées descriptives (tags) - en écrasant éventuellement celles du streaminfo
|
||||
if let Some(d) = metadata.duration_secs {
|
||||
let _ = meta.set_duration(Some(Duration::from_secs(d))).await;
|
||||
}
|
||||
if let Some(artist) = metadata.artist.clone() {
|
||||
let _ = cache
|
||||
.db
|
||||
.set_a_metadata(&pk, "artist", Value::String(artist));
|
||||
if let Some(title) = metadata.title {
|
||||
let _ = meta.set_title(Some(title)).await;
|
||||
}
|
||||
if let Some(album) = metadata.album.clone() {
|
||||
let _ = cache
|
||||
.db
|
||||
.set_a_metadata(&pk, "album", Value::String(album));
|
||||
if let Some(artist) = metadata.artist {
|
||||
let _ = meta.set_artist(Some(artist)).await;
|
||||
}
|
||||
if let Some(album) = metadata.album {
|
||||
let _ = meta.set_album(Some(album)).await;
|
||||
}
|
||||
if let Some(year) = metadata.year {
|
||||
let _ = cache
|
||||
.db
|
||||
.set_a_metadata(&pk, "year", Value::Number(Number::from(year)));
|
||||
let _ = meta.set_year(Some(year)).await;
|
||||
}
|
||||
if let Some(genre) = metadata.genre {
|
||||
let _ = meta.set_genre(Some(genre)).await;
|
||||
}
|
||||
if let Some(track_number) = metadata.track_number {
|
||||
let _ = cache
|
||||
.db
|
||||
.set_a_metadata(&pk, "track_number", Value::Number(Number::from(track_number)));
|
||||
let _ = meta.set_track_number(Some(track_number)).await;
|
||||
}
|
||||
if let Some(track_total) = metadata.track_total {
|
||||
let _ = cache
|
||||
.db
|
||||
.set_a_metadata(&pk, "track_total", Value::Number(Number::from(track_total)));
|
||||
let _ = meta.set_track_total(Some(track_total)).await;
|
||||
}
|
||||
if let Some(disc_number) = metadata.disc_number {
|
||||
let _ = cache
|
||||
.db
|
||||
.set_a_metadata(&pk, "disc_number", Value::Number(Number::from(disc_number)));
|
||||
let _ = meta.set_disc_number(Some(disc_number)).await;
|
||||
}
|
||||
if let Some(disc_total) = metadata.disc_total {
|
||||
let _ = cache
|
||||
.db
|
||||
.set_a_metadata(&pk, "disc_total", Value::Number(Number::from(disc_total)));
|
||||
let _ = meta.set_disc_total(Some(disc_total)).await;
|
||||
}
|
||||
if let Some(genre) = metadata.genre.clone() {
|
||||
let _ = cache
|
||||
.db
|
||||
.set_a_metadata(&pk, "genre", Value::String(genre));
|
||||
if let Some(channels) = metadata.channels {
|
||||
let _ = meta.set_channels(Some(channels)).await;
|
||||
}
|
||||
if let Some(sr) = metadata.sample_rate {
|
||||
let _ = cache
|
||||
.db
|
||||
.set_a_metadata(&pk, "sample_rate", Value::Number(Number::from(sr)));
|
||||
}
|
||||
if let Some(ch) = metadata.channels {
|
||||
let _ = cache
|
||||
.db
|
||||
.set_a_metadata(&pk, "channels", Value::Number(Number::from(ch)));
|
||||
}
|
||||
if let Some(br) = metadata.bitrate {
|
||||
let _ = cache
|
||||
.db
|
||||
.set_a_metadata(&pk, "bitrate", Value::Number(Number::from(br)));
|
||||
if let Some(bitrate) = metadata.bitrate {
|
||||
let _ = meta.set_bitrate(Some(bitrate)).await;
|
||||
}
|
||||
|
||||
// Libérer le lock explicitement avant les opérations de collection
|
||||
drop(meta);
|
||||
|
||||
// Mettre à jour la collection si les métadonnées en fournissent une
|
||||
if collection.is_none() {
|
||||
if let Some(auto_collection) = metadata.collection_key() {
|
||||
cache
|
||||
.db
|
||||
.add(&pk, None, Some(&auto_collection))
|
||||
.map_err(|e| anyhow::anyhow!("Database error: {}", e))?;
|
||||
cache.db.set_origin_url(&pk, url)?;
|
||||
}
|
||||
if let Some(auto_collection) = auto_collection {
|
||||
cache
|
||||
.db
|
||||
.add(&pk, None, Some(&auto_collection))
|
||||
.map_err(|e| anyhow::anyhow!("Database error: {}", e))?;
|
||||
cache.db.set_origin_url(&pk, url)?;
|
||||
}
|
||||
|
||||
Ok(pk)
|
||||
}
|
||||
|
||||
/// Récupère les métadonnées audio d'un fichier en cache
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `cache` - Instance du cache
|
||||
/// * `pk` - Clé primaire du fichier
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Les métadonnées audio désérialisées depuis le JSON stocké en DB
|
||||
///
|
||||
/// # Exemple
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// use pmoaudiocache::cache;
|
||||
///
|
||||
/// # async fn example(cache: &pmoaudiocache::cache::Cache, pk: &str) -> anyhow::Result<()> {
|
||||
/// let metadata = cache::get_metadata(cache, pk)?;
|
||||
/// println!("Title: {:?}", metadata.title);
|
||||
/// println!("Artist: {:?}", metadata.artist);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn get_metadata(cache: &Cache, pk: &str) -> Result<crate::metadata::AudioMetadata> {
|
||||
let read_value = |key: &str| -> Result<Option<Value>> {
|
||||
cache
|
||||
.db
|
||||
.get_a_metadata(pk, key)
|
||||
.map_err(|e| anyhow::anyhow!("Database error: {}", e))
|
||||
};
|
||||
|
||||
let read_string = |key: &str| -> Result<Option<String>> {
|
||||
Ok(read_value(key)?.and_then(|v| v.as_str().map(|s| s.to_string())))
|
||||
};
|
||||
|
||||
let read_u64 = |key: &str| -> Result<Option<u64>> {
|
||||
Ok(read_value(key)?.and_then(|v| v.as_u64()))
|
||||
};
|
||||
|
||||
let read_u32 = |key: &str| -> Result<Option<u32>> {
|
||||
Ok(read_value(key)?.and_then(|v| v.as_u64()).and_then(|n| n.try_into().ok()))
|
||||
};
|
||||
|
||||
let read_u8 = |key: &str| -> Result<Option<u8>> {
|
||||
Ok(read_value(key)?.and_then(|v| v.as_u64()).and_then(|n| n.try_into().ok()))
|
||||
};
|
||||
|
||||
let metadata = crate::metadata::AudioMetadata {
|
||||
title: read_string("title")?,
|
||||
artist: read_string("artist")?,
|
||||
album: read_string("album")?,
|
||||
year: read_u32("year")?,
|
||||
track_number: read_u32("track_number")?,
|
||||
track_total: read_u32("track_total")?,
|
||||
disc_number: read_u32("disc_number")?,
|
||||
disc_total: read_u32("disc_total")?,
|
||||
genre: read_string("genre")?,
|
||||
duration_secs: read_u64("duration_secs")?,
|
||||
sample_rate: read_u32("sample_rate")?,
|
||||
channels: read_u8("channels")?,
|
||||
bitrate: read_u32("bitrate")?,
|
||||
conversion: None,
|
||||
};
|
||||
|
||||
Ok(metadata)
|
||||
}
|
||||
|
||||
/// Parse minimal FLAC STREAMINFO (first metadata block) to retrieve sample rate,
|
||||
/// bits per sample, and total samples.
|
||||
fn parse_flac_streaminfo(data: &[u8]) -> Option<(u32, u8, u64)> {
|
||||
|
||||
@@ -28,12 +28,11 @@
|
||||
//! None,
|
||||
//! ).await?;
|
||||
//!
|
||||
//! // Lecture des métadonnées extraites
|
||||
//! let metadata = cache::get_metadata(&cache, &pk)?;
|
||||
//! println!(
|
||||
//! "Titre: {}",
|
||||
//! metadata.title.as_deref().unwrap_or("Inconnu")
|
||||
//! );
|
||||
//! // Lecture des métadonnées extraites via TrackMetadata
|
||||
//! use pmoaudiocache::metadata_ext::AudioTrackMetadataExt;
|
||||
//! let track_meta = cache.track_metadata(&pk);
|
||||
//! let title = track_meta.read().await.get_title().await?;
|
||||
//! println!("Titre: {}", title.unwrap_or_else(|| "Inconnu".to_string()));
|
||||
//!
|
||||
//! // Accès au fichier FLAC converti
|
||||
//! let flac_path = cache.get(&pk).await?;
|
||||
@@ -90,11 +89,10 @@ pub mod config_ext;
|
||||
|
||||
// Re-exports principaux
|
||||
pub use cache::{
|
||||
add_with_metadata_extraction, get_metadata, new_cache, new_cache_with_consolidation,
|
||||
AudioConfig, Cache,
|
||||
add_with_metadata_extraction, new_cache, new_cache_with_consolidation, AudioConfig, Cache,
|
||||
};
|
||||
pub use metadata::AudioMetadata;
|
||||
pub use metadata_ext::{AudioMetadataExt, AudioTrackMetadataExt};
|
||||
pub use metadata_ext::{AudioMetadataExt, AudioTrackMetadataExt, TrackMetadataDidlExt};
|
||||
pub use track_metadata::AudioCacheTrackMetadata;
|
||||
|
||||
#[cfg(feature = "pmoconfig")]
|
||||
|
||||
@@ -83,3 +83,46 @@ fn map_err(field: &str, pk: &str, err: MetadataError) -> anyhow::Error {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Extension pour convertir TrackMetadata en Resource DIDL-Lite (UPnP)
|
||||
#[async_trait::async_trait]
|
||||
pub trait TrackMetadataDidlExt {
|
||||
/// Convertit les métadonnées en Resource DIDL-Lite
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `url` - URL de la ressource audio
|
||||
///
|
||||
/// # Exemple
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// use pmoaudiocache::metadata_ext::{AudioTrackMetadataExt, TrackMetadataDidlExt};
|
||||
///
|
||||
/// let track_meta = cache.track_metadata(&pk);
|
||||
/// let resource = track_meta.read().await.to_didl_resource("http://example.com/track.flac".to_string()).await;
|
||||
/// ```
|
||||
async fn to_didl_resource(&self, url: String) -> pmodidl::Resource;
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl TrackMetadataDidlExt for dyn TrackMetadata {
|
||||
async fn to_didl_resource(&self, url: String) -> pmodidl::Resource {
|
||||
// Récupérer la durée et la formater pour DIDL-Lite (H:MM:SS)
|
||||
let duration = self.get_duration().await.ok().flatten().map(|d| {
|
||||
let secs = d.as_secs();
|
||||
let hours = secs / 3600;
|
||||
let minutes = (secs % 3600) / 60;
|
||||
let seconds = secs % 60;
|
||||
format!("{}:{:02}:{:02}", hours, minutes, seconds)
|
||||
});
|
||||
|
||||
pmodidl::Resource {
|
||||
protocol_info: "http-get:*:audio/flac:*".to_string(),
|
||||
bits_per_sample: self.get_bits_per_sample().await.ok().flatten().map(|b| b.to_string()),
|
||||
sample_frequency: self.get_sample_rate().await.ok().flatten().map(|sr| sr.to_string()),
|
||||
nr_audio_channels: self.get_channels().await.ok().flatten().map(|ch| ch.to_string()),
|
||||
duration,
|
||||
url,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,6 +176,68 @@ impl TrackMetadata for AudioCacheTrackMetadata {
|
||||
Ok(Some(()))
|
||||
}
|
||||
|
||||
async fn get_genre(&self) -> MetadataResult<String> {
|
||||
Ok(self.read_string("genre")?)
|
||||
}
|
||||
|
||||
async fn set_genre(&mut self, value: Option<String>) -> MetadataResult<()> {
|
||||
self.write_string("genre", value)?;
|
||||
let _ = self.touch().await?;
|
||||
Ok(Some(()))
|
||||
}
|
||||
|
||||
async fn get_track_number(&self) -> MetadataResult<u32> {
|
||||
Ok(match self.read_number("track_number")? {
|
||||
Some(n) => n.as_i64().and_then(|v| u32::try_from(v).ok()),
|
||||
None => None,
|
||||
})
|
||||
}
|
||||
|
||||
async fn set_track_number(&mut self, value: Option<u32>) -> MetadataResult<()> {
|
||||
self.write_number("track_number", value.map(|v| v as i64))?;
|
||||
let _ = self.touch().await?;
|
||||
Ok(Some(()))
|
||||
}
|
||||
|
||||
async fn get_track_total(&self) -> MetadataResult<u32> {
|
||||
Ok(match self.read_number("track_total")? {
|
||||
Some(n) => n.as_i64().and_then(|v| u32::try_from(v).ok()),
|
||||
None => None,
|
||||
})
|
||||
}
|
||||
|
||||
async fn set_track_total(&mut self, value: Option<u32>) -> MetadataResult<()> {
|
||||
self.write_number("track_total", value.map(|v| v as i64))?;
|
||||
let _ = self.touch().await?;
|
||||
Ok(Some(()))
|
||||
}
|
||||
|
||||
async fn get_disc_number(&self) -> MetadataResult<u32> {
|
||||
Ok(match self.read_number("disc_number")? {
|
||||
Some(n) => n.as_i64().and_then(|v| u32::try_from(v).ok()),
|
||||
None => None,
|
||||
})
|
||||
}
|
||||
|
||||
async fn set_disc_number(&mut self, value: Option<u32>) -> MetadataResult<()> {
|
||||
self.write_number("disc_number", value.map(|v| v as i64))?;
|
||||
let _ = self.touch().await?;
|
||||
Ok(Some(()))
|
||||
}
|
||||
|
||||
async fn get_disc_total(&self) -> MetadataResult<u32> {
|
||||
Ok(match self.read_number("disc_total")? {
|
||||
Some(n) => n.as_i64().and_then(|v| u32::try_from(v).ok()),
|
||||
None => None,
|
||||
})
|
||||
}
|
||||
|
||||
async fn set_disc_total(&mut self, value: Option<u32>) -> MetadataResult<()> {
|
||||
self.write_number("disc_total", value.map(|v| v as i64))?;
|
||||
let _ = self.touch().await?;
|
||||
Ok(Some(()))
|
||||
}
|
||||
|
||||
async fn get_duration(&self) -> MetadataResult<Duration> {
|
||||
Ok(self.read_duration()?)
|
||||
}
|
||||
@@ -225,6 +287,32 @@ impl TrackMetadata for AudioCacheTrackMetadata {
|
||||
Ok(Some(()))
|
||||
}
|
||||
|
||||
async fn get_channels(&self) -> MetadataResult<u8> {
|
||||
Ok(match self.read_number("channels")? {
|
||||
Some(n) => n.as_i64().and_then(|v| u8::try_from(v).ok()),
|
||||
None => None,
|
||||
})
|
||||
}
|
||||
|
||||
async fn set_channels(&mut self, value: Option<u8>) -> MetadataResult<()> {
|
||||
self.write_number("channels", value.map(|v| v as i64))?;
|
||||
let _ = self.touch().await?;
|
||||
Ok(Some(()))
|
||||
}
|
||||
|
||||
async fn get_bitrate(&self) -> MetadataResult<u32> {
|
||||
Ok(match self.read_number("bitrate")? {
|
||||
Some(n) => n.as_i64().and_then(|v| u32::try_from(v).ok()),
|
||||
None => None,
|
||||
})
|
||||
}
|
||||
|
||||
async fn set_bitrate(&mut self, value: Option<u32>) -> MetadataResult<()> {
|
||||
self.write_number("bitrate", value.map(|v| v as i64))?;
|
||||
let _ = self.touch().await?;
|
||||
Ok(Some(()))
|
||||
}
|
||||
|
||||
async fn get_track_id(&self) -> MetadataResult<String> {
|
||||
Ok(self.read_string("track_id")?)
|
||||
}
|
||||
|
||||
@@ -186,6 +186,46 @@ pub trait TrackMetadata: Send + Sync {
|
||||
Err(MetadataError::NotImplemented)
|
||||
}
|
||||
|
||||
async fn get_genre(&self) -> MetadataResult<String> {
|
||||
Err(MetadataError::NotImplemented)
|
||||
}
|
||||
|
||||
async fn set_genre(&mut self, _value: Option<String>) -> MetadataResult<()> {
|
||||
Err(MetadataError::NotImplemented)
|
||||
}
|
||||
|
||||
async fn get_track_number(&self) -> MetadataResult<u32> {
|
||||
Err(MetadataError::NotImplemented)
|
||||
}
|
||||
|
||||
async fn set_track_number(&mut self, _value: Option<u32>) -> MetadataResult<()> {
|
||||
Err(MetadataError::NotImplemented)
|
||||
}
|
||||
|
||||
async fn get_track_total(&self) -> MetadataResult<u32> {
|
||||
Err(MetadataError::NotImplemented)
|
||||
}
|
||||
|
||||
async fn set_track_total(&mut self, _value: Option<u32>) -> MetadataResult<()> {
|
||||
Err(MetadataError::NotImplemented)
|
||||
}
|
||||
|
||||
async fn get_disc_number(&self) -> MetadataResult<u32> {
|
||||
Err(MetadataError::NotImplemented)
|
||||
}
|
||||
|
||||
async fn set_disc_number(&mut self, _value: Option<u32>) -> MetadataResult<()> {
|
||||
Err(MetadataError::NotImplemented)
|
||||
}
|
||||
|
||||
async fn get_disc_total(&self) -> MetadataResult<u32> {
|
||||
Err(MetadataError::NotImplemented)
|
||||
}
|
||||
|
||||
async fn set_disc_total(&mut self, _value: Option<u32>) -> MetadataResult<()> {
|
||||
Err(MetadataError::NotImplemented)
|
||||
}
|
||||
|
||||
async fn get_duration(&self) -> MetadataResult<Duration> {
|
||||
Err(MetadataError::NotImplemented)
|
||||
}
|
||||
@@ -218,6 +258,22 @@ pub trait TrackMetadata: Send + Sync {
|
||||
Err(MetadataError::NotImplemented)
|
||||
}
|
||||
|
||||
async fn get_channels(&self) -> MetadataResult<u8> {
|
||||
Err(MetadataError::NotImplemented)
|
||||
}
|
||||
|
||||
async fn set_channels(&mut self, _value: Option<u8>) -> MetadataResult<()> {
|
||||
Err(MetadataError::NotImplemented)
|
||||
}
|
||||
|
||||
async fn get_bitrate(&self) -> MetadataResult<u32> {
|
||||
Err(MetadataError::NotImplemented)
|
||||
}
|
||||
|
||||
async fn set_bitrate(&mut self, _value: Option<u32>) -> MetadataResult<()> {
|
||||
Err(MetadataError::NotImplemented)
|
||||
}
|
||||
|
||||
async fn get_track_id(&self) -> MetadataResult<String> {
|
||||
Err(MetadataError::NotImplemented)
|
||||
}
|
||||
@@ -361,10 +417,17 @@ pub struct MemoryTrackMetadata {
|
||||
artist: Option<String>,
|
||||
album: Option<String>,
|
||||
year: Option<u32>,
|
||||
genre: Option<String>,
|
||||
track_number: Option<u32>,
|
||||
track_total: Option<u32>,
|
||||
disc_number: Option<u32>,
|
||||
disc_total: Option<u32>,
|
||||
duration: Option<Duration>,
|
||||
sample_rate: Option<u32>,
|
||||
total_samples: Option<u64>,
|
||||
bits_per_sample: Option<u8>,
|
||||
channels: Option<u8>,
|
||||
bitrate: Option<u32>,
|
||||
track_id: Option<String>,
|
||||
channel_id: Option<String>,
|
||||
event: Option<String>,
|
||||
@@ -423,6 +486,56 @@ impl TrackMetadata for MemoryTrackMetadata {
|
||||
Ok(Some(()))
|
||||
}
|
||||
|
||||
async fn get_genre(&self) -> MetadataResult<String> {
|
||||
Ok(self.genre.clone())
|
||||
}
|
||||
|
||||
async fn set_genre(&mut self, value: Option<String>) -> MetadataResult<()> {
|
||||
self.genre = value;
|
||||
self.touch().await?;
|
||||
Ok(Some(()))
|
||||
}
|
||||
|
||||
async fn get_track_number(&self) -> MetadataResult<u32> {
|
||||
Ok(self.track_number)
|
||||
}
|
||||
|
||||
async fn set_track_number(&mut self, value: Option<u32>) -> MetadataResult<()> {
|
||||
self.track_number = value;
|
||||
self.touch().await?;
|
||||
Ok(Some(()))
|
||||
}
|
||||
|
||||
async fn get_track_total(&self) -> MetadataResult<u32> {
|
||||
Ok(self.track_total)
|
||||
}
|
||||
|
||||
async fn set_track_total(&mut self, value: Option<u32>) -> MetadataResult<()> {
|
||||
self.track_total = value;
|
||||
self.touch().await?;
|
||||
Ok(Some(()))
|
||||
}
|
||||
|
||||
async fn get_disc_number(&self) -> MetadataResult<u32> {
|
||||
Ok(self.disc_number)
|
||||
}
|
||||
|
||||
async fn set_disc_number(&mut self, value: Option<u32>) -> MetadataResult<()> {
|
||||
self.disc_number = value;
|
||||
self.touch().await?;
|
||||
Ok(Some(()))
|
||||
}
|
||||
|
||||
async fn get_disc_total(&self) -> MetadataResult<u32> {
|
||||
Ok(self.disc_total)
|
||||
}
|
||||
|
||||
async fn set_disc_total(&mut self, value: Option<u32>) -> MetadataResult<()> {
|
||||
self.disc_total = value;
|
||||
self.touch().await?;
|
||||
Ok(Some(()))
|
||||
}
|
||||
|
||||
async fn get_duration(&self) -> MetadataResult<Duration> {
|
||||
Ok(self.duration)
|
||||
}
|
||||
@@ -463,6 +576,26 @@ impl TrackMetadata for MemoryTrackMetadata {
|
||||
Ok(Some(()))
|
||||
}
|
||||
|
||||
async fn get_channels(&self) -> MetadataResult<u8> {
|
||||
Ok(self.channels)
|
||||
}
|
||||
|
||||
async fn set_channels(&mut self, value: Option<u8>) -> MetadataResult<()> {
|
||||
self.channels = value;
|
||||
self.touch().await?;
|
||||
Ok(Some(()))
|
||||
}
|
||||
|
||||
async fn get_bitrate(&self) -> MetadataResult<u32> {
|
||||
Ok(self.bitrate)
|
||||
}
|
||||
|
||||
async fn set_bitrate(&mut self, value: Option<u32>) -> MetadataResult<()> {
|
||||
self.bitrate = value;
|
||||
self.touch().await?;
|
||||
Ok(Some(()))
|
||||
}
|
||||
|
||||
async fn get_track_id(&self) -> MetadataResult<String> {
|
||||
Ok(self.track_id.clone())
|
||||
}
|
||||
|
||||
@@ -204,33 +204,40 @@ impl ReadHandle {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Charger métadonnées
|
||||
let metadata = match pmoaudiocache::get_metadata(&*cache, &record.cache_pk) {
|
||||
Ok(m) => m,
|
||||
Err(_) => continue,
|
||||
};
|
||||
// Charger métadonnées via TrackMetadata
|
||||
use pmoaudiocache::metadata_ext::{AudioTrackMetadataExt, TrackMetadataDidlExt};
|
||||
let track_meta = cache.track_metadata(&record.cache_pk);
|
||||
let meta = track_meta.read().await;
|
||||
|
||||
// Construire l'URL via route_for
|
||||
let url = cache.route_for(&record.cache_pk, None);
|
||||
|
||||
// Créer le Resource DIDL
|
||||
let resource = metadata.to_didl_resource(url);
|
||||
// Créer le Resource DIDL via l'extension trait
|
||||
let resource = meta.to_didl_resource(url).await;
|
||||
|
||||
// Récupérer les métadonnées pour construire l'Item DIDL
|
||||
let title = meta.get_title().await.ok().flatten().unwrap_or_else(|| "Unknown".to_string());
|
||||
let artist = meta.get_artist().await.ok().flatten();
|
||||
let album = meta.get_album().await.ok().flatten();
|
||||
let genre = meta.get_genre().await.ok().flatten();
|
||||
let year = meta.get_year().await.ok().flatten();
|
||||
let track_number = meta.get_track_number().await.ok().flatten();
|
||||
|
||||
// Créer l'Item
|
||||
let item = Item {
|
||||
id: format!("{}:{}", self.playlist.id, pos + idx),
|
||||
parent_id: self.playlist.id.clone(),
|
||||
restricted: Some("1".to_string()),
|
||||
title: metadata.title.unwrap_or_else(|| "Unknown".to_string()),
|
||||
creator: metadata.artist.clone(),
|
||||
title: title.clone(),
|
||||
creator: artist.clone(),
|
||||
class: "object.item.audioItem.musicTrack".to_string(),
|
||||
artist: metadata.artist,
|
||||
album: metadata.album,
|
||||
genre: metadata.genre,
|
||||
artist,
|
||||
album,
|
||||
genre,
|
||||
album_art: None, // TODO: intégrer pmocovers
|
||||
album_art_pk: None,
|
||||
date: metadata.year.map(|y| y.to_string()),
|
||||
original_track_number: metadata.track_number.map(|n| n.to_string()),
|
||||
date: year.map(|y| y.to_string()),
|
||||
original_track_number: track_number.map(|n| n.to_string()),
|
||||
resources: vec![resource],
|
||||
descriptions: vec![],
|
||||
};
|
||||
|
||||
@@ -63,32 +63,6 @@ impl PlaylistTrack {
|
||||
|
||||
/// Récupère les métadonnées audio complètes depuis le cache
|
||||
///
|
||||
/// **Important** : Cette méthode récupère TOUTES les métadonnées de la base de données.
|
||||
/// Si vous n'avez besoin que d'un seul champ (ex: titre), utilisez plutôt les méthodes
|
||||
/// légères `title()`, `artist()`, etc. qui utilisent `get_a_metadata()`.
|
||||
///
|
||||
/// # Exemples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use pmoplaylist::*;
|
||||
/// # async fn example(track: PlaylistTrack) -> Result<()> {
|
||||
/// // ✅ BON : Si vous avez besoin de plusieurs champs
|
||||
/// let metadata = track.metadata().await?;
|
||||
/// let title = metadata.title.as_deref().unwrap_or("Unknown");
|
||||
/// let artist = metadata.artist.as_deref().unwrap_or("Unknown");
|
||||
/// let album = metadata.album.as_deref().unwrap_or("Unknown");
|
||||
///
|
||||
/// // ✅ MIEUX : Si vous n'avez besoin que d'un seul champ (plus léger)
|
||||
/// let title = track.title().await?.unwrap_or_else(|| "Unknown".to_string());
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub async fn metadata(&self) -> Result<pmoaudiocache::AudioMetadata> {
|
||||
let cache = crate::manager::audio_cache()?;
|
||||
pmoaudiocache::get_metadata(&*cache, &self.cache_pk)
|
||||
.map_err(|e| crate::Error::CacheError(e.to_string()))
|
||||
}
|
||||
|
||||
/// Retourne une instance de TrackMetadata pour ce morceau
|
||||
///
|
||||
/// Cette méthode fournit un accès unifié aux métadonnées via le trait `TrackMetadata`.
|
||||
|
||||
Reference in New Issue
Block a user