From b7995a14a1a98e85b6a6b5fdfa3b55a444099e60 Mon Sep 17 00:00:00 2001 From: Eric Coissac Date: Sun, 23 Nov 2025 16:16:05 +0100 Subject: [PATCH] =?UTF-8?q?fin=20de=20l'unification=20des=20m=C3=A9tadonn?= =?UTF-8?q?=C3=A9es=20de=20pmocacheaudio?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pmoaudiocache/src/cache.rs | 247 ++++++++-------------------- pmoaudiocache/src/lib.rs | 16 +- pmoaudiocache/src/metadata_ext.rs | 43 +++++ pmoaudiocache/src/track_metadata.rs | 88 ++++++++++ pmometadata/src/lib.rs | 133 +++++++++++++++ pmoplaylist/src/handle/read.rs | 35 ++-- pmoplaylist/src/track.rs | 26 --- 7 files changed, 363 insertions(+), 225 deletions(-) diff --git a/pmoaudiocache/src/cache.rs b/pmoaudiocache/src/cache.rs index 7f332d71..969b2871 100644 --- a/pmoaudiocache/src/cache.rs +++ b/pmoaudiocache/src/cache.rs @@ -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::with_transformer(dir, limit, Some(transformer_factory)) } -fn persist_transform_streaminfo(cache: &Cache, pk: &str, tmeta: &TransformMetadata) { +async fn persist_transform_streaminfo(cache: Arc, 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, url: &str, collection: Option<&str>, ) -> Result { + 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 { - let read_value = |key: &str| -> Result> { - cache - .db - .get_a_metadata(pk, key) - .map_err(|e| anyhow::anyhow!("Database error: {}", e)) - }; - - let read_string = |key: &str| -> Result> { - Ok(read_value(key)?.and_then(|v| v.as_str().map(|s| s.to_string()))) - }; - - let read_u64 = |key: &str| -> Result> { - Ok(read_value(key)?.and_then(|v| v.as_u64())) - }; - - let read_u32 = |key: &str| -> Result> { - Ok(read_value(key)?.and_then(|v| v.as_u64()).and_then(|n| n.try_into().ok())) - }; - - let read_u8 = |key: &str| -> Result> { - 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)> { diff --git a/pmoaudiocache/src/lib.rs b/pmoaudiocache/src/lib.rs index c375b3b7..d48aedb7 100755 --- a/pmoaudiocache/src/lib.rs +++ b/pmoaudiocache/src/lib.rs @@ -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")] diff --git a/pmoaudiocache/src/metadata_ext.rs b/pmoaudiocache/src/metadata_ext.rs index 5c9df714..ac2b1653 100644 --- a/pmoaudiocache/src/metadata_ext.rs +++ b/pmoaudiocache/src/metadata_ext.rs @@ -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, + } + } +} diff --git a/pmoaudiocache/src/track_metadata.rs b/pmoaudiocache/src/track_metadata.rs index 47ae25ae..e67f9705 100644 --- a/pmoaudiocache/src/track_metadata.rs +++ b/pmoaudiocache/src/track_metadata.rs @@ -176,6 +176,68 @@ impl TrackMetadata for AudioCacheTrackMetadata { Ok(Some(())) } + async fn get_genre(&self) -> MetadataResult { + Ok(self.read_string("genre")?) + } + + async fn set_genre(&mut self, value: Option) -> MetadataResult<()> { + self.write_string("genre", value)?; + let _ = self.touch().await?; + Ok(Some(())) + } + + async fn get_track_number(&self) -> MetadataResult { + 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) -> 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 { + 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) -> 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 { + 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) -> 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 { + 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) -> MetadataResult<()> { + self.write_number("disc_total", value.map(|v| v as i64))?; + let _ = self.touch().await?; + Ok(Some(())) + } + async fn get_duration(&self) -> MetadataResult { Ok(self.read_duration()?) } @@ -225,6 +287,32 @@ impl TrackMetadata for AudioCacheTrackMetadata { Ok(Some(())) } + async fn get_channels(&self) -> MetadataResult { + 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) -> MetadataResult<()> { + self.write_number("channels", value.map(|v| v as i64))?; + let _ = self.touch().await?; + Ok(Some(())) + } + + async fn get_bitrate(&self) -> MetadataResult { + 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) -> MetadataResult<()> { + self.write_number("bitrate", value.map(|v| v as i64))?; + let _ = self.touch().await?; + Ok(Some(())) + } + async fn get_track_id(&self) -> MetadataResult { Ok(self.read_string("track_id")?) } diff --git a/pmometadata/src/lib.rs b/pmometadata/src/lib.rs index ec97b9e4..d27979d2 100755 --- a/pmometadata/src/lib.rs +++ b/pmometadata/src/lib.rs @@ -186,6 +186,46 @@ pub trait TrackMetadata: Send + Sync { Err(MetadataError::NotImplemented) } + async fn get_genre(&self) -> MetadataResult { + Err(MetadataError::NotImplemented) + } + + async fn set_genre(&mut self, _value: Option) -> MetadataResult<()> { + Err(MetadataError::NotImplemented) + } + + async fn get_track_number(&self) -> MetadataResult { + Err(MetadataError::NotImplemented) + } + + async fn set_track_number(&mut self, _value: Option) -> MetadataResult<()> { + Err(MetadataError::NotImplemented) + } + + async fn get_track_total(&self) -> MetadataResult { + Err(MetadataError::NotImplemented) + } + + async fn set_track_total(&mut self, _value: Option) -> MetadataResult<()> { + Err(MetadataError::NotImplemented) + } + + async fn get_disc_number(&self) -> MetadataResult { + Err(MetadataError::NotImplemented) + } + + async fn set_disc_number(&mut self, _value: Option) -> MetadataResult<()> { + Err(MetadataError::NotImplemented) + } + + async fn get_disc_total(&self) -> MetadataResult { + Err(MetadataError::NotImplemented) + } + + async fn set_disc_total(&mut self, _value: Option) -> MetadataResult<()> { + Err(MetadataError::NotImplemented) + } + async fn get_duration(&self) -> MetadataResult { Err(MetadataError::NotImplemented) } @@ -218,6 +258,22 @@ pub trait TrackMetadata: Send + Sync { Err(MetadataError::NotImplemented) } + async fn get_channels(&self) -> MetadataResult { + Err(MetadataError::NotImplemented) + } + + async fn set_channels(&mut self, _value: Option) -> MetadataResult<()> { + Err(MetadataError::NotImplemented) + } + + async fn get_bitrate(&self) -> MetadataResult { + Err(MetadataError::NotImplemented) + } + + async fn set_bitrate(&mut self, _value: Option) -> MetadataResult<()> { + Err(MetadataError::NotImplemented) + } + async fn get_track_id(&self) -> MetadataResult { Err(MetadataError::NotImplemented) } @@ -361,10 +417,17 @@ pub struct MemoryTrackMetadata { artist: Option, album: Option, year: Option, + genre: Option, + track_number: Option, + track_total: Option, + disc_number: Option, + disc_total: Option, duration: Option, sample_rate: Option, total_samples: Option, bits_per_sample: Option, + channels: Option, + bitrate: Option, track_id: Option, channel_id: Option, event: Option, @@ -423,6 +486,56 @@ impl TrackMetadata for MemoryTrackMetadata { Ok(Some(())) } + async fn get_genre(&self) -> MetadataResult { + Ok(self.genre.clone()) + } + + async fn set_genre(&mut self, value: Option) -> MetadataResult<()> { + self.genre = value; + self.touch().await?; + Ok(Some(())) + } + + async fn get_track_number(&self) -> MetadataResult { + Ok(self.track_number) + } + + async fn set_track_number(&mut self, value: Option) -> MetadataResult<()> { + self.track_number = value; + self.touch().await?; + Ok(Some(())) + } + + async fn get_track_total(&self) -> MetadataResult { + Ok(self.track_total) + } + + async fn set_track_total(&mut self, value: Option) -> MetadataResult<()> { + self.track_total = value; + self.touch().await?; + Ok(Some(())) + } + + async fn get_disc_number(&self) -> MetadataResult { + Ok(self.disc_number) + } + + async fn set_disc_number(&mut self, value: Option) -> MetadataResult<()> { + self.disc_number = value; + self.touch().await?; + Ok(Some(())) + } + + async fn get_disc_total(&self) -> MetadataResult { + Ok(self.disc_total) + } + + async fn set_disc_total(&mut self, value: Option) -> MetadataResult<()> { + self.disc_total = value; + self.touch().await?; + Ok(Some(())) + } + async fn get_duration(&self) -> MetadataResult { Ok(self.duration) } @@ -463,6 +576,26 @@ impl TrackMetadata for MemoryTrackMetadata { Ok(Some(())) } + async fn get_channels(&self) -> MetadataResult { + Ok(self.channels) + } + + async fn set_channels(&mut self, value: Option) -> MetadataResult<()> { + self.channels = value; + self.touch().await?; + Ok(Some(())) + } + + async fn get_bitrate(&self) -> MetadataResult { + Ok(self.bitrate) + } + + async fn set_bitrate(&mut self, value: Option) -> MetadataResult<()> { + self.bitrate = value; + self.touch().await?; + Ok(Some(())) + } + async fn get_track_id(&self) -> MetadataResult { Ok(self.track_id.clone()) } diff --git a/pmoplaylist/src/handle/read.rs b/pmoplaylist/src/handle/read.rs index 1fa832fc..6f34a8fb 100644 --- a/pmoplaylist/src/handle/read.rs +++ b/pmoplaylist/src/handle/read.rs @@ -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![], }; diff --git a/pmoplaylist/src/track.rs b/pmoplaylist/src/track.rs index 94bd493c..dad6beb1 100755 --- a/pmoplaylist/src/track.rs +++ b/pmoplaylist/src/track.rs @@ -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 { - 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`.