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}