On continue le refactoring des sources
This commit is contained in:
@@ -363,10 +363,10 @@ pub fn new_cache(dir: &str, limit: usize) -> Result<Cache> {
|
||||
/// use pmoaudiocache::cache;
|
||||
///
|
||||
/// # async fn example() -> anyhow::Result<()> {
|
||||
/// let cache = cache::new_cache("./audio_cache", 1000, "http://localhost:8080")?;
|
||||
/// let cache = cache::new_cache("./audio_cache", 1000)?;
|
||||
/// let pk = cache::add_with_metadata_extraction(
|
||||
/// &cache,
|
||||
/// "http://example.com/track.flac",
|
||||
/// "https://example.com/track.flac",
|
||||
/// Some("artist:album")
|
||||
/// ).await?;
|
||||
/// # Ok(())
|
||||
|
||||
150
pmoaudiocache/src/flac.rs
Normal file
150
pmoaudiocache/src/flac.rs
Normal file
@@ -0,0 +1,150 @@
|
||||
// //! Module de conversion audio en FLAC
|
||||
// //!
|
||||
// //! Ce module gère la conversion de divers formats audio vers FLAC
|
||||
// //! pour standardiser le stockage dans le cache.
|
||||
|
||||
// use anyhow::{anyhow, Result};
|
||||
// use std::io::Cursor;
|
||||
// use symphonia::core::audio::SampleBuffer;
|
||||
// use symphonia::core::codecs::{DecoderOptions, CODEC_TYPE_NULL};
|
||||
// use symphonia::core::errors::Error as SymphoniaError;
|
||||
// use symphonia::core::formats::FormatOptions;
|
||||
// use symphonia::core::io::MediaSourceStream;
|
||||
// use symphonia::core::meta::MetadataOptions;
|
||||
// use symphonia::core::probe::Hint;
|
||||
|
||||
// /// Convertit des données audio en FLAC
|
||||
// ///
|
||||
// /// Cette fonction accepte n'importe quel format audio supporté par Symphonia
|
||||
// /// et le convertit en FLAC pour un stockage standardisé.
|
||||
// ///
|
||||
// /// # Arguments
|
||||
// ///
|
||||
// /// * `data` - Données audio brutes (n'importe quel format)
|
||||
// /// * `extension` - Extension du fichier source (optionnel, aide à la détection)
|
||||
// ///
|
||||
// /// # Returns
|
||||
// ///
|
||||
// /// Données audio au format FLAC
|
||||
// ///
|
||||
// /// # Exemple
|
||||
// ///
|
||||
// /// ```rust,no_run
|
||||
// /// use pmoaudiocache::flac::convert_to_flac;
|
||||
// ///
|
||||
// /// let mp3_data = std::fs::read("track.mp3").unwrap();
|
||||
// /// let flac_data = convert_to_flac(&mp3_data, Some("mp3")).unwrap();
|
||||
// /// ```
|
||||
// pub fn convert_to_flac(data: &[u8], extension: Option<&str>) -> Result<Vec<u8>> {
|
||||
// // Si c'est déjà du FLAC, on le retourne tel quel
|
||||
// if is_flac(data) {
|
||||
// return Ok(data.to_vec());
|
||||
// }
|
||||
|
||||
// // Créer un MediaSource depuis les données (en clonant pour avoir 'static)
|
||||
// let data_owned = data.to_vec();
|
||||
// let cursor = Cursor::new(data_owned);
|
||||
// let mss = MediaSourceStream::new(Box::new(cursor), Default::default());
|
||||
|
||||
// // Créer un hint si on a l'extension
|
||||
// let mut hint = Hint::new();
|
||||
// if let Some(ext) = extension {
|
||||
// hint.with_extension(ext);
|
||||
// }
|
||||
|
||||
// // Prober le format
|
||||
// let probed = symphonia::default::get_probe()
|
||||
// .format(
|
||||
// &hint,
|
||||
// mss,
|
||||
// &FormatOptions::default(),
|
||||
// &MetadataOptions::default(),
|
||||
// )
|
||||
// .map_err(|e| anyhow!("Impossible de détecter le format audio: {}", e))?;
|
||||
|
||||
// let mut format = probed.format;
|
||||
|
||||
// // Obtenir le premier track audio
|
||||
// let track = format
|
||||
// .tracks()
|
||||
// .iter()
|
||||
// .find(|t| t.codec_params.codec != CODEC_TYPE_NULL)
|
||||
// .ok_or_else(|| anyhow!("Aucune piste audio trouvée"))?;
|
||||
|
||||
// // Créer un décodeur
|
||||
// let mut decoder = symphonia::default::get_codecs()
|
||||
// .make(&track.codec_params, &DecoderOptions::default())
|
||||
// .map_err(|e| anyhow!("Impossible de créer le décodeur: {}", e))?;
|
||||
|
||||
// // Buffer pour stocker les samples décodés
|
||||
// let mut samples = Vec::new();
|
||||
// let track_id = track.id;
|
||||
|
||||
// // Décoder tous les packets
|
||||
// loop {
|
||||
// let packet = match format.next_packet() {
|
||||
// Ok(packet) => packet,
|
||||
// Err(SymphoniaError::ResetRequired) => {
|
||||
// // Reset du décodeur requis
|
||||
// decoder.reset();
|
||||
// continue;
|
||||
// }
|
||||
// Err(SymphoniaError::IoError(e)) if e.kind() == std::io::ErrorKind::UnexpectedEof => {
|
||||
// break;
|
||||
// }
|
||||
// Err(e) => return Err(anyhow!("Erreur lors de la lecture: {}", e)),
|
||||
// };
|
||||
|
||||
// // Ignorer les packets qui ne sont pas de notre track
|
||||
// if packet.track_id() != track_id {
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// match decoder.decode(&packet) {
|
||||
// Ok(decoded) => {
|
||||
// // Convertir les samples en format standard
|
||||
// let spec = *decoded.spec();
|
||||
// let duration = decoded.capacity() as u64;
|
||||
|
||||
// let mut sample_buf = SampleBuffer::<i16>::new(duration, spec);
|
||||
// sample_buf.copy_interleaved_ref(decoded);
|
||||
|
||||
// samples.extend_from_slice(sample_buf.samples());
|
||||
// }
|
||||
// Err(SymphoniaError::DecodeError(_)) => continue,
|
||||
// Err(e) => return Err(anyhow!("Erreur de décodage: {}", e)),
|
||||
// }
|
||||
// }
|
||||
|
||||
// if samples.is_empty() {
|
||||
// return Err(anyhow!("Aucun sample décodé"));
|
||||
// }
|
||||
|
||||
// // Note: Pour l'encodage FLAC, on aurait besoin d'une bibliothèque comme
|
||||
// // `flacenc` qui n'existe pas encore en Rust. Pour l'instant, on stocke
|
||||
// // les données telles quelles si c'est déjà du FLAC, sinon on retourne
|
||||
// // les données originales avec un warning.
|
||||
|
||||
// // TODO: Implémenter l'encodage FLAC quand une bibliothèque sera disponible
|
||||
// tracing::warn!("Encodage FLAC non implémenté, stockage du format original");
|
||||
// Ok(data.to_vec())
|
||||
// }
|
||||
|
||||
// /// Vérifie si les données sont déjà au format FLAC
|
||||
// fn is_flac(data: &[u8]) -> bool {
|
||||
// data.len() >= 4 && &data[0..4] == b"fLaC"
|
||||
// }
|
||||
|
||||
// #[cfg(test)]
|
||||
// mod tests {
|
||||
// use super::*;
|
||||
|
||||
// #[test]
|
||||
// fn test_is_flac() {
|
||||
// let flac_header = b"fLaC\x00\x00\x00\x22";
|
||||
// assert!(is_flac(flac_header));
|
||||
|
||||
// let not_flac = b"RIFF\x00\x00\x00\x00";
|
||||
// assert!(!is_flac(not_flac));
|
||||
// }
|
||||
// }
|
||||
@@ -1,134 +1,80 @@
|
||||
//! # pmoaudiocache - Cache de pistes audio pour PMOMusic
|
||||
//! # pmoaudiocache – Cache de pistes audio pour PMOMusic
|
||||
//!
|
||||
//! Cette crate fournit un système de cache pour les pistes audio avec conversion
|
||||
//! automatique en FLAC et extraction des métadonnées.
|
||||
//! `pmoaudiocache` s'appuie sur [`pmocache`] pour fournir un cache spécialisé
|
||||
//! dans les fichiers audio. Il assure la conversion transparente au format FLAC,
|
||||
//! l'extraction des métadonnées et la mise à disposition d'outils pour les exposer.
|
||||
//!
|
||||
//! ## Vue d'ensemble
|
||||
//! ## Fonctionnalités
|
||||
//!
|
||||
//! `pmoaudiocache` étend `pmocache` pour gérer spécifiquement les fichiers audio :
|
||||
//! - **Téléchargement asynchrone** via le système de download de `pmocache`
|
||||
//! - **Conversion automatique en FLAC** lors du téléchargement (via transformer)
|
||||
//! - **Extraction et stockage des métadonnées** en JSON dans la base de données
|
||||
//! - **Gestion de collections** basées sur artiste/album
|
||||
//! - **Streaming progressif** automatique (via `pmocache`)
|
||||
//! - **API REST complète** fournie par `pmocache`
|
||||
//! - conversion automatique des entrées en FLAC grâce à un `StreamTransformer` ;
|
||||
//! - extraction des tags (artiste, album, titre, etc.) via [`metadata::AudioMetadata`] ;
|
||||
//! - stockage des métadonnées dans la table `metadata` de `pmocache::DB` ;
|
||||
//! - helpers pour renseigner les collections à partir des tags ;
|
||||
//! - intégration optionnelle avec `pmoserver` (routes REST + diffusion de fichiers).
|
||||
//!
|
||||
//! ## Architecture
|
||||
//!
|
||||
//! Cette crate est une spécialisation minimale de `pmocache` :
|
||||
//! - Configuration via `AudioConfig`
|
||||
//! - Transformer FLAC pour la conversion automatique
|
||||
//! - Helpers pour l'extraction et la lecture des métadonnées
|
||||
//!
|
||||
//! Tout le reste (DB, API REST, streaming) est fourni par `pmocache`.
|
||||
//!
|
||||
//! ## Utilisation
|
||||
//!
|
||||
//! ### Exemple basique
|
||||
//! ## Exemple rapide
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! use pmoaudiocache::cache;
|
||||
//!
|
||||
//! #[tokio::main]
|
||||
//! async fn main() -> anyhow::Result<()> {
|
||||
//! // Créer le cache
|
||||
//! let cache = cache::new_cache("./audio_cache", 1000, "http://localhost:8080")?;
|
||||
//! let cache = cache::new_cache("./audio_cache", 500)?;
|
||||
//!
|
||||
//! // Ajouter une piste avec extraction des métadonnées
|
||||
//! // Télécharge la piste, déclenche la conversion FLAC et stocke les métadonnées.
|
||||
//! let pk = cache::add_with_metadata_extraction(
|
||||
//! &cache,
|
||||
//! "http://example.com/track.flac",
|
||||
//! None // collection auto-détectée depuis métadonnées
|
||||
//! "https://example.com/track.mp3",
|
||||
//! None,
|
||||
//! ).await?;
|
||||
//!
|
||||
//! // Lire les métadonnées
|
||||
//! // Lecture des métadonnées extraites
|
||||
//! let metadata = cache::get_metadata(&cache, &pk)?;
|
||||
//! println!("{} - {}",
|
||||
//! metadata.artist.as_deref().unwrap_or("Unknown"),
|
||||
//! metadata.title.as_deref().unwrap_or("Unknown")
|
||||
//! println!(
|
||||
//! "Titre: {}",
|
||||
//! metadata.title.as_deref().unwrap_or("Inconnu")
|
||||
//! );
|
||||
//!
|
||||
//! // Le fichier FLAC est disponible immédiatement après le download
|
||||
//! let file_path = cache.get(&pk).await?;
|
||||
//! println!("FLAC file: {:?}", file_path);
|
||||
//! // Accès au fichier FLAC converti
|
||||
//! let flac_path = cache.get(&pk).await?;
|
||||
//! println!("Fichier converti: {flac_path:?}");
|
||||
//!
|
||||
//! Ok(())
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! ### Utilisation avec pmoserver
|
||||
//! ## Intégration serveur (feature `pmoserver`)
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! use pmoaudiocache::AudioCacheExt;
|
||||
//! use pmoserver::ServerBuilder;
|
||||
//! Lorsque la feature `pmoserver` est activée, [`AudioCacheExt`] permet
|
||||
//! d'enregistrer automatiquement les routes suivantes :
|
||||
//!
|
||||
//! #[tokio::main]
|
||||
//! async fn main() -> anyhow::Result<()> {
|
||||
//! let mut server = ServerBuilder::new_configured().build();
|
||||
//! - `GET /audio/tracks/{pk}` : téléchargement/stream du FLAC original ;
|
||||
//! - `GET /audio/tracks/{pk}/{qualifier}` : variantes (ex: `orig`) ;
|
||||
//! - `GET /api/audio` / `POST /api/audio` / `DELETE /api/audio` : API REST générique ;
|
||||
//! - `GET /api/audio/{pk}/status` : suivi de téléchargement ;
|
||||
//! - endpoints OpenAPI/Swagger lorsqu'`openapi` est activée.
|
||||
//!
|
||||
//! // Initialiser le cache audio avec configuration automatique
|
||||
//! server.init_audio_cache_configured().await?;
|
||||
//! ## Métadonnées gérées
|
||||
//!
|
||||
//! server.start().await;
|
||||
//! server.wait().await;
|
||||
//! Ok(())
|
||||
//! }
|
||||
//! ```
|
||||
//! Le module [`metadata`] extrait notamment :
|
||||
//! - titre, artiste, album, genre ;
|
||||
//! - numéros de piste/disque et totaux associés ;
|
||||
//! - année, durée, bitrate, sample rate, nombre de canaux.
|
||||
//!
|
||||
//! ## API HTTP (avec feature "pmoserver")
|
||||
//! En l'absence d'artiste/album, aucune collection automatique n'est créée.
|
||||
//!
|
||||
//! Lorsque la feature `pmoserver` est activée, les routes suivantes sont disponibles :
|
||||
//! ## Modules
|
||||
//!
|
||||
//! ### Routes de fichiers
|
||||
//! - `GET /audio/tracks/{pk}` - Stream du fichier FLAC original
|
||||
//! - `GET /audio/tracks/{pk}/orig` - Alias pour l'original
|
||||
//! - [`cache`] : instanciation du cache et helpers de téléchargement ;
|
||||
//! - [`metadata`] : extraction/structure des métadonnées audio ;
|
||||
//! - [`config_ext`] *(feature `pmoconfig`)* : dérivation de la configuration depuis `pmoconfig`;
|
||||
//! - [`openapi`] *(feature `pmoserver`)* : documentation des routes REST.
|
||||
//!
|
||||
//! ### API REST
|
||||
//! - `GET /api/audio` - Liste toutes les pistes
|
||||
//! - `POST /api/audio` - Ajoute une piste depuis une URL
|
||||
//! - `GET /api/audio/{pk}` - Informations complètes d'une piste
|
||||
//! - `DELETE /api/audio/{pk}` - Supprime une piste
|
||||
//! - `GET /api/audio/{pk}/status` - Statut du téléchargement
|
||||
//! - `POST /api/audio/consolidate` - Consolide le cache
|
||||
//! - `DELETE /api/audio` - Purge tout le cache
|
||||
//! ## Crates voisines
|
||||
//!
|
||||
//! ## Métadonnées supportées
|
||||
//!
|
||||
//! Les métadonnées suivantes sont extraites automatiquement :
|
||||
//! - Titre, artiste, album
|
||||
//! - Année, genre
|
||||
//! - Numéro de piste/disque
|
||||
//! - Durée, taux d'échantillonnage, bitrate
|
||||
//! - Nombre de canaux
|
||||
//!
|
||||
//! ## Format des collections
|
||||
//!
|
||||
//! Les collections sont identifiées par une clé au format `"artist:album"`, avec :
|
||||
//! - Conversion en minuscules
|
||||
//! - Remplacement des espaces par des underscores
|
||||
//! - Exemple : `"Pink Floyd - Wish You Were Here"` → `"pink_floyd:wish_you_were_here"`
|
||||
//!
|
||||
//! ## Différences avec l'ancienne version
|
||||
//!
|
||||
//! Cette version refactorisée de `pmoaudiocache` :
|
||||
//! - ✅ **Supprime le champ `conversion_status`** : le système `Download` de `pmocache` gère déjà l'état asynchrone
|
||||
//! - ✅ **Utilise `pmocache::DB`** : plus de DB personnalisée, les métadonnées sont en JSON
|
||||
//! - ✅ **API REST générique** : fournie par `pmocache`, plus de code custom
|
||||
//! - ✅ **Code réduit de 52%** : de ~1681 lignes à ~800 lignes
|
||||
//! - ✅ **Streaming progressif** : automatique via `pmocache`
|
||||
//! - ✅ **Politique LRU optimisée** : nouvel index composite dans `pmocache`
|
||||
//!
|
||||
//! ## Dépendances principales
|
||||
//!
|
||||
//! - `pmocache` : Cache générique avec download asynchrone
|
||||
//! - `lofty` : Extraction de métadonnées audio
|
||||
//! - `tokio` : Runtime asynchrone
|
||||
//!
|
||||
//! ## Voir aussi
|
||||
//!
|
||||
//! - [`pmocache`] : Cache générique
|
||||
//! - [`pmocovers`] : Cache d'images (architecture similaire)
|
||||
//! - [`pmoserver`] : Serveur HTTP
|
||||
//! - [`pmocache`] : fondation générique ;
|
||||
//! - [`pmocovers`] : spécialisation images (architecture similaire) ;
|
||||
//! - [`pmoserver`] : serveur HTTP optionnel.
|
||||
|
||||
pub mod cache;
|
||||
pub mod metadata;
|
||||
|
||||
Reference in New Issue
Block a user