Files
pmomusic/pmoaudiocache/src/lib.rs

281 lines
9.7 KiB
Rust
Executable File
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! # 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<Arc<Cache>> = 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<Cache>) {
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<Cache>)` - 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<Arc<Cache>> {
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<Cache>` - Instance partagée du cache
async fn init_audio_cache(
&mut self,
cache_dir: &str,
limit: usize,
) -> anyhow::Result<std::sync::Arc<Cache>>;
/// 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<std::sync::Arc<Cache>>;
}
#[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<Arc<Cache>> {
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::<AudioConfig>)
.post(crate::api::add_audio_item)
.delete(pmocache::api::purge_cache::<AudioConfig>),
)
.route(
"/{pk}",
axum::routing::get(pmocache::api::get_item_info::<AudioConfig>)
.delete(pmocache::api::delete_item::<AudioConfig>),
)
.route(
"/{pk}/status",
axum::routing::get(pmocache::api::get_download_status::<AudioConfig>),
)
.route(
"/consolidate",
axum::routing::post(pmocache::api::consolidate_cache::<AudioConfig>),
)
.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<Arc<Cache>> {
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
}
}