1use super::read::CompressionPresent;
2use crate::error::Result;
3use crate::macros::{decode_err, try_vec};
4use crate::properties::FileProperties;
5use crate::util::text::utf8_decode;
6
7use std::borrow::Cow;
8use std::io::Read;
9use std::time::Duration;
10
11use crate::io::ReadExt;
12use byteorder::{BigEndian, ReadBytesExt};
13
14#[allow(non_camel_case_types)]
18#[derive(Clone, Eq, PartialEq, Default, Debug)]
19pub enum AiffCompressionType {
20 #[default]
21 None,
23 ACE2,
25 ACE8,
27 MAC3,
29 MAC6,
31 sowt,
33 fl32,
35 fl64,
37 alaw,
39 ulaw,
41 ULAW,
43 ALAW,
45 FL32,
47 Other {
49 compression_type: [u8; 4],
51 compression_name: String,
53 },
54}
55
56impl AiffCompressionType {
57 pub fn compression_name(&self) -> Cow<'_, str> {
70 match self {
71 AiffCompressionType::None => Cow::Borrowed("not compressed"),
72 AiffCompressionType::ACE2 => Cow::Borrowed("ACE 2-to-1"),
73 AiffCompressionType::ACE8 => Cow::Borrowed("ACE 8-to-3"),
74 AiffCompressionType::MAC3 => Cow::Borrowed("MACE 3-to-1"),
75 AiffCompressionType::MAC6 => Cow::Borrowed("MACE 6-to-1"),
76 AiffCompressionType::sowt => Cow::Borrowed(""), AiffCompressionType::fl32 => Cow::Borrowed("32-bit floating point"),
78 AiffCompressionType::fl64 => Cow::Borrowed("64-bit floating point"),
79 AiffCompressionType::alaw => Cow::Borrowed("ALaw 2:1"),
80 AiffCompressionType::ulaw => Cow::Borrowed("µLaw 2:1"),
81 AiffCompressionType::ULAW => Cow::Borrowed("CCITT G.711 u-law"),
82 AiffCompressionType::ALAW => Cow::Borrowed("CCITT G.711 A-law"),
83 AiffCompressionType::FL32 => Cow::Borrowed("Float 32"),
84 AiffCompressionType::Other {
85 compression_name, ..
86 } => Cow::from(compression_name),
87 }
88 }
89}
90
91#[derive(Debug, PartialEq, Eq, Clone, Default)]
93#[non_exhaustive]
94pub struct AiffProperties {
95 pub(crate) duration: Duration,
96 pub(crate) overall_bitrate: u32,
97 pub(crate) audio_bitrate: u32,
98 pub(crate) sample_rate: u32,
99 pub(crate) sample_size: u16,
100 pub(crate) channels: u16,
101 pub(crate) compression_type: Option<AiffCompressionType>,
102}
103
104impl From<AiffProperties> for FileProperties {
105 fn from(value: AiffProperties) -> Self {
106 Self {
107 duration: value.duration,
108 overall_bitrate: Some(value.overall_bitrate),
109 audio_bitrate: Some(value.audio_bitrate),
110 sample_rate: Some(value.sample_rate),
111 bit_depth: Some(value.sample_size as u8),
112 channels: Some(value.channels as u8),
113 channel_mask: None,
114 }
115 }
116}
117
118impl AiffProperties {
119 pub fn duration(&self) -> Duration {
121 self.duration
122 }
123
124 pub fn overall_bitrate(&self) -> u32 {
126 self.overall_bitrate
127 }
128
129 pub fn audio_bitrate(&self) -> u32 {
131 self.audio_bitrate
132 }
133
134 pub fn sample_rate(&self) -> u32 {
136 self.sample_rate
137 }
138
139 pub fn sample_size(&self) -> u16 {
141 self.sample_size
142 }
143
144 pub fn channels(&self) -> u16 {
146 self.channels
147 }
148
149 pub fn compression_type(&self) -> Option<&AiffCompressionType> {
151 self.compression_type.as_ref()
152 }
153}
154
155pub(super) fn read_properties(
156 comm: &mut &[u8],
157 compression_present: CompressionPresent,
158 stream_len: u32,
159 file_length: u64,
160) -> Result<AiffProperties> {
161 let channels = comm.read_u16::<BigEndian>()?;
162
163 if channels == 0 {
164 decode_err!(@BAIL Aiff, "File contains 0 channels");
165 }
166
167 let sample_frames = comm.read_u32::<BigEndian>()?;
168 let sample_size = comm.read_u16::<BigEndian>()?;
169
170 let sample_rate_extended = comm.read_f80()?;
171 let sample_rate_64 = sample_rate_extended.as_f64();
172 if !sample_rate_64.is_finite() || !sample_rate_64.is_sign_positive() {
173 decode_err!(@BAIL Aiff, "Invalid sample rate");
174 }
175
176 let sample_rate = sample_rate_64.round() as u32;
177
178 let (duration, overall_bitrate, audio_bitrate) = if sample_rate > 0 && sample_frames > 0 {
179 let length = (f64::from(sample_frames) * 1000.0) / f64::from(sample_rate);
180
181 (
182 Duration::from_millis(length as u64),
183 ((file_length as f64) * 8.0 / length + 0.5) as u32,
184 (f64::from(stream_len) * 8.0 / length + 0.5) as u32,
185 )
186 } else {
187 (Duration::ZERO, 0, 0)
188 };
189
190 let is_compressed = comm.len() >= 5 && compression_present == CompressionPresent::Yes;
191 if !is_compressed {
192 return Ok(AiffProperties {
193 duration,
194 overall_bitrate,
195 audio_bitrate,
196 sample_rate,
197 sample_size,
198 channels,
199 compression_type: None,
200 });
201 }
202
203 let mut compression_type = [0u8; 4];
204 comm.read_exact(&mut compression_type)?;
205
206 let compression = Some(match &compression_type {
207 b"NONE" => AiffCompressionType::None,
208 b"ACE2" => AiffCompressionType::ACE2,
209 b"ACE8" => AiffCompressionType::ACE8,
210 b"MAC3" => AiffCompressionType::MAC3,
211 b"MAC6" => AiffCompressionType::MAC6,
212 b"sowt" => AiffCompressionType::sowt,
213 b"fl32" => AiffCompressionType::fl32,
214 b"fl64" => AiffCompressionType::fl64,
215 b"alaw" => AiffCompressionType::alaw,
216 b"ulaw" => AiffCompressionType::ulaw,
217 b"ULAW" => AiffCompressionType::ULAW,
218 b"ALAW" => AiffCompressionType::ALAW,
219 b"FL32" => AiffCompressionType::FL32,
220 _ => {
221 log::debug!(
222 "Encountered unknown compression type: {:?}",
223 compression_type
224 );
225
226 let mut compression_name = String::new();
228
229 let compression_name_size = comm.read_u8()?;
230 if compression_name_size > 0 {
231 let mut compression_name_bytes = try_vec![0u8; compression_name_size as usize];
232 comm.read_exact(&mut compression_name_bytes)?;
233
234 compression_name = utf8_decode(compression_name_bytes)?;
235 }
236
237 AiffCompressionType::Other {
238 compression_type,
239 compression_name,
240 }
241 },
242 });
243
244 Ok(AiffProperties {
245 duration,
246 overall_bitrate,
247 audio_bitrate,
248 sample_rate,
249 sample_size,
250 channels,
251 compression_type: compression,
252 })
253}