//! # pmoaudiocache – Cache de pistes audio pour PMOMusic //! //! `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. //! //! ## Fonctionnalités //! //! - 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). //! - référence de fichiers FLAC locaux : [`cache::add_local_file`] détecte les fichiers déjà au //! bon format et enregistre une entrée du cache sans recopier les octets tout en laissant les //! autres formats passer par la conversion standard. //! - support complet des lazy PK hérités de [`pmocache`], permettant de publier des playlists //! avec des entrées différées et de déclencher le téléchargement lors de la première lecture. //! //! ## Exemple rapide //! //! ```rust,no_run //! use pmoaudiocache::cache; //! //! #[tokio::main] //! async fn main() -> anyhow::Result<()> { //! let cache = cache::new_cache("./audio_cache", 500)?; //! //! // Télécharge la piste, déclenche la conversion FLAC et stocke les métadonnées. //! let pk = cache::add_with_metadata_extraction( //! &cache, //! "https://example.com/track.mp3", //! None, //! ).await?; //! //! // 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?; //! println!("Fichier converti: {flac_path:?}"); //! //! Ok(()) //! } //! ``` //! //! ## Intégration serveur (feature `pmoserver`) //! //! Lorsque la feature `pmoserver` est activée, [`AudioCacheExt`] permet //! d'enregistrer automatiquement les routes suivantes : //! //! - `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. //! //! ## Métadonnées gérées //! //! 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. //! //! En l'absence d'artiste/album, aucune collection automatique n'est créée. //! //! ## Modules //! //! - [`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. //! //! ## Crates voisines //! //! - [`pmocache`] : fondation générique ; //! - [`pmocovers`] : spécialisation images (architecture similaire) ; //! - [`pmoserver`] : serveur HTTP optionnel. pub mod cache; pub mod metadata; pub mod metadata_ext; pub mod track_metadata; /// Module public pour la création de transformers FLAC streaming /// /// Ce module expose les fonctionnalités de conversion FLAC progressive /// pour permettre aux utilisateurs de créer des transformers custom ou /// de réutiliser les implémentations par défaut. pub mod streaming; #[cfg(feature = "pmoserver")] pub mod api; #[cfg(feature = "pmoserver")] pub mod openapi; #[cfg(feature = "pmoconfig")] pub mod config_ext; // Re-exports principaux pub use cache::{ add_with_metadata_extraction, new_cache, new_cache_with_consolidation, AudioConfig, Cache, }; pub use metadata::AudioMetadata; pub use metadata_ext::{AudioMetadataExt, AudioTrackMetadataExt, TrackMetadataDidlExt}; pub use track_metadata::AudioCacheTrackMetadata; #[cfg(feature = "pmoconfig")] pub use config_ext::AudioCacheConfigExt; #[cfg(feature = "pmoserver")] pub use openapi::ApiDoc; // ============================================================================ // Registre global singleton // ============================================================================ use once_cell::sync::OnceCell; use std::sync::Arc; static AUDIO_CACHE: OnceCell> = OnceCell::new(); /// Enregistre le cache audio global /// /// Cette fonction doit être appelée au démarrage de l'application /// pour rendre le cache audio disponible globalement. /// /// # Arguments /// /// * `cache` - Instance partagée du cache audio à enregistrer /// /// # Behavior /// /// - Si appelée plusieurs fois, seul le premier appel prend effet /// - Thread-safe: peut être appelée depuis plusieurs threads simultanément /// - Une fois enregistré, le cache est accessible via [`get_audio_cache`] /// /// # Examples /// /// ```rust,ignore /// use pmoaudiocache::{new_cache, register_audio_cache}; /// use std::sync::Arc; /// /// let cache = Arc::new(new_cache("./cache", 1000)?); /// register_audio_cache(cache); /// ``` pub fn register_audio_cache(cache: Arc) { let _ = AUDIO_CACHE.set(cache); } /// Accès global au cache audio /// /// Retourne une référence au cache audio enregistré via [`register_audio_cache`], /// ou `None` si aucun cache n'a été enregistré. /// /// # Returns /// /// * `Some(Arc)` - Instance partagée du cache audio si enregistré /// * `None` - Si aucun cache n'a été enregistré /// /// # Thread Safety /// /// Cette fonction est thread-safe et peut être appelée depuis plusieurs threads. /// /// # Examples /// /// ```rust,ignore /// use pmoaudiocache::get_audio_cache; /// /// if let Some(cache) = get_audio_cache() { /// // Utiliser le cache /// } /// ``` pub fn get_audio_cache() -> Option> { AUDIO_CACHE.get().cloned() } // ============================================================================ // Extension pmoserver (inline comme pmocovers) // ============================================================================ /// Trait pour étendre un serveur HTTP avec des fonctionnalités de cache audio. #[cfg(feature = "pmoserver")] pub trait AudioCacheExt { /// Initialise le cache audio et enregistre les routes HTTP. /// /// # Arguments /// /// * `cache_dir` - Répertoire de stockage du cache /// * `limit` - Limite de taille du cache (en nombre de pistes) /// /// # Returns /// /// * `Arc` - Instance partagée du cache async fn init_audio_cache( &mut self, cache_dir: &str, limit: usize, ) -> anyhow::Result>; /// Initialise le cache audio avec la configuration par défaut. /// /// Utilise automatiquement les paramètres de `pmoconfig::Config`. async fn init_audio_cache_configured(&mut self) -> anyhow::Result>; } #[cfg(feature = "pmoserver")] use pmocache::pmoserver_ext::create_file_router; #[cfg(feature = "pmoserver")] use utoipa::OpenApi; #[cfg(feature = "pmoserver")] impl AudioCacheExt for pmoserver::Server { async fn init_audio_cache( &mut self, cache_dir: &str, limit: usize, ) -> anyhow::Result> { let cache = Arc::new(crate::cache::new_cache(cache_dir, limit)?); // Router de fichiers pour servir les pistes FLAC // Routes: GET /audio/tracks/{pk} et GET /audio/tracks/{pk}/{param} let file_router = create_file_router( cache.clone(), "audio/flac", // Content-Type ); self.add_router("/", file_router).await; // API REST (handlers génériques + POST spécialisé audio) let mut api_router = axum::Router::new() .route( "/", axum::routing::get(pmocache::api::list_items::) .post(crate::api::add_audio_item) .delete(pmocache::api::purge_cache::), ) .route( "/{pk}", axum::routing::get(pmocache::api::get_item_info::) .delete(pmocache::api::delete_item::), ) .route( "/{pk}/status", axum::routing::get(pmocache::api::get_download_status::), ) .route( "/consolidate", axum::routing::post(pmocache::api::consolidate_cache::), ) .with_state(cache.clone()); // Ajouter les endpoints audio spécifiques // Route: GET /api/audio/{pk}/cover-url api_router = api_router.merge( axum::Router::new() .route( "/{pk}/cover-url", axum::routing::get(crate::api::get_cover_url), ) .with_state(cache.clone()), ); let openapi = crate::ApiDoc::openapi(); self.add_openapi(api_router, openapi, "audio").await; Ok(cache) } async fn init_audio_cache_configured(&mut self) -> anyhow::Result> { use crate::AudioCacheConfigExt; let config = pmoconfig::get_config(); let cache_dir = config.get_audiocache_dir()?; let limit = config.get_audiocache_size()?; self.init_audio_cache(&cache_dir, limit).await } }