unity_asset_decode/audio/
export.rs

1//! Audio export utilities
2//!
3//! This module provides functionality for exporting audio to various formats.
4
5use super::types::DecodedAudio;
6use crate::error::{BinaryError, Result};
7use std::path::Path;
8
9/// Audio exporter utility
10///
11/// This struct provides methods for exporting decoded audio data to various formats.
12pub struct AudioExporter;
13
14impl AudioExporter {
15    /// Export audio as WAV file
16    ///
17    /// This is the most common export format, providing uncompressed audio
18    /// with full quality preservation.
19    pub fn export_wav<P: AsRef<Path>>(audio: &DecodedAudio, path: P) -> Result<()> {
20        use std::fs::File;
21        use std::io::{BufWriter, Write};
22
23        let file = File::create(path)
24            .map_err(|e| BinaryError::generic(format!("Failed to create WAV file: {}", e)))?;
25        let mut writer = BufWriter::new(file);
26
27        // Convert f32 samples to i16
28        let i16_samples = audio.to_i16_samples();
29        let byte_rate = audio.sample_rate * audio.channels * 2; // 16-bit samples
30        let block_align = audio.channels * 2;
31        let data_size = i16_samples.len() * 2;
32        let file_size = 36 + data_size;
33
34        // Write WAV header
35        writer
36            .write_all(b"RIFF")
37            .map_err(|e| BinaryError::generic(format!("Write error: {}", e)))?;
38        writer
39            .write_all(&(file_size as u32).to_le_bytes())
40            .map_err(|e| BinaryError::generic(format!("Write error: {}", e)))?;
41        writer
42            .write_all(b"WAVE")
43            .map_err(|e| BinaryError::generic(format!("Write error: {}", e)))?;
44
45        // Write format chunk
46        writer
47            .write_all(b"fmt ")
48            .map_err(|e| BinaryError::generic(format!("Write error: {}", e)))?;
49        writer
50            .write_all(&16u32.to_le_bytes())
51            .map_err(|e| BinaryError::generic(format!("Write error: {}", e)))?; // Chunk size
52        writer
53            .write_all(&1u16.to_le_bytes())
54            .map_err(|e| BinaryError::generic(format!("Write error: {}", e)))?; // Audio format (PCM)
55        writer
56            .write_all(&(audio.channels as u16).to_le_bytes())
57            .map_err(|e| BinaryError::generic(format!("Write error: {}", e)))?;
58        writer
59            .write_all(&audio.sample_rate.to_le_bytes())
60            .map_err(|e| BinaryError::generic(format!("Write error: {}", e)))?;
61        writer
62            .write_all(&byte_rate.to_le_bytes())
63            .map_err(|e| BinaryError::generic(format!("Write error: {}", e)))?;
64        writer
65            .write_all(&(block_align as u16).to_le_bytes())
66            .map_err(|e| BinaryError::generic(format!("Write error: {}", e)))?;
67        writer
68            .write_all(&16u16.to_le_bytes())
69            .map_err(|e| BinaryError::generic(format!("Write error: {}", e)))?; // Bits per sample
70
71        // Write data chunk
72        writer
73            .write_all(b"data")
74            .map_err(|e| BinaryError::generic(format!("Write error: {}", e)))?;
75        writer
76            .write_all(&(data_size as u32).to_le_bytes())
77            .map_err(|e| BinaryError::generic(format!("Write error: {}", e)))?;
78
79        // Write sample data
80        for sample in i16_samples {
81            writer
82                .write_all(&sample.to_le_bytes())
83                .map_err(|e| BinaryError::generic(format!("Write error: {}", e)))?;
84        }
85
86        writer
87            .flush()
88            .map_err(|e| BinaryError::generic(format!("Flush error: {}", e)))?;
89        Ok(())
90    }
91
92    /// Export audio as raw PCM data
93    pub fn export_raw_pcm<P: AsRef<Path>>(
94        audio: &DecodedAudio,
95        path: P,
96        bit_depth: u8,
97    ) -> Result<()> {
98        use std::fs::File;
99        use std::io::{BufWriter, Write};
100
101        let file = File::create(path)
102            .map_err(|e| BinaryError::generic(format!("Failed to create PCM file: {}", e)))?;
103        let mut writer = BufWriter::new(file);
104
105        match bit_depth {
106            16 => {
107                let i16_samples = audio.to_i16_samples();
108                for sample in i16_samples {
109                    writer
110                        .write_all(&sample.to_le_bytes())
111                        .map_err(|e| BinaryError::generic(format!("Write error: {}", e)))?;
112                }
113            }
114            32 => {
115                let i32_samples = audio.to_i32_samples();
116                for sample in i32_samples {
117                    writer
118                        .write_all(&sample.to_le_bytes())
119                        .map_err(|e| BinaryError::generic(format!("Write error: {}", e)))?;
120                }
121            }
122            _ => {
123                return Err(BinaryError::invalid_data(
124                    "Unsupported bit depth for PCM export",
125                ));
126            }
127        }
128
129        writer
130            .flush()
131            .map_err(|e| BinaryError::generic(format!("Flush error: {}", e)))?;
132        Ok(())
133    }
134
135    /// Export audio with automatic format detection based on file extension
136    pub fn export_auto<P: AsRef<Path>>(audio: &DecodedAudio, path: P) -> Result<()> {
137        let path_ref = path.as_ref();
138        let extension = path_ref
139            .extension()
140            .and_then(|ext| ext.to_str())
141            .unwrap_or("")
142            .to_lowercase();
143
144        match extension.as_str() {
145            "wav" => Self::export_wav(audio, path),
146            "pcm" | "raw" => Self::export_raw_pcm(audio, path, 16),
147            _ => {
148                // Default to WAV for unknown extensions
149                Self::export_wav(audio, path)
150            }
151        }
152    }
153
154    /// Get supported export formats
155    pub fn supported_formats() -> Vec<&'static str> {
156        vec!["wav", "pcm", "raw"]
157    }
158
159    /// Check if a format is supported for export
160    pub fn is_format_supported(extension: &str) -> bool {
161        Self::supported_formats().contains(&extension.to_lowercase().as_str())
162    }
163
164    /// Create a filename with the given base name and format extension
165    pub fn create_filename(base_name: &str, format: &str) -> String {
166        let clean_base =
167            base_name.trim_end_matches(|c: char| !c.is_alphanumeric() && c != '_' && c != '-');
168        format!("{}.{}", clean_base, format.to_lowercase())
169    }
170
171    /// Validate that the audio has valid properties for export
172    pub fn validate_for_export(audio: &DecodedAudio) -> Result<()> {
173        if audio.samples.is_empty() {
174            return Err(BinaryError::invalid_data("Audio has no samples"));
175        }
176
177        if audio.sample_rate == 0 {
178            return Err(BinaryError::invalid_data("Invalid sample rate"));
179        }
180
181        if audio.channels == 0 {
182            return Err(BinaryError::invalid_data("Invalid channel count"));
183        }
184
185        // Check for reasonable limits
186        if audio.sample_rate > 192000 {
187            return Err(BinaryError::invalid_data("Sample rate too high"));
188        }
189
190        if audio.channels > 32 {
191            return Err(BinaryError::invalid_data("Too many channels"));
192        }
193
194        Ok(())
195    }
196
197    /// Export with validation
198    pub fn export_validated<P: AsRef<Path>>(audio: &DecodedAudio, path: P) -> Result<()> {
199        Self::validate_for_export(audio)?;
200        Self::export_auto(audio, path)
201    }
202}
203
204/// Export options for advanced export scenarios
205#[derive(Debug, Clone)]
206pub struct ExportOptions {
207    pub format: AudioFormat,
208    pub bit_depth: u8,
209    pub sample_rate: Option<u32>, // For resampling
210}
211
212/// Supported audio export formats
213#[derive(Debug, Clone, Copy, PartialEq, Eq)]
214pub enum AudioFormat {
215    Wav,
216    RawPcm,
217}
218
219impl Default for ExportOptions {
220    fn default() -> Self {
221        Self {
222            format: AudioFormat::Wav,
223            bit_depth: 16,
224            sample_rate: None,
225        }
226    }
227}
228
229impl ExportOptions {
230    /// Create WAV export options
231    pub fn wav() -> Self {
232        Self {
233            format: AudioFormat::Wav,
234            bit_depth: 16,
235            sample_rate: None,
236        }
237    }
238
239    /// Create raw PCM export options with bit depth
240    pub fn raw_pcm(bit_depth: u8) -> Self {
241        Self {
242            format: AudioFormat::RawPcm,
243            bit_depth,
244            sample_rate: None,
245        }
246    }
247
248    /// Set sample rate for resampling
249    pub fn with_sample_rate(mut self, sample_rate: u32) -> Self {
250        self.sample_rate = Some(sample_rate);
251        self
252    }
253
254    /// Export with these options
255    pub fn export<P: AsRef<Path>>(&self, audio: &DecodedAudio, path: P) -> Result<()> {
256        // TODO: Implement resampling if sample_rate is specified
257        let audio_to_export = audio; // For now, use original audio
258
259        match self.format {
260            AudioFormat::Wav => AudioExporter::export_wav(audio_to_export, path),
261            AudioFormat::RawPcm => {
262                AudioExporter::export_raw_pcm(audio_to_export, path, self.bit_depth)
263            }
264        }
265    }
266}