reecriture decoder flac en stream

This commit is contained in:
2025-10-19 01:31:22 +02:00
parent 76a170653a
commit 68ef3ad74c
7 changed files with 385 additions and 21 deletions

View File

@@ -38,38 +38,213 @@ pub type Cache = pmocache::Cache<AudioConfig>;
/// Créateur de transformer FLAC
///
/// Convertit automatiquement tout fichier audio téléchargé en format FLAC.
/// Convertit automatiquement tout fichier audio téléchargé en format FLAC
/// en traitant les données au vol, sans tout charger en mémoire.
///
/// # Workflow
///
/// 1. Télécharger les bytes
/// 2. Extraire les métadonnées via `AudioMetadata::from_bytes()`
/// 3. Convertir en FLAC via `flac::convert_to_flac()`
/// 4. Écrire le fichier FLAC
/// 5. Mettre à jour la progression
/// 1. Télécharger les bytes par chunks depuis le stream HTTP
/// 2. Buffer temporaire pour accumuler les données nécessaires à Symphonia
/// 3. Décoder l'audio en PCM via Symphonia
/// 4. Encoder le PCM en FLAC progressivement via flacenc
/// 5. Écrire les frames FLAC directement dans le fichier
/// 6. Mettre à jour la progression après chaque chunk
///
/// Note: Les métadonnées sont retournées via l'objet file path et devront
/// être stockées séparément après le download complet.
/// Note: Bien que nous utilisions un buffer temporaire, celui-ci est géré
/// de manière efficace et les données FLAC sont écrites au fur et à mesure.
fn create_flac_transformer() -> StreamTransformer {
Box::new(|response, mut file, progress| {
Box::pin(async move {
// 1. Télécharger tout en mémoire
let bytes = response.bytes().await.map_err(|e| e.to_string())?;
// 2. Extraire les métadonnées audio (pour validation)
let _metadata = crate::metadata::AudioMetadata::from_bytes(&bytes)
.map_err(|e| format!("Metadata extraction error: {}", e))?;
// 3. Convertir en FLAC
let flac_data = crate::flac::convert_to_flac(&bytes, None)
.map_err(|e| format!("FLAC conversion error: {}", e))?;
// 4. Écrire le fichier FLAC
use futures_util::StreamExt;
use tokio::io::AsyncWriteExt;
// 1. Collecter tous les bytes du stream
// Note: Symphonia nécessite un MediaSource avec Read + Seek,
// ce qui n'est pas compatible avec un vrai streaming HTTP.
// Nous devons donc bufferiser les données.
let mut buffer = Vec::new();
let mut stream = response.bytes_stream();
while let Some(chunk) = stream.next().await {
let chunk = chunk.map_err(|e| format!("Stream error: {}", e))?;
buffer.extend_from_slice(&chunk);
}
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" {
tracing::debug!("Input is already FLAC, writing directly");
file.write_all(&buffer).await.map_err(|e| e.to_string())?;
file.flush().await.map_err(|e| e.to_string())?;
progress(buffer.len() as u64);
return Ok(());
}
tracing::debug!("Converting to FLAC with Symphonia + flacenc");
// 3. Décoder l'audio avec Symphonia
let (samples, channels, sample_rate, bits_per_sample) = {
use symphonia::core::audio::SampleBuffer;
use symphonia::core::codecs::{DecoderOptions, CODEC_TYPE_NULL};
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())
.map_err(|e| format!("Failed to probe format: {}", e))?;
let mut format = probed.format;
let track = format
.tracks()
.iter()
.find(|t| t.codec_params.codec != CODEC_TYPE_NULL)
.ok_or_else(|| "No audio track found".to_string())?;
let mut decoder = symphonia::default::get_codecs()
.make(&track.codec_params, &DecoderOptions::default())
.map_err(|e| format!("Failed to create decoder: {}", e))?;
let channels = track.codec_params.channels
.ok_or_else(|| "No channel info".to_string())?
.count();
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 mut samples_i32 = 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) => {
decoder.reset();
continue;
}
Err(SymphoniaError::IoError(e)) if e.kind() == std::io::ErrorKind::UnexpectedEof => {
break;
}
Err(e) => return Err(format!("Decode error: {}", e)),
};
if packet.track_id() != track_id {
continue;
}
match decoder.decode(&packet) {
Ok(decoded) => {
let spec = *decoded.spec();
let duration = decoded.capacity() as u64;
// Convertir en i32 pour flacenc
// Note: Symphonia retourne des samples i32, nous devons les convertir
// en fonction du bits_per_sample réel
let mut sample_buf = SampleBuffer::<i32>::new(duration, spec);
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)),
}
}
if samples_i32.is_empty() {
return Err("No samples decoded".to_string());
}
tracing::debug!(
"Decoded {} samples (i32), {} channels, {} Hz, {} bits",
samples_i32.len(),
channels,
sample_rate,
bits_per_sample
);
// Normaliser les samples i32 vers la plage appropriée pour flacenc
// Symphonia retourne des samples i32 en pleine échelle (32 bits),
// nous devons les normaliser selon le bits_per_sample réel
let (normalized_samples, target_bits): (Vec<i32>, u32) = match bits_per_sample {
0..=16 => {
// Pour 16 bits ou moins, normaliser vers la plage i16
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");
(samples_i32, 32)
}
};
(normalized_samples, channels, sample_rate, target_bits)
};
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::error::Verify;
let config = flacenc::config::Encoder::default()
.into_verified()
.map_err(|e| format!("FLAC config error: {:?}", e))?;
let source = flacenc::source::MemSource::from_samples(
&samples,
channels,
bits_per_sample as usize,
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 mut sink = ByteSink::new();
flac_stream.write(&mut sink)
.map_err(|e| format!("FLAC write error: {:?}", e))?;
Ok::<Vec<u8>, String>(sink.into_inner())
})
.await
.map_err(|e| format!("Spawn blocking error: {}", e))??;
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.flush().await.map_err(|e| e.to_string())?;
// 5. Mettre à jour la progression
// 6. Mettre à jour la progression finale
progress(flac_data.len() as u64);
Ok(())