moosicbox_lofty/iff/aiff/
properties.rs1use 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 byteorder::{BigEndian, ReadBytesExt};
12
13#[allow(non_camel_case_types)]
17#[derive(Clone, Eq, PartialEq, Default, Debug)]
18pub enum AiffCompressionType {
19 #[default]
20 None,
22 ACE2,
24 ACE8,
26 MAC3,
28 MAC6,
30 sowt,
32 fl32,
34 fl64,
36 alaw,
38 ulaw,
40 ULAW,
42 ALAW,
44 FL32,
46 Other {
48 compression_type: [u8; 4],
50 compression_name: String,
52 },
53}
54
55impl AiffCompressionType {
56 pub fn compression_name(&self) -> Cow<'_, str> {
69 match self {
70 AiffCompressionType::None => Cow::Borrowed("not compressed"),
71 AiffCompressionType::ACE2 => Cow::Borrowed("ACE 2-to-1"),
72 AiffCompressionType::ACE8 => Cow::Borrowed("ACE 8-to-3"),
73 AiffCompressionType::MAC3 => Cow::Borrowed("MACE 3-to-1"),
74 AiffCompressionType::MAC6 => Cow::Borrowed("MACE 6-to-1"),
75 AiffCompressionType::sowt => Cow::Borrowed(""), AiffCompressionType::fl32 => Cow::Borrowed("32-bit floating point"),
77 AiffCompressionType::fl64 => Cow::Borrowed("64-bit floating point"),
78 AiffCompressionType::alaw => Cow::Borrowed("ALaw 2:1"),
79 AiffCompressionType::ulaw => Cow::Borrowed("µLaw 2:1"),
80 AiffCompressionType::ULAW => Cow::Borrowed("CCITT G.711 u-law"),
81 AiffCompressionType::ALAW => Cow::Borrowed("CCITT G.711 A-law"),
82 AiffCompressionType::FL32 => Cow::Borrowed("Float 32"),
83 AiffCompressionType::Other {
84 compression_name, ..
85 } => Cow::from(compression_name),
86 }
87 }
88}
89
90#[derive(Debug, PartialEq, Eq, Clone, Default)]
92#[non_exhaustive]
93pub struct AiffProperties {
94 pub(crate) duration: Duration,
95 pub(crate) overall_bitrate: u32,
96 pub(crate) audio_bitrate: u32,
97 pub(crate) sample_rate: u32,
98 pub(crate) sample_size: u16,
99 pub(crate) channels: u16,
100 pub(crate) compression_type: Option<AiffCompressionType>,
101}
102
103impl From<AiffProperties> for FileProperties {
104 fn from(value: AiffProperties) -> Self {
105 Self {
106 duration: value.duration,
107 overall_bitrate: Some(value.overall_bitrate),
108 audio_bitrate: Some(value.audio_bitrate),
109 sample_rate: Some(value.sample_rate),
110 bit_depth: Some(value.sample_size as u8),
111 channels: Some(value.channels as u8),
112 channel_mask: None,
113 }
114 }
115}
116
117impl AiffProperties {
118 pub fn duration(&self) -> Duration {
120 self.duration
121 }
122
123 pub fn overall_bitrate(&self) -> u32 {
125 self.overall_bitrate
126 }
127
128 pub fn audio_bitrate(&self) -> u32 {
130 self.audio_bitrate
131 }
132
133 pub fn sample_rate(&self) -> u32 {
135 self.sample_rate
136 }
137
138 pub fn sample_size(&self) -> u16 {
140 self.sample_size
141 }
142
143 pub fn channels(&self) -> u16 {
145 self.channels
146 }
147
148 pub fn compression_type(&self) -> Option<&AiffCompressionType> {
150 self.compression_type.as_ref()
151 }
152}
153
154pub(super) fn read_properties(
155 comm: &mut &[u8],
156 compression_present: CompressionPresent,
157 stream_len: u32,
158 file_length: u64,
159) -> Result<AiffProperties> {
160 let channels = comm.read_u16::<BigEndian>()?;
161
162 if channels == 0 {
163 decode_err!(@BAIL Aiff, "File contains 0 channels");
164 }
165
166 let sample_frames = comm.read_u32::<BigEndian>()?;
167 let sample_size = comm.read_u16::<BigEndian>()?;
168
169 let mut sample_rate_bytes = [0; 10];
170 comm.read_exact(&mut sample_rate_bytes)?;
171
172 let sign = u64::from(sample_rate_bytes[0] & 0x80);
173
174 sample_rate_bytes[0] &= 0x7F;
175
176 let mut exponent = u16::from(sample_rate_bytes[0]) << 8 | u16::from(sample_rate_bytes[1]);
177 exponent = exponent - 16383 + 1023;
178
179 let fraction = &mut sample_rate_bytes[2..];
180 fraction[0] &= 0x7F;
181
182 let fraction: Vec<u64> = fraction.iter_mut().map(|v| u64::from(*v)).collect();
183
184 let fraction = fraction[0] << 56
185 | fraction[1] << 48
186 | fraction[2] << 40
187 | fraction[3] << 32
188 | fraction[4] << 24
189 | fraction[5] << 16
190 | fraction[6] << 8
191 | fraction[7];
192
193 let f64_bytes = sign << 56 | u64::from(exponent) << 52 | fraction >> 11;
194 let float = f64::from_be_bytes(f64_bytes.to_be_bytes());
195
196 let sample_rate = float.round() as u32;
197
198 let (duration, overall_bitrate, audio_bitrate) = if sample_rate > 0 && sample_frames > 0 {
199 let length = (f64::from(sample_frames) * 1000.0) / f64::from(sample_rate);
200
201 (
202 Duration::from_millis(length as u64),
203 ((file_length as f64) * 8.0 / length + 0.5) as u32,
204 (f64::from(stream_len) * 8.0 / length + 0.5) as u32,
205 )
206 } else {
207 (Duration::ZERO, 0, 0)
208 };
209
210 let mut compression = None;
211 if comm.len() >= 5 && compression_present == CompressionPresent::Yes {
212 let mut compression_type = [0u8; 4];
213 comm.read_exact(&mut compression_type)?;
214
215 compression = Some(match &compression_type {
216 b"NONE" => AiffCompressionType::None,
217 b"ACE2" => AiffCompressionType::ACE2,
218 b"ACE8" => AiffCompressionType::ACE8,
219 b"MAC3" => AiffCompressionType::MAC3,
220 b"MAC6" => AiffCompressionType::MAC6,
221 b"sowt" => AiffCompressionType::sowt,
222 b"fl32" => AiffCompressionType::fl32,
223 b"fl64" => AiffCompressionType::fl64,
224 b"alaw" => AiffCompressionType::alaw,
225 b"ulaw" => AiffCompressionType::ulaw,
226 b"ULAW" => AiffCompressionType::ULAW,
227 b"ALAW" => AiffCompressionType::ALAW,
228 b"FL32" => AiffCompressionType::FL32,
229 _ => {
230 let mut compression_name = String::new();
232
233 let compression_name_size = comm.read_u8()?;
234 if compression_name_size > 0 {
235 let mut compression_name_bytes = try_vec![0u8; compression_name_size as usize];
236 comm.read_exact(&mut compression_name_bytes)?;
237
238 compression_name = utf8_decode(compression_name_bytes)?;
239 }
240
241 AiffCompressionType::Other {
242 compression_type,
243 compression_name,
244 }
245 },
246 });
247 }
248
249 Ok(AiffProperties {
250 duration,
251 overall_bitrate,
252 audio_bitrate,
253 sample_rate,
254 sample_size,
255 channels,
256 compression_type: compression,
257 })
258}