use super::types::DecodedAudio;
use crate::error::{BinaryError, Result};
use std::path::Path;
pub struct AudioExporter;
impl AudioExporter {
pub fn export_wav<P: AsRef<Path>>(audio: &DecodedAudio, path: P) -> Result<()> {
use std::fs::File;
use std::io::{BufWriter, Write};
let file = File::create(path)
.map_err(|e| BinaryError::generic(format!("Failed to create WAV file: {}", e)))?;
let mut writer = BufWriter::new(file);
let i16_samples = audio.to_i16_samples();
let byte_rate = audio.sample_rate * audio.channels * 2; let block_align = audio.channels * 2;
let data_size = i16_samples.len() * 2;
let file_size = 36 + data_size;
writer
.write_all(b"RIFF")
.map_err(|e| BinaryError::generic(format!("Write error: {}", e)))?;
writer
.write_all(&(file_size as u32).to_le_bytes())
.map_err(|e| BinaryError::generic(format!("Write error: {}", e)))?;
writer
.write_all(b"WAVE")
.map_err(|e| BinaryError::generic(format!("Write error: {}", e)))?;
writer
.write_all(b"fmt ")
.map_err(|e| BinaryError::generic(format!("Write error: {}", e)))?;
writer
.write_all(&16u32.to_le_bytes())
.map_err(|e| BinaryError::generic(format!("Write error: {}", e)))?; writer
.write_all(&1u16.to_le_bytes())
.map_err(|e| BinaryError::generic(format!("Write error: {}", e)))?; writer
.write_all(&(audio.channels as u16).to_le_bytes())
.map_err(|e| BinaryError::generic(format!("Write error: {}", e)))?;
writer
.write_all(&audio.sample_rate.to_le_bytes())
.map_err(|e| BinaryError::generic(format!("Write error: {}", e)))?;
writer
.write_all(&byte_rate.to_le_bytes())
.map_err(|e| BinaryError::generic(format!("Write error: {}", e)))?;
writer
.write_all(&(block_align as u16).to_le_bytes())
.map_err(|e| BinaryError::generic(format!("Write error: {}", e)))?;
writer
.write_all(&16u16.to_le_bytes())
.map_err(|e| BinaryError::generic(format!("Write error: {}", e)))?;
writer
.write_all(b"data")
.map_err(|e| BinaryError::generic(format!("Write error: {}", e)))?;
writer
.write_all(&(data_size as u32).to_le_bytes())
.map_err(|e| BinaryError::generic(format!("Write error: {}", e)))?;
for sample in i16_samples {
writer
.write_all(&sample.to_le_bytes())
.map_err(|e| BinaryError::generic(format!("Write error: {}", e)))?;
}
writer
.flush()
.map_err(|e| BinaryError::generic(format!("Flush error: {}", e)))?;
Ok(())
}
pub fn export_raw_pcm<P: AsRef<Path>>(
audio: &DecodedAudio,
path: P,
bit_depth: u8,
) -> Result<()> {
use std::fs::File;
use std::io::{BufWriter, Write};
let file = File::create(path)
.map_err(|e| BinaryError::generic(format!("Failed to create PCM file: {}", e)))?;
let mut writer = BufWriter::new(file);
match bit_depth {
16 => {
let i16_samples = audio.to_i16_samples();
for sample in i16_samples {
writer
.write_all(&sample.to_le_bytes())
.map_err(|e| BinaryError::generic(format!("Write error: {}", e)))?;
}
}
32 => {
let i32_samples = audio.to_i32_samples();
for sample in i32_samples {
writer
.write_all(&sample.to_le_bytes())
.map_err(|e| BinaryError::generic(format!("Write error: {}", e)))?;
}
}
_ => {
return Err(BinaryError::invalid_data(
"Unsupported bit depth for PCM export",
));
}
}
writer
.flush()
.map_err(|e| BinaryError::generic(format!("Flush error: {}", e)))?;
Ok(())
}
pub fn export_auto<P: AsRef<Path>>(audio: &DecodedAudio, path: P) -> Result<()> {
let path_ref = path.as_ref();
let extension = path_ref
.extension()
.and_then(|ext| ext.to_str())
.unwrap_or("")
.to_lowercase();
match extension.as_str() {
"wav" => Self::export_wav(audio, path),
"pcm" | "raw" => Self::export_raw_pcm(audio, path, 16),
_ => {
Self::export_wav(audio, path)
}
}
}
pub fn supported_formats() -> Vec<&'static str> {
vec!["wav", "pcm", "raw"]
}
pub fn is_format_supported(extension: &str) -> bool {
Self::supported_formats().contains(&extension.to_lowercase().as_str())
}
pub fn create_filename(base_name: &str, format: &str) -> String {
let clean_base =
base_name.trim_end_matches(|c: char| !c.is_alphanumeric() && c != '_' && c != '-');
format!("{}.{}", clean_base, format.to_lowercase())
}
pub fn validate_for_export(audio: &DecodedAudio) -> Result<()> {
if audio.samples.is_empty() {
return Err(BinaryError::invalid_data("Audio has no samples"));
}
if audio.sample_rate == 0 {
return Err(BinaryError::invalid_data("Invalid sample rate"));
}
if audio.channels == 0 {
return Err(BinaryError::invalid_data("Invalid channel count"));
}
if audio.sample_rate > 192000 {
return Err(BinaryError::invalid_data("Sample rate too high"));
}
if audio.channels > 32 {
return Err(BinaryError::invalid_data("Too many channels"));
}
Ok(())
}
pub fn export_validated<P: AsRef<Path>>(audio: &DecodedAudio, path: P) -> Result<()> {
Self::validate_for_export(audio)?;
Self::export_auto(audio, path)
}
}
#[derive(Debug, Clone)]
pub struct ExportOptions {
pub format: AudioFormat,
pub bit_depth: u8,
pub sample_rate: Option<u32>, }
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AudioFormat {
Wav,
RawPcm,
}
impl Default for ExportOptions {
fn default() -> Self {
Self {
format: AudioFormat::Wav,
bit_depth: 16,
sample_rate: None,
}
}
}
impl ExportOptions {
pub fn wav() -> Self {
Self {
format: AudioFormat::Wav,
bit_depth: 16,
sample_rate: None,
}
}
pub fn raw_pcm(bit_depth: u8) -> Self {
Self {
format: AudioFormat::RawPcm,
bit_depth,
sample_rate: None,
}
}
pub fn with_sample_rate(mut self, sample_rate: u32) -> Self {
self.sample_rate = Some(sample_rate);
self
}
pub fn export<P: AsRef<Path>>(&self, audio: &DecodedAudio, path: P) -> Result<()> {
let audio_to_export = audio;
match self.format {
AudioFormat::Wav => AudioExporter::export_wav(audio_to_export, path),
AudioFormat::RawPcm => {
AudioExporter::export_raw_pcm(audio_to_export, path, self.bit_depth)
}
}
}
}