2025-10-26 13:44:41 +01:00
|
|
|
|
//! # pmoaudiocache – Cache de pistes audio pour PMOMusic
|
2025-10-12 21:39:20 +02:00
|
|
|
|
//!
|
2025-10-26 13:44:41 +01:00
|
|
|
|
//! `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.
|
2025-10-12 21:39:20 +02:00
|
|
|
|
//!
|
2025-10-26 13:44:41 +01:00
|
|
|
|
//! ## Fonctionnalités
|
2025-10-12 21:39:20 +02:00
|
|
|
|
//!
|
2025-10-26 13:44:41 +01:00
|
|
|
|
//! - 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).
|
2025-12-17 10:10:56 +01:00
|
|
|
|
//! - 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.
|
2025-10-12 21:39:20 +02:00
|
|
|
|
//!
|
2025-10-26 13:44:41 +01:00
|
|
|
|
//! ## Exemple rapide
|
2025-10-12 21:39:20 +02:00
|
|
|
|
//!
|
|
|
|
|
|
//! ```rust,no_run
|
2025-10-17 23:08:06 +02:00
|
|
|
|
//! use pmoaudiocache::cache;
|
2025-10-12 21:39:20 +02:00
|
|
|
|
//!
|
|
|
|
|
|
//! #[tokio::main]
|
|
|
|
|
|
//! async fn main() -> anyhow::Result<()> {
|
2025-10-26 13:44:41 +01:00
|
|
|
|
//! let cache = cache::new_cache("./audio_cache", 500)?;
|
2025-10-13 11:35:07 +02:00
|
|
|
|
//!
|
2025-10-26 13:44:41 +01:00
|
|
|
|
//! // Télécharge la piste, déclenche la conversion FLAC et stocke les métadonnées.
|
2025-10-17 23:08:06 +02:00
|
|
|
|
//! let pk = cache::add_with_metadata_extraction(
|
|
|
|
|
|
//! &cache,
|
2025-10-26 13:44:41 +01:00
|
|
|
|
//! "https://example.com/track.mp3",
|
|
|
|
|
|
//! None,
|
2025-10-13 11:35:07 +02:00
|
|
|
|
//! ).await?;
|
|
|
|
|
|
//!
|
2025-11-23 16:16:05 +01:00
|
|
|
|
//! // 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()));
|
2025-10-12 21:39:20 +02:00
|
|
|
|
//!
|
2025-10-26 13:44:41 +01:00
|
|
|
|
//! // Accès au fichier FLAC converti
|
|
|
|
|
|
//! let flac_path = cache.get(&pk).await?;
|
|
|
|
|
|
//! println!("Fichier converti: {flac_path:?}");
|
2025-10-12 21:39:20 +02:00
|
|
|
|
//!
|
|
|
|
|
|
//! Ok(())
|
|
|
|
|
|
//! }
|
|
|
|
|
|
//! ```
|
|
|
|
|
|
//!
|
2025-10-26 13:44:41 +01:00
|
|
|
|
//! ## Intégration serveur (feature `pmoserver`)
|
2025-10-12 21:39:20 +02:00
|
|
|
|
//!
|
2025-10-26 13:44:41 +01:00
|
|
|
|
//! Lorsque la feature `pmoserver` est activée, [`AudioCacheExt`] permet
|
|
|
|
|
|
//! d'enregistrer automatiquement les routes suivantes :
|
2025-10-12 21:39:20 +02:00
|
|
|
|
//!
|
2025-10-26 13:44:41 +01:00
|
|
|
|
//! - `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.
|
2025-10-12 21:39:20 +02:00
|
|
|
|
//!
|
2025-10-26 13:44:41 +01:00
|
|
|
|
//! ## Métadonnées gérées
|
2025-10-12 21:39:20 +02:00
|
|
|
|
//!
|
2025-10-26 13:44:41 +01:00
|
|
|
|
//! 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.
|
2025-10-17 23:08:06 +02:00
|
|
|
|
//!
|
2025-10-26 13:44:41 +01:00
|
|
|
|
//! En l'absence d'artiste/album, aucune collection automatique n'est créée.
|
2025-10-17 23:08:06 +02:00
|
|
|
|
//!
|
2025-10-26 13:44:41 +01:00
|
|
|
|
//! ## Modules
|
2025-10-12 21:39:20 +02:00
|
|
|
|
//!
|
2025-10-26 13:44:41 +01:00
|
|
|
|
//! - [`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.
|
2025-10-12 21:39:20 +02:00
|
|
|
|
//!
|
2025-10-26 13:44:41 +01:00
|
|
|
|
//! ## Crates voisines
|
2025-10-12 21:39:20 +02:00
|
|
|
|
//!
|
2025-10-26 13:44:41 +01:00
|
|
|
|
//! - [`pmocache`] : fondation générique ;
|
|
|
|
|
|
//! - [`pmocovers`] : spécialisation images (architecture similaire) ;
|
|
|
|
|
|
//! - [`pmoserver`] : serveur HTTP optionnel.
|
2025-10-12 21:39:20 +02:00
|
|
|
|
|
2025-10-17 23:08:06 +02:00
|
|
|
|
pub mod cache;
|
2025-10-19 13:42:29 +02:00
|
|
|
|
pub mod metadata;
|
2025-10-27 12:32:31 +01:00
|
|
|
|
pub mod metadata_ext;
|
2025-11-02 15:06:27 +01:00
|
|
|
|
pub mod track_metadata;
|
2025-10-12 21:39:20 +02:00
|
|
|
|
|
2025-12-17 15:15:10 +01:00
|
|
|
|
/// 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;
|
|
|
|
|
|
|
2025-11-25 08:24:08 +01:00
|
|
|
|
#[cfg(feature = "pmoserver")]
|
|
|
|
|
|
pub mod api;
|
|
|
|
|
|
|
2025-10-12 21:39:20 +02:00
|
|
|
|
#[cfg(feature = "pmoserver")]
|
2025-10-17 23:08:06 +02:00
|
|
|
|
pub mod openapi;
|
|
|
|
|
|
|
2025-10-25 17:14:24 +02:00
|
|
|
|
#[cfg(feature = "pmoconfig")]
|
|
|
|
|
|
pub mod config_ext;
|
|
|
|
|
|
|
2025-10-17 23:08:06 +02:00
|
|
|
|
// Re-exports principaux
|
2025-11-16 08:34:33 +01:00
|
|
|
|
pub use cache::{
|
2025-11-23 16:16:05 +01:00
|
|
|
|
add_with_metadata_extraction, new_cache, new_cache_with_consolidation, AudioConfig, Cache,
|
2025-11-16 08:34:33 +01:00
|
|
|
|
};
|
2025-10-17 23:08:06 +02:00
|
|
|
|
pub use metadata::AudioMetadata;
|
2025-11-23 16:16:05 +01:00
|
|
|
|
pub use metadata_ext::{AudioMetadataExt, AudioTrackMetadataExt, TrackMetadataDidlExt};
|
2025-11-02 15:06:27 +01:00
|
|
|
|
pub use track_metadata::AudioCacheTrackMetadata;
|
2025-10-12 21:39:20 +02:00
|
|
|
|
|
2025-10-25 17:14:24 +02:00
|
|
|
|
#[cfg(feature = "pmoconfig")]
|
|
|
|
|
|
pub use config_ext::AudioCacheConfigExt;
|
|
|
|
|
|
|
2025-10-12 21:39:20 +02:00
|
|
|
|
#[cfg(feature = "pmoserver")]
|
2025-10-17 23:45:01 +02:00
|
|
|
|
pub use openapi::ApiDoc;
|
2025-10-12 21:39:20 +02:00
|
|
|
|
|
2025-11-05 20:48:52 +00:00
|
|
|
|
// ============================================================================
|
|
|
|
|
|
// 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.
|
|
|
|
|
|
///
|
2025-11-05 20:54:07 +00:00
|
|
|
|
/// # 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`]
|
|
|
|
|
|
///
|
2025-11-05 20:48:52 +00:00
|
|
|
|
/// # 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
|
|
|
|
|
|
///
|
2025-11-05 20:54:07 +00:00
|
|
|
|
/// 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.
|
|
|
|
|
|
///
|
2025-11-05 20:48:52 +00:00
|
|
|
|
/// # 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()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-17 23:45:01 +02:00
|
|
|
|
// ============================================================================
|
|
|
|
|
|
// Extension pmoserver (inline comme pmocovers)
|
|
|
|
|
|
// ============================================================================
|
|
|
|
|
|
|
|
|
|
|
|
/// Trait pour étendre un serveur HTTP avec des fonctionnalités de cache audio.
|
2025-10-12 21:39:20 +02:00
|
|
|
|
#[cfg(feature = "pmoserver")]
|
2025-10-17 23:45:01 +02:00
|
|
|
|
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
|
2025-10-19 13:42:29 +02:00
|
|
|
|
async fn init_audio_cache(
|
|
|
|
|
|
&mut self,
|
|
|
|
|
|
cache_dir: &str,
|
|
|
|
|
|
limit: usize,
|
|
|
|
|
|
) -> anyhow::Result<std::sync::Arc<Cache>>;
|
2025-10-17 23:45:01 +02:00
|
|
|
|
|
|
|
|
|
|
/// 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")]
|
2025-12-17 10:10:56 +01:00
|
|
|
|
use pmocache::pmoserver_ext::create_file_router;
|
2025-10-17 23:45:01 +02:00
|
|
|
|
#[cfg(feature = "pmoserver")]
|
|
|
|
|
|
use utoipa::OpenApi;
|
|
|
|
|
|
|
|
|
|
|
|
#[cfg(feature = "pmoserver")]
|
|
|
|
|
|
impl AudioCacheExt for pmoserver::Server {
|
2025-10-19 13:42:29 +02:00
|
|
|
|
async fn init_audio_cache(
|
|
|
|
|
|
&mut self,
|
|
|
|
|
|
cache_dir: &str,
|
|
|
|
|
|
limit: usize,
|
|
|
|
|
|
) -> anyhow::Result<Arc<Cache>> {
|
2025-10-18 09:58:39 +02:00
|
|
|
|
let cache = Arc::new(crate::cache::new_cache(cache_dir, limit)?);
|
2025-10-17 23:45:01 +02:00
|
|
|
|
|
|
|
|
|
|
// 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(),
|
2025-10-19 13:42:29 +02:00
|
|
|
|
"audio/flac", // Content-Type
|
2025-10-17 23:45:01 +02:00
|
|
|
|
);
|
|
|
|
|
|
self.add_router("/", file_router).await;
|
|
|
|
|
|
|
2025-12-17 10:10:56 +01:00
|
|
|
|
// 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());
|
2025-11-25 08:24:08 +01:00
|
|
|
|
|
|
|
|
|
|
// 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),
|
|
|
|
|
|
)
|
2025-11-29 14:19:16 +01:00
|
|
|
|
.with_state(cache.clone()),
|
2025-11-25 08:24:08 +01:00
|
|
|
|
);
|
|
|
|
|
|
|
2025-10-17 23:45:01 +02:00
|
|
|
|
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>> {
|
2025-10-25 17:14:24 +02:00
|
|
|
|
use crate::AudioCacheConfigExt;
|
2025-10-17 23:45:01 +02:00
|
|
|
|
let config = pmoconfig::get_config();
|
2025-10-25 17:14:24 +02:00
|
|
|
|
let cache_dir = config.get_audiocache_dir()?;
|
|
|
|
|
|
let limit = config.get_audiocache_size()?;
|
2025-10-17 23:45:01 +02:00
|
|
|
|
self.init_audio_cache(&cache_dir, limit).await
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|