unity_asset_binary/audio/
converter.rs

1//! AudioClip converter and processor
2//!
3//! This module provides the main conversion logic for Unity AudioClip objects.
4//! Inspired by UnityPy/export/AudioClipConverter.py
5
6use super::formats::AudioCompressionFormat;
7use super::types::{AudioClip, AudioClipMeta, StreamingInfo};
8use crate::error::{BinaryError, Result};
9use crate::object::UnityObject;
10use crate::unity_version::UnityVersion;
11
12/// Main audio converter
13///
14/// This struct handles the conversion of Unity objects to AudioClip structures
15/// and provides methods for processing audio data.
16pub struct AudioClipConverter {
17    version: UnityVersion,
18}
19
20impl AudioClipConverter {
21    /// Create a new AudioClip converter
22    pub fn new(version: UnityVersion) -> Self {
23        Self { version }
24    }
25
26    /// Convert Unity object to AudioClip
27    ///
28    /// This method extracts audio data from a Unity object and creates
29    /// an AudioClip structure with all necessary metadata.
30    pub fn from_unity_object(&self, obj: &UnityObject) -> Result<AudioClip> {
31        // For now, use a simplified approach similar to the texture implementation
32        // TODO: Implement proper TypeTree parsing when available
33        self.parse_binary_data(&obj.info.data)
34    }
35
36    /// Parse AudioClip from raw binary data (simplified version)
37    #[allow(clippy::field_reassign_with_default)]
38    fn parse_binary_data(&self, data: &[u8]) -> Result<AudioClip> {
39        if data.is_empty() {
40            return Err(BinaryError::invalid_data("Empty audio data"));
41        }
42
43        let mut reader = crate::reader::BinaryReader::new(data, crate::reader::ByteOrder::Little);
44        let mut clip = AudioClip::default();
45
46        // Read name first
47        clip.name = reader
48            .read_aligned_string()
49            .unwrap_or_else(|_| "UnknownAudio".to_string());
50
51        // Read metadata based on Unity version
52        if self.version.major < 5 {
53            // Legacy format (Unity < 5.0)
54            let format = reader.read_i32().unwrap_or(0);
55            let type_ = reader.read_i32().unwrap_or(0);
56            let is_3d = reader.read_bool().unwrap_or(false);
57            let use_hardware = reader.read_bool().unwrap_or(false);
58
59            clip.meta = AudioClipMeta::Legacy {
60                format,
61                type_,
62                is_3d,
63                use_hardware,
64            };
65        } else {
66            // Modern format (Unity >= 5.0)
67            let load_type = reader.read_i32().unwrap_or(0);
68            let channels = reader.read_i32().unwrap_or(2);
69            let frequency = reader.read_i32().unwrap_or(44100);
70            let bits_per_sample = reader.read_i32().unwrap_or(16);
71            let length = reader.read_f32().unwrap_or(0.0);
72            let is_tracker_format = reader.read_bool().unwrap_or(false);
73            let subsound_index = reader.read_i32().unwrap_or(0);
74            let preload_audio_data = reader.read_bool().unwrap_or(true);
75            let load_in_background = reader.read_bool().unwrap_or(false);
76            let legacy_3d = reader.read_bool().unwrap_or(false);
77
78            let compression_format_val = reader.read_i32().unwrap_or(0);
79            let compression_format = AudioCompressionFormat::from(compression_format_val);
80
81            clip.meta = AudioClipMeta::Modern {
82                load_type,
83                channels,
84                frequency,
85                bits_per_sample,
86                length,
87                is_tracker_format,
88                subsound_index,
89                preload_audio_data,
90                load_in_background,
91                legacy_3d,
92                compression_format,
93            };
94
95            // Extract ambisonic flag (Unity 2017+)
96            if self.version.major >= 2017 {
97                clip.ambisonic = reader.read_bool().ok();
98            }
99        }
100
101        // Read streaming info
102        let stream_offset = reader.read_u64().unwrap_or(0);
103        let stream_size = reader.read_u32().unwrap_or(0);
104        let stream_path = reader.read_aligned_string().unwrap_or_default();
105
106        clip.stream_info = StreamingInfo {
107            offset: stream_offset,
108            size: stream_size,
109            path: stream_path,
110        };
111
112        // Read audio data size and data
113        let data_size = reader.read_u32().unwrap_or(0);
114        if data_size > 0 && reader.remaining() >= data_size as usize {
115            clip.data = reader.read_bytes(data_size as usize).unwrap_or_default();
116        } else if reader.remaining() > 0 {
117            // Fallback: take all remaining data
118            let remaining_data = reader.read_remaining();
119            clip.data = remaining_data.to_vec();
120        }
121
122        clip.size = clip.data.len() as u64;
123
124        Ok(clip)
125    }
126
127    /// Get supported formats for this Unity version
128    pub fn supported_formats(&self) -> Vec<AudioCompressionFormat> {
129        let mut formats = vec![
130            AudioCompressionFormat::PCM,
131            AudioCompressionFormat::Vorbis,
132            AudioCompressionFormat::ADPCM,
133        ];
134
135        // Add formats based on Unity version
136        if self.version.major >= 4 {
137            formats.push(AudioCompressionFormat::MP3);
138        }
139
140        if self.version.major >= 5 {
141            formats.push(AudioCompressionFormat::AAC);
142        }
143
144        // Platform-specific formats (usually not supported for decoding)
145        // formats.push(AudioCompressionFormat::VAG);
146        // formats.push(AudioCompressionFormat::XMA);
147        // formats.push(AudioCompressionFormat::ATRAC9);
148
149        formats
150    }
151
152    /// Check if a format can be processed
153    pub fn can_process(&self, format: AudioCompressionFormat) -> bool {
154        self.supported_formats().contains(&format)
155    }
156
157    /// Load streaming data from external file
158    pub fn load_streaming_data(&self, clip: &AudioClip) -> Result<Vec<u8>> {
159        if clip.stream_info.path.is_empty() {
160            return Err(BinaryError::invalid_data("No streaming path specified"));
161        }
162
163        // Try to read from the streaming file
164        use std::fs;
165        use std::path::Path;
166
167        let stream_path = Path::new(&clip.stream_info.path);
168
169        // Try different possible locations for the streaming file
170        let possible_paths = [
171            stream_path.to_path_buf(),
172            Path::new("StreamingAssets").join(stream_path),
173            Path::new("..").join(stream_path),
174        ];
175
176        for path in &possible_paths {
177            if path.exists() {
178                match fs::File::open(path) {
179                    Ok(mut file) => {
180                        use std::io::{Read, Seek, SeekFrom};
181
182                        // Seek to the specified offset
183                        if file.seek(SeekFrom::Start(clip.stream_info.offset)).is_err() {
184                            continue; // Try next path
185                        }
186
187                        // Read the specified amount of data
188                        let mut buffer = vec![0u8; clip.stream_info.size as usize];
189                        match file.read_exact(&mut buffer) {
190                            Ok(_) => return Ok(buffer),
191                            Err(_) => continue, // Try next path
192                        }
193                    }
194                    Err(_) => continue, // Try next path
195                }
196            }
197        }
198
199        Err(BinaryError::generic(format!(
200            "Could not load streaming data from: {}",
201            clip.stream_info.path
202        )))
203    }
204
205    /// Get audio data (either embedded or streamed)
206    pub fn get_audio_data(&self, clip: &AudioClip) -> Result<Vec<u8>> {
207        if !clip.data.is_empty() {
208            // Use embedded data
209            Ok(clip.data.clone())
210        } else if clip.is_streamed() {
211            // Load streaming data
212            self.load_streaming_data(clip)
213        } else {
214            Err(BinaryError::invalid_data("No audio data available"))
215        }
216    }
217}
218
219// Legacy compatibility - alias for the old processor name
220pub type AudioClipProcessor = AudioClipConverter;