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#[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 pub fn duration(&self) -> Duration {
41 self.duration
42 }
43
44 pub fn overall_bitrate(&self) -> u32 {
46 self.overall_bitrate
47 }
48
49 pub fn bitrate(&self) -> u32 {
51 self.audio_bitrate
52 }
53
54 pub fn sample_rate(&self) -> u32 {
56 self.sample_rate
57 }
58
59 pub fn bit_depth(&self) -> u8 {
61 self.bit_depth
62 }
63
64 pub fn channels(&self) -> u8 {
66 self.channels
67 }
68
69 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 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 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 let descriptor_len = u32::from_le_bytes(
117 descriptor[2..6].try_into().unwrap(), );
119
120 if descriptor_len > 52 {
123 data.seek(SeekFrom::Current(i64::from(descriptor_len - 52)))?;
124 }
125
126 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 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 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 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 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
232fn 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}