fin de l'unification des métadonnées de pmocacheaudio

This commit is contained in:
2025-11-23 16:16:05 +01:00
parent 17edc082b1
commit b7995a14a1
7 changed files with 363 additions and 225 deletions

View File

@@ -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)> {

View File

@@ -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")]

View File

@@ -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,
}
}
}

View File

@@ -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")?)
}

View File

@@ -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())
}

View File

@@ -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![],
};

View File

@@ -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`.