Lire le flac en stream et le décoder en PCM avec claxon
This commit is contained in:
@@ -108,7 +108,15 @@ fn create_flac_transformer() -> StreamTransformer {
|
||||
&FormatOptions::default(),
|
||||
&MetadataOptions::default(),
|
||||
)
|
||||
.map_err(|e| format!("Failed to probe format: {}", e))?;
|
||||
.map_err(|e| {
|
||||
tracing::error!("Symphonia failed to detect audio format: {}", e);
|
||||
format!(
|
||||
"Unable to detect audio format. Error: {}. \
|
||||
Supported formats: MP3, WAV, OGG, FLAC, AAC, ALAC. \
|
||||
Please verify the URL points to a valid audio file.",
|
||||
e
|
||||
)
|
||||
})?;
|
||||
|
||||
let mut format = probed.format;
|
||||
|
||||
@@ -116,22 +124,40 @@ fn create_flac_transformer() -> StreamTransformer {
|
||||
.tracks()
|
||||
.iter()
|
||||
.find(|t| t.codec_params.codec != CODEC_TYPE_NULL)
|
||||
.ok_or_else(|| "No audio track found".to_string())?;
|
||||
.ok_or_else(|| {
|
||||
tracing::error!("No audio track found in the file");
|
||||
"No audio track found in the file. The file may be corrupted or not a valid audio file.".to_string()
|
||||
})?;
|
||||
|
||||
let codec_name = format!("{:?}", track.codec_params.codec);
|
||||
tracing::debug!("Detected codec: {}", codec_name);
|
||||
|
||||
let mut decoder = symphonia::default::get_codecs()
|
||||
.make(&track.codec_params, &DecoderOptions::default())
|
||||
.map_err(|e| format!("Failed to create decoder: {}", e))?;
|
||||
.map_err(|e| {
|
||||
tracing::error!("Failed to create decoder for codec {}: {}", codec_name, e);
|
||||
format!(
|
||||
"Codec '{}' is not supported or failed to initialize. Error: {}",
|
||||
codec_name, e
|
||||
)
|
||||
})?;
|
||||
|
||||
let channels = track
|
||||
.codec_params
|
||||
.channels
|
||||
.ok_or_else(|| "No channel info".to_string())?
|
||||
.ok_or_else(|| {
|
||||
tracing::error!("Audio file missing channel information");
|
||||
"Audio file is missing channel information. The file may be corrupted.".to_string()
|
||||
})?
|
||||
.count();
|
||||
|
||||
let sample_rate = track
|
||||
.codec_params
|
||||
.sample_rate
|
||||
.ok_or_else(|| "No sample rate info".to_string())?;
|
||||
.ok_or_else(|| {
|
||||
tracing::error!("Audio file missing sample rate information");
|
||||
"Audio file is missing sample rate information. The file may be corrupted.".to_string()
|
||||
})?;
|
||||
|
||||
let bits_per_sample = track.codec_params.bits_per_sample.unwrap_or(16);
|
||||
|
||||
@@ -151,7 +177,10 @@ fn create_flac_transformer() -> StreamTransformer {
|
||||
{
|
||||
break;
|
||||
}
|
||||
Err(e) => return Err(format!("Decode error: {}", e)),
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to read audio packet: {}", e);
|
||||
return Err(format!("Failed to read audio data: {}. The file may be corrupted.", e));
|
||||
}
|
||||
};
|
||||
|
||||
if packet.track_id() != track_id {
|
||||
@@ -170,13 +199,20 @@ fn create_flac_transformer() -> StreamTransformer {
|
||||
sample_buf.copy_interleaved_ref(decoded);
|
||||
samples_i32.extend_from_slice(sample_buf.samples());
|
||||
}
|
||||
Err(SymphoniaError::DecodeError(_)) => continue,
|
||||
Err(e) => return Err(format!("Decode error: {}", e)),
|
||||
Err(SymphoniaError::DecodeError(e)) => {
|
||||
tracing::warn!("Skipping corrupted audio packet: {}", e);
|
||||
continue;
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("Fatal decode error: {}", e);
|
||||
return Err(format!("Failed to decode audio: {}. The file may be corrupted or use an unsupported codec variant.", e));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if samples_i32.is_empty() {
|
||||
return Err("No samples decoded".to_string());
|
||||
tracing::error!("No audio samples could be decoded from the file");
|
||||
return Err("No audio samples could be decoded. The file may be corrupted or empty.".to_string());
|
||||
}
|
||||
|
||||
tracing::debug!(
|
||||
@@ -231,7 +267,10 @@ fn create_flac_transformer() -> StreamTransformer {
|
||||
|
||||
let config = flacenc::config::Encoder::default()
|
||||
.into_verified()
|
||||
.map_err(|e| format!("FLAC config error: {:?}", e))?;
|
||||
.map_err(|e| {
|
||||
tracing::error!("Failed to create FLAC encoder config: {:?}", e);
|
||||
format!("Internal error: FLAC encoder configuration failed: {:?}", e)
|
||||
})?;
|
||||
|
||||
let source = flacenc::source::MemSource::from_samples(
|
||||
&samples,
|
||||
@@ -242,17 +281,26 @@ fn create_flac_transformer() -> StreamTransformer {
|
||||
|
||||
let flac_stream =
|
||||
flacenc::encode_with_fixed_block_size(&config, source, config.block_size)
|
||||
.map_err(|e| format!("FLAC encode error: {:?}", e))?;
|
||||
.map_err(|e| {
|
||||
tracing::error!("FLAC encoding failed: {:?}", e);
|
||||
format!("Failed to encode audio to FLAC format: {:?}", e)
|
||||
})?;
|
||||
|
||||
let mut sink = ByteSink::new();
|
||||
flac_stream
|
||||
.write(&mut sink)
|
||||
.map_err(|e| format!("FLAC write error: {:?}", e))?;
|
||||
.map_err(|e| {
|
||||
tracing::error!("Failed to write FLAC stream: {:?}", e);
|
||||
format!("Failed to write FLAC data: {:?}", e)
|
||||
})?;
|
||||
|
||||
Ok::<Vec<u8>, String>(sink.into_inner())
|
||||
})
|
||||
.await
|
||||
.map_err(|e| format!("Spawn blocking error: {}", e))??;
|
||||
.map_err(|e| {
|
||||
tracing::error!("FLAC encoding task panicked: {}", e);
|
||||
format!("Internal error: FLAC encoding task failed: {}", e)
|
||||
})??;
|
||||
|
||||
tracing::debug!("FLAC encoding complete: {} bytes", flac_data.len());
|
||||
|
||||
|
||||
@@ -1,150 +0,0 @@
|
||||
//! Module de conversion audio en FLAC
|
||||
//!
|
||||
//! Ce module gère la conversion de divers formats audio vers FLAC
|
||||
//! 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;
|
||||
use symphonia::core::formats::FormatOptions;
|
||||
use symphonia::core::io::MediaSourceStream;
|
||||
use symphonia::core::meta::MetadataOptions;
|
||||
use symphonia::core::probe::Hint;
|
||||
|
||||
/// Convertit des données audio en FLAC
|
||||
///
|
||||
/// Cette fonction accepte n'importe quel format audio supporté par Symphonia
|
||||
/// et le convertit en FLAC pour un stockage standardisé.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `data` - Données audio brutes (n'importe quel format)
|
||||
/// * `extension` - Extension du fichier source (optionnel, aide à la détection)
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Données audio au format FLAC
|
||||
///
|
||||
/// # Exemple
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// use pmoaudiocache::flac::convert_to_flac;
|
||||
///
|
||||
/// let mp3_data = std::fs::read("track.mp3").unwrap();
|
||||
/// let flac_data = convert_to_flac(&mp3_data, Some("mp3")).unwrap();
|
||||
/// ```
|
||||
pub fn convert_to_flac(data: &[u8], extension: Option<&str>) -> Result<Vec<u8>> {
|
||||
// Si c'est déjà du FLAC, on le retourne tel quel
|
||||
if is_flac(data) {
|
||||
return Ok(data.to_vec());
|
||||
}
|
||||
|
||||
// Créer un MediaSource depuis les données (en clonant pour avoir 'static)
|
||||
let data_owned = data.to_vec();
|
||||
let cursor = Cursor::new(data_owned);
|
||||
let mss = MediaSourceStream::new(Box::new(cursor), Default::default());
|
||||
|
||||
// Créer un hint si on a l'extension
|
||||
let mut hint = Hint::new();
|
||||
if let Some(ext) = extension {
|
||||
hint.with_extension(ext);
|
||||
}
|
||||
|
||||
// Prober le format
|
||||
let probed = symphonia::default::get_probe()
|
||||
.format(
|
||||
&hint,
|
||||
mss,
|
||||
&FormatOptions::default(),
|
||||
&MetadataOptions::default(),
|
||||
)
|
||||
.map_err(|e| anyhow!("Impossible de détecter le format audio: {}", e))?;
|
||||
|
||||
let mut format = probed.format;
|
||||
|
||||
// Obtenir le premier track audio
|
||||
let track = format
|
||||
.tracks()
|
||||
.iter()
|
||||
.find(|t| t.codec_params.codec != CODEC_TYPE_NULL)
|
||||
.ok_or_else(|| anyhow!("Aucune piste audio trouvée"))?;
|
||||
|
||||
// Créer un décodeur
|
||||
let mut decoder = symphonia::default::get_codecs()
|
||||
.make(&track.codec_params, &DecoderOptions::default())
|
||||
.map_err(|e| anyhow!("Impossible de créer le décodeur: {}", e))?;
|
||||
|
||||
// Buffer pour stocker les samples décodés
|
||||
let mut samples = Vec::new();
|
||||
let track_id = track.id;
|
||||
|
||||
// Décoder tous les packets
|
||||
loop {
|
||||
let packet = match format.next_packet() {
|
||||
Ok(packet) => packet,
|
||||
Err(SymphoniaError::ResetRequired) => {
|
||||
// Reset du décodeur requis
|
||||
decoder.reset();
|
||||
continue;
|
||||
}
|
||||
Err(SymphoniaError::IoError(e)) if e.kind() == std::io::ErrorKind::UnexpectedEof => {
|
||||
break;
|
||||
}
|
||||
Err(e) => return Err(anyhow!("Erreur lors de la lecture: {}", e)),
|
||||
};
|
||||
|
||||
// Ignorer les packets qui ne sont pas de notre track
|
||||
if packet.track_id() != track_id {
|
||||
continue;
|
||||
}
|
||||
|
||||
match decoder.decode(&packet) {
|
||||
Ok(decoded) => {
|
||||
// Convertir les samples en format standard
|
||||
let spec = *decoded.spec();
|
||||
let duration = decoded.capacity() as u64;
|
||||
|
||||
let mut sample_buf = SampleBuffer::<i16>::new(duration, spec);
|
||||
sample_buf.copy_interleaved_ref(decoded);
|
||||
|
||||
samples.extend_from_slice(sample_buf.samples());
|
||||
}
|
||||
Err(SymphoniaError::DecodeError(_)) => continue,
|
||||
Err(e) => return Err(anyhow!("Erreur de décodage: {}", e)),
|
||||
}
|
||||
}
|
||||
|
||||
if samples.is_empty() {
|
||||
return Err(anyhow!("Aucun sample décodé"));
|
||||
}
|
||||
|
||||
// Note: Pour l'encodage FLAC, on aurait besoin d'une bibliothèque comme
|
||||
// `flacenc` qui n'existe pas encore en Rust. Pour l'instant, on stocke
|
||||
// les données telles quelles si c'est déjà du FLAC, sinon on retourne
|
||||
// les données originales avec un warning.
|
||||
|
||||
// TODO: Implémenter l'encodage FLAC quand une bibliothèque sera disponible
|
||||
tracing::warn!("Encodage FLAC non implémenté, stockage du format original");
|
||||
Ok(data.to_vec())
|
||||
}
|
||||
|
||||
/// Vérifie si les données sont déjà au format FLAC
|
||||
fn is_flac(data: &[u8]) -> bool {
|
||||
data.len() >= 4 && &data[0..4] == b"fLaC"
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_is_flac() {
|
||||
let flac_header = b"fLaC\x00\x00\x00\x22";
|
||||
assert!(is_flac(flac_header));
|
||||
|
||||
let not_flac = b"RIFF\x00\x00\x00\x00";
|
||||
assert!(!is_flac(not_flac));
|
||||
}
|
||||
}
|
||||
@@ -131,7 +131,6 @@
|
||||
//! - [`pmoserver`] : Serveur HTTP
|
||||
|
||||
pub mod cache;
|
||||
pub mod flac;
|
||||
pub mod metadata;
|
||||
|
||||
#[cfg(feature = "pmoserver")]
|
||||
|
||||
Reference in New Issue
Block a user