Skip to main content

lofty/ape/
properties.rs

1use crate::config::ParsingMode;
2use crate::error::Result;
3use crate::macros::decode_err;
4use crate::properties::FileProperties;
5
6use std::io::{Read, Seek, SeekFrom};
7use std::time::Duration;
8
9use byteorder::{LittleEndian, ReadBytesExt};
10
11/// An APE file's audio properties
12#[derive(Clone, Debug, PartialEq, Eq, Default)]
13#[non_exhaustive]
14pub struct ApeProperties {
15	pub(crate) version: u16,
16	pub(crate) duration: Duration,
17	pub(crate) overall_bitrate: u32,
18	pub(crate) audio_bitrate: u32,
19	pub(crate) sample_rate: u32,
20	pub(crate) bit_depth: u8,
21	pub(crate) channels: u8,
22}
23
24impl From<ApeProperties> for FileProperties {
25	fn from(input: ApeProperties) -> Self {
26		Self {
27			duration: input.duration,
28			overall_bitrate: Some(input.overall_bitrate),
29			audio_bitrate: Some(input.audio_bitrate),
30			sample_rate: Some(input.sample_rate),
31			bit_depth: Some(input.bit_depth),
32			channels: Some(input.channels),
33			channel_mask: None,
34		}
35	}
36}
37
38impl ApeProperties {
39	/// Duration of the audio
40	pub fn duration(&self) -> Duration {
41		self.duration
42	}
43
44	/// Overall bitrate (kbps)
45	pub fn overall_bitrate(&self) -> u32 {
46		self.overall_bitrate
47	}
48
49	/// Audio bitrate (kbps)
50	pub fn bitrate(&self) -> u32 {
51		self.audio_bitrate
52	}
53
54	/// Sample rate (Hz)
55	pub fn sample_rate(&self) -> u32 {
56		self.sample_rate
57	}
58
59	/// Bits per sample
60	pub fn bit_depth(&self) -> u8 {
61		self.bit_depth
62	}
63
64	/// Channel count
65	pub fn channels(&self) -> u8 {
66		self.channels
67	}
68
69	/// APE version
70	pub fn version(&self) -> u16 {
71		self.version
72	}
73}
74
75pub(super) fn read_properties<R>(
76	data: &mut R,
77	stream_len: u64,
78	file_length: u64,
79	parse_mode: ParsingMode,
80) -> Result<ApeProperties>
81where
82	R: Read + Seek,
83{
84	let version = data
85		.read_u16::<LittleEndian>()
86		.map_err(|_| decode_err!(Ape, "Unable to read APE tag version"))?;
87
88	// Property reading differs between versions
89	if version >= 3980 {
90		properties_gt_3980(data, version, stream_len, file_length, parse_mode)
91	} else {
92		properties_lt_3980(data, version, stream_len, file_length, parse_mode)
93	}
94}
95
96fn properties_gt_3980<R>(
97	data: &mut R,
98	version: u16,
99	stream_len: u64,
100	file_length: u64,
101	parse_mode: ParsingMode,
102) -> Result<ApeProperties>
103where
104	R: Read + Seek,
105{
106	// First read the file descriptor
107	let mut descriptor = [0; 46];
108	data.read_exact(&mut descriptor).map_err(|_| {
109		decode_err!(
110			Ape,
111			"Not enough data left in reader to finish file descriptor"
112		)
113	})?;
114
115	// The only piece of information we need from the file descriptor
116	let descriptor_len = u32::from_le_bytes(
117		descriptor[2..6].try_into().unwrap(), // Infallible
118	);
119
120	// The descriptor should be 52 bytes long (including ['M', 'A', 'C', ' ']
121	// Anything extra is unknown, and just gets skipped
122	if descriptor_len > 52 {
123		data.seek(SeekFrom::Current(i64::from(descriptor_len - 52)))?;
124	}
125
126	// Move on to the header
127	let mut header = [0; 24];
128	data.read_exact(&mut header)
129		.map_err(|_| decode_err!(Ape, "Not enough data left in reader to finish MAC header"))?;
130
131	let mut properties = ApeProperties::default();
132	properties.version = version;
133
134	// Skip the first 4 bytes of the header
135	// Compression type (2)
136	// Format flags (2)
137	let header_read = &mut &header[4..];
138
139	let blocks_per_frame = header_read.read_u32::<LittleEndian>()?;
140	let final_frame_blocks = header_read.read_u32::<LittleEndian>()?;
141	let total_frames = header_read.read_u32::<LittleEndian>()?;
142
143	properties.bit_depth = header_read.read_u16::<LittleEndian>()? as u8;
144	properties.channels = header_read.read_u16::<LittleEndian>()? as u8;
145	properties.sample_rate = header_read.read_u32::<LittleEndian>()?;
146
147	match verify(total_frames, properties.channels) {
148		Err(e) if parse_mode == ParsingMode::Strict => return Err(e),
149		Err(_) => return Ok(properties),
150		_ => {},
151	}
152
153	get_duration_bitrate(
154		&mut properties,
155		file_length,
156		total_frames,
157		final_frame_blocks,
158		blocks_per_frame,
159		stream_len,
160	);
161
162	Ok(properties)
163}
164
165fn properties_lt_3980<R>(
166	data: &mut R,
167	version: u16,
168	stream_len: u64,
169	file_length: u64,
170	parse_mode: ParsingMode,
171) -> Result<ApeProperties>
172where
173	R: Read,
174{
175	// Versions < 3980 don't have a descriptor
176	let mut header = [0; 26];
177	data.read_exact(&mut header)
178		.map_err(|_| decode_err!(Ape, "Not enough data left in reader to finish MAC header"))?;
179
180	let mut properties = ApeProperties::default();
181	properties.version = version;
182
183	let header_reader = &mut &header[..];
184
185	// https://github.com/fernandotcl/monkeys-audio/blob/5fe956c7e67c13daa80518a4cc7001e9fa185297/src/MACLib/MACLib.h#L74
186	let compression_level = header_reader.read_u16::<LittleEndian>()?;
187	let format_flags = header_reader.read_u16::<LittleEndian>()?;
188	if (format_flags & 0b1) == 1 {
189		properties.bit_depth = 8
190	} else if (format_flags & 0b1000) == 8 {
191		properties.bit_depth = 24
192	} else {
193		properties.bit_depth = 16
194	}
195
196	let blocks_per_frame = match version {
197		_ if version >= 3950 => 73728 * 4,
198		_ if version >= 3900 || (version >= 3800 && compression_level >= 4000) => 73728,
199		_ => 9216,
200	};
201
202	properties.channels = header_reader.read_u16::<LittleEndian>()? as u8;
203	properties.sample_rate = header_reader.read_u32::<LittleEndian>()?;
204
205	// Skipping 8 bytes
206	// WAV header length (4)
207	// WAV tail length (4)
208	let mut _skip = [0; 8];
209	header_reader.read_exact(&mut _skip)?;
210
211	let total_frames = header_reader.read_u32::<LittleEndian>()?;
212	let final_frame_blocks = header_reader.read_u32::<LittleEndian>()?;
213
214	match verify(total_frames, properties.channels) {
215		Err(e) if parse_mode == ParsingMode::Strict => return Err(e),
216		Err(_) => return Ok(properties),
217		_ => {},
218	}
219
220	get_duration_bitrate(
221		&mut properties,
222		file_length,
223		total_frames,
224		final_frame_blocks,
225		blocks_per_frame,
226		stream_len,
227	);
228
229	Ok(properties)
230}
231
232/// Verifies the channel count falls within the bounds of the spec, and we have some audio frames to work with.
233fn verify(total_frames: u32, channels: u8) -> Result<()> {
234	if !(1..=32).contains(&channels) {
235		decode_err!(@BAIL Ape, "File has an invalid channel count (must be between 1 and 32 inclusive)");
236	}
237
238	if total_frames == 0 {
239		decode_err!(@BAIL Ape, "File contains no frames");
240	}
241
242	Ok(())
243}
244
245fn get_duration_bitrate(
246	properties: &mut ApeProperties,
247	file_length: u64,
248	total_frames: u32,
249	final_frame_blocks: u32,
250	blocks_per_frame: u32,
251	stream_len: u64,
252) {
253	let mut total_samples = u64::from(final_frame_blocks);
254
255	if total_samples > 1 {
256		total_samples += u64::from(blocks_per_frame) * u64::from(total_frames - 1)
257	}
258
259	if properties.sample_rate > 0 {
260		let length = (total_samples as f64 * 1000.0) / f64::from(properties.sample_rate);
261
262		properties.duration = Duration::from_millis((length + 0.5) as u64);
263		properties.audio_bitrate = ((stream_len as f64) * 8.0 / length + 0.5) as u32;
264		properties.overall_bitrate = ((file_length as f64) * 8.0 / length + 0.5) as u32;
265	}
266}