2025-10-12 21:39:20 +02:00
|
|
|
//! # pmoaudiocache - Cache de pistes audio pour PMOMusic
|
|
|
|
|
//!
|
2025-10-17 23:08:06 +02:00
|
|
|
//! Cette crate fournit un système de cache pour les pistes audio avec conversion
|
|
|
|
|
//! automatique en FLAC et extraction des métadonnées.
|
2025-10-12 21:39:20 +02:00
|
|
|
//!
|
|
|
|
|
//! ## Vue d'ensemble
|
|
|
|
|
//!
|
|
|
|
|
//! `pmoaudiocache` étend `pmocache` pour gérer spécifiquement les fichiers audio :
|
2025-10-17 23:08:06 +02:00
|
|
|
//! - **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`
|
2025-10-12 21:39:20 +02:00
|
|
|
//!
|
|
|
|
|
//! ## Architecture
|
|
|
|
|
//!
|
2025-10-17 23:08:06 +02:00
|
|
|
//! 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`.
|
2025-10-12 21:39:20 +02:00
|
|
|
//!
|
|
|
|
|
//! ## Utilisation
|
|
|
|
|
//!
|
|
|
|
|
//! ### Exemple basique
|
|
|
|
|
//!
|
|
|
|
|
//! ```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-17 23:08:06 +02:00
|
|
|
//! // Créer le cache
|
|
|
|
|
//! let cache = cache::new_cache("./audio_cache", 1000, "http://localhost:8080")?;
|
2025-10-13 11:35:07 +02:00
|
|
|
//!
|
2025-10-17 23:08:06 +02:00
|
|
|
//! // Ajouter une piste avec extraction des métadonnées
|
|
|
|
|
//! let pk = cache::add_with_metadata_extraction(
|
|
|
|
|
//! &cache,
|
2025-10-13 11:35:07 +02:00
|
|
|
//! "http://example.com/track.flac",
|
2025-10-17 23:08:06 +02:00
|
|
|
//! None // collection auto-détectée depuis métadonnées
|
2025-10-13 11:35:07 +02:00
|
|
|
//! ).await?;
|
|
|
|
|
//!
|
2025-10-17 23:08:06 +02:00
|
|
|
//! // Lire les métadonnées
|
|
|
|
|
//! let metadata = cache::get_metadata(&cache, &pk)?;
|
|
|
|
|
//! println!("{} - {}",
|
|
|
|
|
//! metadata.artist.as_deref().unwrap_or("Unknown"),
|
|
|
|
|
//! metadata.title.as_deref().unwrap_or("Unknown")
|
|
|
|
|
//! );
|
2025-10-12 21:39:20 +02:00
|
|
|
//!
|
2025-10-17 23:08:06 +02:00
|
|
|
//! // Le fichier FLAC est disponible immédiatement après le download
|
|
|
|
|
//! let file_path = cache.get(&pk).await?;
|
|
|
|
|
//! println!("FLAC file: {:?}", file_path);
|
2025-10-12 21:39:20 +02:00
|
|
|
//!
|
|
|
|
|
//! Ok(())
|
|
|
|
|
//! }
|
|
|
|
|
//! ```
|
|
|
|
|
//!
|
2025-10-17 23:08:06 +02:00
|
|
|
//! ### Utilisation avec pmoserver
|
2025-10-12 21:39:20 +02:00
|
|
|
//!
|
|
|
|
|
//! ```rust,no_run
|
|
|
|
|
//! use pmoaudiocache::AudioCacheExt;
|
|
|
|
|
//! use pmoserver::ServerBuilder;
|
|
|
|
|
//!
|
|
|
|
|
//! #[tokio::main]
|
|
|
|
|
//! async fn main() -> anyhow::Result<()> {
|
|
|
|
|
//! let mut server = ServerBuilder::new_configured().build();
|
|
|
|
|
//!
|
2025-10-17 23:08:06 +02:00
|
|
|
//! // Initialiser le cache audio avec configuration automatique
|
|
|
|
|
//! server.init_audio_cache_configured().await?;
|
2025-10-12 21:39:20 +02:00
|
|
|
//!
|
|
|
|
|
//! server.start().await;
|
|
|
|
|
//! server.wait().await;
|
|
|
|
|
//! Ok(())
|
|
|
|
|
//! }
|
|
|
|
|
//! ```
|
|
|
|
|
//!
|
2025-10-17 23:08:06 +02:00
|
|
|
//! ## API HTTP (avec feature "pmoserver")
|
|
|
|
|
//!
|
|
|
|
|
//! Lorsque la feature `pmoserver` est activée, les routes suivantes sont disponibles :
|
2025-10-12 21:39:20 +02:00
|
|
|
//!
|
2025-10-17 23:08:06 +02:00
|
|
|
//! ### Routes de fichiers
|
|
|
|
|
//! - `GET /audio/tracks/{pk}` - Stream du fichier FLAC original
|
|
|
|
|
//! - `GET /audio/tracks/{pk}/orig` - Alias pour l'original
|
2025-10-13 11:35:07 +02:00
|
|
|
//!
|
2025-10-17 23:08:06 +02:00
|
|
|
//! ### API REST
|
2025-10-13 11:35:07 +02:00
|
|
|
//! - `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
|
2025-10-17 23:08:06 +02:00
|
|
|
//! - `GET /api/audio/{pk}/status` - Statut du téléchargement
|
|
|
|
|
//! - `POST /api/audio/consolidate` - Consolide le cache
|
2025-10-13 11:35:07 +02:00
|
|
|
//! - `DELETE /api/audio` - Purge tout le cache
|
2025-10-12 21:39:20 +02:00
|
|
|
//!
|
|
|
|
|
//! ## 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"`
|
|
|
|
|
//!
|
2025-10-17 23:08:06 +02:00
|
|
|
//! ## 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`
|
|
|
|
|
//!
|
2025-10-12 21:39:20 +02:00
|
|
|
//! ## Dépendances principales
|
|
|
|
|
//!
|
2025-10-17 23:08:06 +02:00
|
|
|
//! - `pmocache` : Cache générique avec download asynchrone
|
2025-10-12 21:39:20 +02:00
|
|
|
//! - `lofty` : Extraction de métadonnées audio
|
|
|
|
|
//! - `tokio` : Runtime asynchrone
|
|
|
|
|
//!
|
|
|
|
|
//! ## Voir aussi
|
|
|
|
|
//!
|
|
|
|
|
//! - [`pmocache`] : Cache générique
|
2025-10-17 23:08:06 +02:00
|
|
|
//! - [`pmocovers`] : Cache d'images (architecture similaire)
|
2025-10-12 21:39:20 +02:00
|
|
|
//! - [`pmoserver`] : Serveur HTTP
|
|
|
|
|
|
2025-10-17 23:08:06 +02:00
|
|
|
pub mod cache;
|
2025-10-12 21:39:20 +02:00
|
|
|
pub mod metadata;
|
|
|
|
|
pub mod flac;
|
|
|
|
|
|
|
|
|
|
#[cfg(feature = "pmoserver")]
|
2025-10-17 23:08:06 +02:00
|
|
|
pub mod openapi;
|
|
|
|
|
|
|
|
|
|
// Re-exports principaux
|
|
|
|
|
pub use cache::{Cache, AudioConfig, new_cache, add_with_metadata_extraction, get_metadata};
|
|
|
|
|
pub use metadata::AudioMetadata;
|
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-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
|
|
|
|
|
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, create_api_router};
|
|
|
|
|
#[cfg(feature = "pmoserver")]
|
|
|
|
|
use std::sync::Arc;
|
|
|
|
|
#[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 base_url = self.info().base_url;
|
|
|
|
|
let cache = Arc::new(crate::cache::new_cache(cache_dir, limit, &base_url)?);
|
|
|
|
|
|
|
|
|
|
// 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 générique (pmocache)
|
|
|
|
|
// Routes: GET/POST/DELETE /api/audio, etc.
|
|
|
|
|
let api_router = create_api_router(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>> {
|
|
|
|
|
let config = pmoconfig::get_config();
|
|
|
|
|
let cache_dir = config.get_audio_cache_dir()?;
|
|
|
|
|
let limit = config.get_audio_cache_size()?;
|
|
|
|
|
self.init_audio_cache(&cache_dir, limit).await
|
|
|
|
|
}
|
|
|
|
|
}
|