1use std::path::Path;
29
30use super::bits::BitReader;
31use crate::tags::TagError;
32use crate::tags::bytes::read_at_most;
33
34#[path = "flac_precision.rs"]
35mod precision;
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43pub struct Limits {
44 pub max_bytes: usize,
46 pub max_frames: usize,
48}
49
50impl Limits {
51 pub fn quick() -> Limits {
54 Limits {
55 max_bytes: 8 * 1024 * 1024,
56 max_frames: 512,
57 }
58 }
59
60 pub fn thorough() -> Limits {
62 Limits {
63 max_bytes: usize::MAX,
64 max_frames: usize::MAX,
65 }
66 }
67}
68
69impl Default for Limits {
70 fn default() -> Limits {
71 Limits::quick()
72 }
73}
74
75#[derive(Debug, Clone, PartialEq)]
78pub struct FlacAudit {
79 pub declared_bit_depth: u16,
81 pub effective_bit_depth: u16,
88 pub channels: u16,
90 pub stereo: StereoContent,
92 pub digital_silence: bool,
94 pub frames_examined: usize,
96 pub truncated: bool,
100}
101
102impl FlacAudit {
103 pub fn is_padded(&self) -> bool {
105 self.effective_bit_depth < self.declared_bit_depth
106 }
107}
108
109#[derive(Debug, Clone, Copy, PartialEq, Eq)]
111pub enum StereoContent {
112 Unknown,
114 Independent,
116 Duplicated,
118}
119
120pub fn audit(path: &Path, limits: Limits) -> Result<FlacAudit, TagError> {
122 let mut file = crate::tags::bytes::open_regular(path)?;
123 let (stream_info, audio_start) = read_stream_info(&mut file)?;
124
125 let budget = match limits.max_bytes {
126 usize::MAX => file.metadata()?.len().saturating_sub(audio_start) as usize,
127 bytes => bytes,
128 };
129 let data = read_at_most(&mut file, audio_start, budget)?;
130 let reached_end_of_file = audio_start + data.len() as u64 >= file.metadata()?.len();
131 let mut reader = BitReader::new(&data);
132
133 let mut min_wasted = u32::MAX;
134 let mut confirmed_wasted = u32::MAX;
135 let mut precision = precision::PrecisionConfirmation::new(stream_info.raw);
136 let mut frames = 0usize;
137 let mut all_constant = true;
138 let mut duplicated_frames = 0usize;
139 let mut stereo_frames = 0usize;
140
141 while let Some(frame) = read_frame(&mut reader, &stream_info) {
142 frames += 1;
143 min_wasted = min_wasted.min(frame.min_wasted);
144 if min_wasted > 0 && confirmed_wasted > 0 && !frame.all_constant {
149 let confirmed = data
150 .get(frame.span.start..frame.span.end)
151 .and_then(|bytes| precision.confirm(bytes, frame.block_size))
152 .unwrap_or(0);
153 confirmed_wasted = confirmed_wasted.min(confirmed);
154 }
155 all_constant &= frame.all_constant;
156 if frame.is_stereo {
157 stereo_frames += 1;
158 if frame.channels_identical {
159 duplicated_frames += 1;
160 }
161 }
162 reader.align_to_byte();
164 if reader.is_exhausted() || frames >= limits.max_frames {
165 break;
166 }
167 }
168
169 let wasted = if frames == 0 || min_wasted == u32::MAX {
170 0
171 } else {
172 min_wasted.min(confirmed_wasted)
173 };
174 let effective = (stream_info.bits_per_sample as u32).saturating_sub(wasted);
175
176 let stereo = if stream_info.channels != 2 || stereo_frames == 0 {
177 StereoContent::Unknown
178 } else if duplicated_frames == stereo_frames {
179 StereoContent::Duplicated
180 } else {
181 StereoContent::Independent
182 };
183
184 Ok(FlacAudit {
185 declared_bit_depth: stream_info.bits_per_sample,
186 effective_bit_depth: effective.max(1) as u16,
187 channels: stream_info.channels,
188 stereo,
189 digital_silence: frames > 0 && all_constant,
190 frames_examined: frames,
191 truncated: !reached_end_of_file || frames >= limits.max_frames,
192 })
193}
194
195pub(crate) struct StreamInfo {
197 pub(crate) bits_per_sample: u16,
198 pub(crate) channels: u16,
199 raw: Option<[u8; 34]>,
202}
203
204pub(crate) fn read_stream_info(file: &mut std::fs::File) -> Result<(StreamInfo, u64), TagError> {
206 let start = crate::tags::id3::skip_id3v2(file)?;
207 let header = read_at_most(file, start, 4)?;
208 if header.len() < 4 || &header[..4] != b"fLaC" {
209 return Err(TagError::UnrecognizedFormat);
210 }
211
212 let mut offset = start + 4;
213 let mut info = None;
214 loop {
215 let head = read_at_most(file, offset, 4)?;
216 if head.len() < 4 {
217 return Err(TagError::Malformed("metadata block header truncated"));
218 }
219 let last = head[0] & 0x80 != 0;
220 let kind = head[0] & 0x7F;
221 let length = ((head[1] as u64) << 16) | ((head[2] as u64) << 8) | head[3] as u64;
222 offset += 4;
223
224 if kind == 0 {
225 let body = read_at_most(file, offset, length.min(34) as usize)?;
226 if body.len() < 18 {
227 return Err(TagError::Malformed("STREAMINFO truncated"));
228 }
229 let packed = u64::from_be_bytes([
230 body[10], body[11], body[12], body[13], body[14], body[15], body[16], body[17],
231 ]);
232 info = Some(StreamInfo {
233 channels: (((packed >> 41) & 0x07) as u16) + 1,
234 bits_per_sample: (((packed >> 36) & 0x1F) as u16) + 1,
235 raw: body.as_slice().try_into().ok(),
236 });
237 }
238 offset += length;
239 if last {
240 break;
241 }
242 }
243
244 match info {
245 Some(info) => Ok((info, offset)),
246 None => Err(TagError::Malformed("FLAC stream without STREAMINFO")),
247 }
248}
249
250pub(crate) struct FrameAudit {
252 min_wasted: u32,
253 block_size: u32,
254 all_constant: bool,
255 is_stereo: bool,
256 channels_identical: bool,
257 pub(crate) span: FrameSpan,
261}
262
263pub(crate) struct FrameSpan {
265 pub(crate) start: usize,
267 pub(crate) end: usize,
269 pub(crate) header_end: usize,
271 pub(crate) stored_crc8: u8,
273 pub(crate) stored_crc16: u16,
275}
276
277pub(crate) fn read_frame(reader: &mut BitReader<'_>, info: &StreamInfo) -> Option<FrameAudit> {
282 let start = reader.bit_pos() / 8;
283 if reader.bits(14)? != 0b11_1111_1111_1110 {
284 return None;
285 }
286 reader.bits(1)?; reader.bits(1)?; let block_size_bits = reader.bits(4)? as u32;
290 let sample_rate_bits = reader.bits(4)? as u32;
291 let channel_assignment = reader.bits(4)? as u32;
292 let sample_size_bits = reader.bits(3)? as u32;
293 reader.bits(1)?; let first = reader.bits(8)? as u8;
297 let extra = match first {
298 0x00..=0x7F => 0,
299 0xC0..=0xDF => 1,
300 0xE0..=0xEF => 2,
301 0xF0..=0xF7 => 3,
302 0xF8..=0xFB => 4,
303 0xFC..=0xFD => 5,
304 0xFE => 6,
305 _ => return None,
306 };
307 reader.skip(extra * 8)?;
308
309 let block_size = match block_size_bits {
310 0 => return None,
311 1 => 192,
312 2..=5 => 576 << (block_size_bits - 2),
313 6 => reader.bits(8)? as u32 + 1,
314 7 => reader.bits(16)? as u32 + 1,
315 _ => 256 << (block_size_bits - 8),
316 };
317 match sample_rate_bits {
318 12 => {
319 reader.bits(8)?;
320 }
321 13 | 14 => {
322 reader.bits(16)?;
323 }
324 15 => return None,
325 _ => {}
326 }
327 let header_end = reader.bit_pos() / 8;
328 let stored_crc8 = reader.bits(8)? as u8;
329
330 let bits_per_sample = match sample_size_bits {
331 0 => info.bits_per_sample as u32,
332 1 => 8,
333 2 => 12,
334 4 => 16,
335 5 => 20,
336 6 => 24,
337 7 => 32,
338 _ => return None,
339 };
340
341 let (channel_count, side_index) = match channel_assignment {
344 0..=7 => (channel_assignment + 1, None),
345 8 => (2, Some(1)),
346 9 => (2, Some(0)),
347 10 => (2, Some(1)),
348 _ => return None,
349 };
350 let mid_index = if channel_assignment == 10 {
354 Some(0)
355 } else {
356 None
357 };
358
359 let mut min_wasted = u32::MAX;
360 let mut all_constant = true;
361 let mut side_is_zero = false;
362
363 for channel in 0..channel_count {
364 let extra_bit = side_index == Some(channel);
365 let bps = bits_per_sample + if extra_bit { 1 } else { 0 };
366 let sub = read_subframe(reader, block_size, bps)?;
367 if sub.constant_value.is_none() {
371 let corrected = sub.wasted + if mid_index == Some(channel) { 1 } else { 0 };
372 min_wasted = min_wasted.min(corrected);
373 }
374 all_constant &= sub.constant_value == Some(0);
375 if extra_bit && sub.constant_value == Some(0) {
376 side_is_zero = true;
377 }
378 }
379
380 reader.align_to_byte();
381 let stored_crc16 = reader.bits(16)? as u16;
382
383 Some(FrameAudit {
384 min_wasted,
385 block_size,
386 all_constant,
387 is_stereo: side_index.is_some(),
388 channels_identical: side_is_zero,
389 span: FrameSpan {
390 start,
391 end: reader.bit_pos() / 8,
392 header_end,
393 stored_crc8,
394 stored_crc16,
395 },
396 })
397}
398
399struct SubframeAudit {
401 wasted: u32,
402 constant_value: Option<i64>,
404}
405
406fn read_subframe(
412 reader: &mut BitReader<'_>,
413 block_size: u32,
414 bits_per_sample: u32,
415) -> Option<SubframeAudit> {
416 if reader.bits(1)? != 0 {
417 return None; }
419 let kind = reader.bits(6)? as u32;
420 let has_wasted = reader.bits(1)? == 1;
421 let wasted = if has_wasted { reader.unary()? + 1 } else { 0 };
422
423 let bps = bits_per_sample.checked_sub(wasted)?;
424 if bps == 0 || bps > 33 {
425 return None;
426 }
427
428 let mut constant_value = None;
429 match kind {
430 0 => {
431 constant_value = Some(reader.signed_bits(bps)?);
432 }
433 1 => {
434 reader.skip(block_size as usize * bps as usize)?;
435 }
436 8..=12 => {
437 let order = kind - 8;
438 reader.skip(order as usize * bps as usize)?;
439 skip_residual(reader, block_size, order)?;
440 }
441 32..=63 => {
442 let order = kind - 31;
443 reader.skip(order as usize * bps as usize)?;
444 let precision = reader.bits(4)? as u32 + 1;
445 if precision > 15 {
446 return None; }
448 reader.bits(5)?; reader.skip(order as usize * precision as usize)?;
450 skip_residual(reader, block_size, order)?;
451 }
452 _ => return None, }
454
455 Some(SubframeAudit {
456 wasted,
457 constant_value,
458 })
459}
460
461fn skip_residual(reader: &mut BitReader<'_>, block_size: u32, order: u32) -> Option<()> {
463 let method = reader.bits(2)?;
464 let param_bits = match method {
465 0 => 4,
466 1 => 5,
467 _ => return None, };
469 let escape = (1u64 << param_bits) - 1;
470
471 let partition_order = reader.bits(4)? as u32;
472 let partitions = 1u32 << partition_order;
473 if !block_size.is_multiple_of(partitions) {
474 return None;
475 }
476
477 for partition in 0..partitions {
478 let mut samples = block_size >> partition_order;
479 if partition == 0 {
480 samples = samples.checked_sub(order)?;
481 }
482 let parameter = reader.bits(param_bits)?;
483 if parameter == escape {
484 let raw_bits = reader.bits(5)? as usize;
485 reader.skip(samples as usize * raw_bits)?;
486 } else {
487 for _ in 0..samples {
488 reader.unary()?;
489 reader.bits(parameter as u32)?;
490 }
491 }
492 }
493 Some(())
494}