unity_asset_decode/audio/
export.rs1use super::types::DecodedAudio;
6use crate::error::{BinaryError, Result};
7use std::path::Path;
8
9pub struct AudioExporter;
13
14impl AudioExporter {
15 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 let i16_samples = audio.to_i16_samples();
29 let byte_rate = audio.sample_rate * audio.channels * 2; let block_align = audio.channels * 2;
31 let data_size = i16_samples.len() * 2;
32 let file_size = 36 + data_size;
33
34 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 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)))?; writer
53 .write_all(&1u16.to_le_bytes())
54 .map_err(|e| BinaryError::generic(format!("Write error: {}", e)))?; 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)))?; 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 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 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 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 Self::export_wav(audio, path)
150 }
151 }
152 }
153
154 pub fn supported_formats() -> Vec<&'static str> {
156 vec!["wav", "pcm", "raw"]
157 }
158
159 pub fn is_format_supported(extension: &str) -> bool {
161 Self::supported_formats().contains(&extension.to_lowercase().as_str())
162 }
163
164 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 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 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 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#[derive(Debug, Clone)]
206pub struct ExportOptions {
207 pub format: AudioFormat,
208 pub bit_depth: u8,
209 pub sample_rate: Option<u32>, }
211
212#[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 pub fn wav() -> Self {
232 Self {
233 format: AudioFormat::Wav,
234 bit_depth: 16,
235 sample_rate: None,
236 }
237 }
238
239 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 pub fn with_sample_rate(mut self, sample_rate: u32) -> Self {
250 self.sample_rate = Some(sample_rate);
251 self
252 }
253
254 pub fn export<P: AsRef<Path>>(&self, audio: &DecodedAudio, path: P) -> Result<()> {
256 let audio_to_export = audio; 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}