amélioration de la webapp
This commit is contained in:
@@ -30,12 +30,20 @@ async fn main() -> anyhow::Result<()> {
|
||||
let header = &data[0..4];
|
||||
if header == b"fLaC" {
|
||||
println!("✓ File is FLAC!");
|
||||
} else if header[0..3] == *b"ID3" || (header.len() >= 2 && header[0] == 0xFF && (header[1] & 0xE0) == 0xE0) {
|
||||
} else if header[0..3] == *b"ID3"
|
||||
|| (header.len() >= 2 && header[0] == 0xFF && (header[1] & 0xE0) == 0xE0)
|
||||
{
|
||||
println!("✗ File is still MP3!");
|
||||
println!(" Header: {:02X} {:02X} {:02X} {:02X}", header[0], header[1], header[2], header[3]);
|
||||
println!(
|
||||
" Header: {:02X} {:02X} {:02X} {:02X}",
|
||||
header[0], header[1], header[2], header[3]
|
||||
);
|
||||
} else {
|
||||
println!("? Unknown format");
|
||||
println!(" Header: {:02X} {:02X} {:02X} {:02X}", header[0], header[1], header[2], header[3]);
|
||||
println!(
|
||||
" Header: {:02X} {:02X} {:02X} {:02X}",
|
||||
header[0], header[1], header[2], header[3]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -70,7 +70,10 @@ fn create_flac_transformer() -> StreamTransformer {
|
||||
buffer.extend_from_slice(&chunk);
|
||||
}
|
||||
|
||||
tracing::debug!("Downloaded {} bytes total, starting FLAC conversion", buffer.len());
|
||||
tracing::debug!(
|
||||
"Downloaded {} bytes total, starting FLAC conversion",
|
||||
buffer.len()
|
||||
);
|
||||
|
||||
// 2. Si c'est déjà du FLAC, on l'écrit directement
|
||||
if buffer.len() >= 4 && &buffer[0..4] == b"fLaC" {
|
||||
@@ -85,21 +88,26 @@ fn create_flac_transformer() -> StreamTransformer {
|
||||
|
||||
// 3. Décoder l'audio avec Symphonia
|
||||
let (samples, channels, sample_rate, bits_per_sample) = {
|
||||
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;
|
||||
use symphonia::core::errors::Error as SymphoniaError;
|
||||
use std::io::Cursor;
|
||||
|
||||
let cursor = Cursor::new(buffer);
|
||||
let mss = MediaSourceStream::new(Box::new(cursor), Default::default());
|
||||
|
||||
let hint = Hint::new();
|
||||
let probed = symphonia::default::get_probe()
|
||||
.format(&hint, mss, &FormatOptions::default(), &MetadataOptions::default())
|
||||
.format(
|
||||
&hint,
|
||||
mss,
|
||||
&FormatOptions::default(),
|
||||
&MetadataOptions::default(),
|
||||
)
|
||||
.map_err(|e| format!("Failed to probe format: {}", e))?;
|
||||
|
||||
let mut format = probed.format;
|
||||
@@ -114,15 +122,18 @@ fn create_flac_transformer() -> StreamTransformer {
|
||||
.make(&track.codec_params, &DecoderOptions::default())
|
||||
.map_err(|e| format!("Failed to create decoder: {}", e))?;
|
||||
|
||||
let channels = track.codec_params.channels
|
||||
let channels = track
|
||||
.codec_params
|
||||
.channels
|
||||
.ok_or_else(|| "No channel info".to_string())?
|
||||
.count();
|
||||
|
||||
let sample_rate = track.codec_params.sample_rate
|
||||
let sample_rate = track
|
||||
.codec_params
|
||||
.sample_rate
|
||||
.ok_or_else(|| "No sample rate info".to_string())?;
|
||||
|
||||
let bits_per_sample = track.codec_params.bits_per_sample
|
||||
.unwrap_or(16);
|
||||
let bits_per_sample = track.codec_params.bits_per_sample.unwrap_or(16);
|
||||
|
||||
let mut samples_i32 = Vec::new();
|
||||
let track_id = track.id;
|
||||
@@ -135,7 +146,9 @@ fn create_flac_transformer() -> StreamTransformer {
|
||||
decoder.reset();
|
||||
continue;
|
||||
}
|
||||
Err(SymphoniaError::IoError(e)) if e.kind() == std::io::ErrorKind::UnexpectedEof => {
|
||||
Err(SymphoniaError::IoError(e))
|
||||
if e.kind() == std::io::ErrorKind::UnexpectedEof =>
|
||||
{
|
||||
break;
|
||||
}
|
||||
Err(e) => return Err(format!("Decode error: {}", e)),
|
||||
@@ -183,13 +196,13 @@ fn create_flac_transformer() -> StreamTransformer {
|
||||
tracing::debug!("Normalizing to 16-bit");
|
||||
let samples = samples_i32.iter().map(|&s| (s >> 16) as i32).collect();
|
||||
(samples, 16)
|
||||
},
|
||||
}
|
||||
17..=24 => {
|
||||
// Pour 17-24 bits, normaliser vers la plage 24-bit
|
||||
tracing::debug!("Normalizing to 24-bit");
|
||||
let samples = samples_i32.iter().map(|&s| (s >> 8) as i32).collect();
|
||||
(samples, 24)
|
||||
},
|
||||
}
|
||||
_ => {
|
||||
// Pour 25-32 bits, garder la pleine échelle i32
|
||||
tracing::debug!("Keeping 32-bit");
|
||||
@@ -200,15 +213,20 @@ fn create_flac_transformer() -> StreamTransformer {
|
||||
(normalized_samples, channels, sample_rate, target_bits)
|
||||
};
|
||||
|
||||
tracing::debug!("Encoding to FLAC: {} samples, {} channels, {} Hz, {} bits",
|
||||
samples.len(), channels, sample_rate, bits_per_sample);
|
||||
tracing::debug!(
|
||||
"Encoding to FLAC: {} samples, {} channels, {} Hz, {} bits",
|
||||
samples.len(),
|
||||
channels,
|
||||
sample_rate,
|
||||
bits_per_sample
|
||||
);
|
||||
|
||||
// 4. Encoder en FLAC avec flacenc
|
||||
// Note: L'encodage FLAC est une opération bloquante/CPU-intensive,
|
||||
// donc nous l'exécutons dans un thread bloquant pour ne pas bloquer le runtime Tokio
|
||||
let flac_data = tokio::task::spawn_blocking(move || {
|
||||
use flacenc::component::BitRepr;
|
||||
use flacenc::bitsink::ByteSink;
|
||||
use flacenc::component::BitRepr;
|
||||
use flacenc::error::Verify;
|
||||
|
||||
let config = flacenc::config::Encoder::default()
|
||||
@@ -222,15 +240,13 @@ fn create_flac_transformer() -> StreamTransformer {
|
||||
sample_rate as usize,
|
||||
);
|
||||
|
||||
let flac_stream = flacenc::encode_with_fixed_block_size(
|
||||
&config,
|
||||
source,
|
||||
config.block_size,
|
||||
)
|
||||
.map_err(|e| format!("FLAC encode error: {:?}", e))?;
|
||||
let flac_stream =
|
||||
flacenc::encode_with_fixed_block_size(&config, source, config.block_size)
|
||||
.map_err(|e| format!("FLAC encode error: {:?}", e))?;
|
||||
|
||||
let mut sink = ByteSink::new();
|
||||
flac_stream.write(&mut sink)
|
||||
flac_stream
|
||||
.write(&mut sink)
|
||||
.map_err(|e| format!("FLAC write error: {:?}", e))?;
|
||||
|
||||
Ok::<Vec<u8>, String>(sink.into_inner())
|
||||
@@ -241,7 +257,9 @@ fn create_flac_transformer() -> StreamTransformer {
|
||||
tracing::debug!("FLAC encoding complete: {} bytes", flac_data.len());
|
||||
|
||||
// 5. Écrire le fichier FLAC
|
||||
file.write_all(&flac_data).await.map_err(|e| e.to_string())?;
|
||||
file.write_all(&flac_data)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
file.flush().await.map_err(|e| e.to_string())?;
|
||||
|
||||
// 6. Mettre à jour la progression finale
|
||||
@@ -328,13 +346,17 @@ pub async fn add_with_metadata_extraction(
|
||||
let metadata_json = serde_json::to_string(&metadata)?;
|
||||
|
||||
// Stocker dans la DB
|
||||
cache.db.update_metadata(&pk, &metadata_json)
|
||||
cache
|
||||
.db
|
||||
.update_metadata(&pk, &metadata_json)
|
||||
.map_err(|e| anyhow::anyhow!("Database error: {}", e))?;
|
||||
|
||||
// Mettre à jour la collection si les métadonnées en fournissent une
|
||||
if collection.is_none() {
|
||||
if let Some(auto_collection) = metadata.collection_key() {
|
||||
cache.db.add(&pk, url, Some(&auto_collection))
|
||||
cache
|
||||
.db
|
||||
.add(&pk, url, Some(&auto_collection))
|
||||
.map_err(|e| anyhow::anyhow!("Database error: {}", e))?;
|
||||
}
|
||||
}
|
||||
@@ -366,7 +388,9 @@ pub async fn add_with_metadata_extraction(
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn get_metadata(cache: &Cache, pk: &str) -> Result<crate::metadata::AudioMetadata> {
|
||||
let metadata_json = cache.db.get_metadata_json(pk)
|
||||
let metadata_json = cache
|
||||
.db
|
||||
.get_metadata_json(pk)
|
||||
.map_err(|e| anyhow::anyhow!("Database error: {}", e))?
|
||||
.ok_or_else(|| anyhow::anyhow!("No metadata found for pk: {}", pk))?;
|
||||
|
||||
@@ -375,4 +399,3 @@ pub fn get_metadata(cache: &Cache, pk: &str) -> Result<crate::metadata::AudioMet
|
||||
|
||||
Ok(metadata)
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
//! 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;
|
||||
@@ -11,7 +12,6 @@ use symphonia::core::formats::FormatOptions;
|
||||
use symphonia::core::io::MediaSourceStream;
|
||||
use symphonia::core::meta::MetadataOptions;
|
||||
use symphonia::core::probe::Hint;
|
||||
use std::io::Cursor;
|
||||
|
||||
/// Convertit des données audio en FLAC
|
||||
///
|
||||
@@ -54,7 +54,12 @@ pub fn convert_to_flac(data: &[u8], extension: Option<&str>) -> Result<Vec<u8>>
|
||||
|
||||
// Prober le format
|
||||
let probed = symphonia::default::get_probe()
|
||||
.format(&hint, mss, &FormatOptions::default(), &MetadataOptions::default())
|
||||
.format(
|
||||
&hint,
|
||||
mss,
|
||||
&FormatOptions::default(),
|
||||
&MetadataOptions::default(),
|
||||
)
|
||||
.map_err(|e| anyhow!("Impossible de détecter le format audio: {}", e))?;
|
||||
|
||||
let mut format = probed.format;
|
||||
|
||||
@@ -131,14 +131,14 @@
|
||||
//! - [`pmoserver`] : Serveur HTTP
|
||||
|
||||
pub mod cache;
|
||||
pub mod metadata;
|
||||
pub mod flac;
|
||||
pub mod metadata;
|
||||
|
||||
#[cfg(feature = "pmoserver")]
|
||||
pub mod openapi;
|
||||
|
||||
// Re-exports principaux
|
||||
pub use cache::{Cache, AudioConfig, new_cache, add_with_metadata_extraction, get_metadata};
|
||||
pub use cache::{add_with_metadata_extraction, get_metadata, new_cache, AudioConfig, Cache};
|
||||
pub use metadata::AudioMetadata;
|
||||
|
||||
#[cfg(feature = "pmoserver")]
|
||||
@@ -161,7 +161,11 @@ pub trait AudioCacheExt {
|
||||
/// # 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>>;
|
||||
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.
|
||||
///
|
||||
@@ -170,7 +174,7 @@ pub trait AudioCacheExt {
|
||||
}
|
||||
|
||||
#[cfg(feature = "pmoserver")]
|
||||
use pmocache::pmoserver_ext::{create_file_router, create_api_router};
|
||||
use pmocache::pmoserver_ext::{create_api_router, create_file_router};
|
||||
#[cfg(feature = "pmoserver")]
|
||||
use std::sync::Arc;
|
||||
#[cfg(feature = "pmoserver")]
|
||||
@@ -178,14 +182,18 @@ 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>> {
|
||||
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
|
||||
"audio/flac", // Content-Type
|
||||
);
|
||||
self.add_router("/", file_router).await;
|
||||
|
||||
|
||||
@@ -87,12 +87,12 @@ impl AudioMetadata {
|
||||
/// println!("Titre: {:?}", metadata.title);
|
||||
/// ```
|
||||
pub fn from_file(path: &Path) -> Result<Self> {
|
||||
let tagged_file = Probe::open(path)?
|
||||
.options(ParseOptions::new())
|
||||
.read()?;
|
||||
let tagged_file = Probe::open(path)?.options(ParseOptions::new()).read()?;
|
||||
|
||||
let properties = tagged_file.properties();
|
||||
let tag = tagged_file.primary_tag().or_else(|| tagged_file.first_tag());
|
||||
let tag = tagged_file
|
||||
.primary_tag()
|
||||
.or_else(|| tagged_file.first_tag());
|
||||
|
||||
let mut metadata = Self {
|
||||
title: None,
|
||||
@@ -138,7 +138,9 @@ impl AudioMetadata {
|
||||
.read()?;
|
||||
|
||||
let properties = tagged_file.properties();
|
||||
let tag = tagged_file.primary_tag().or_else(|| tagged_file.first_tag());
|
||||
let tag = tagged_file
|
||||
.primary_tag()
|
||||
.or_else(|| tagged_file.first_tag());
|
||||
|
||||
let mut metadata = Self {
|
||||
title: None,
|
||||
|
||||
Reference in New Issue
Block a user