1use std::io::{Read, Seek, SeekFrom};
2
3use crate::error::{ApeError, ApeResult};
4
5pub const APE_FORMAT_FLAG_8_BIT: u16 = 1 << 0; #[allow(dead_code)]
11pub const APE_FORMAT_FLAG_CRC: u16 = 1 << 1; pub const APE_FORMAT_FLAG_HAS_PEAK_LEVEL: u16 = 1 << 2; pub const APE_FORMAT_FLAG_24_BIT: u16 = 1 << 3; pub const APE_FORMAT_FLAG_HAS_SEEK_ELEMENTS: u16 = 1 << 4;
15pub const APE_FORMAT_FLAG_CREATE_WAV_HEADER: u16 = 1 << 5;
16pub const APE_FORMAT_FLAG_AIFF: u16 = 1 << 6;
17pub const APE_FORMAT_FLAG_W64: u16 = 1 << 7;
18pub const APE_FORMAT_FLAG_SND: u16 = 1 << 8;
19pub const APE_FORMAT_FLAG_BIG_ENDIAN: u16 = 1 << 9;
20pub const APE_FORMAT_FLAG_CAF: u16 = 1 << 10;
21pub const APE_FORMAT_FLAG_SIGNED_8_BIT: u16 = 1 << 11;
22pub const APE_FORMAT_FLAG_FLOATING_POINT: u16 = 1 << 12;
23
24pub const APE_MINIMUM_CHANNELS: u16 = 1;
29pub const APE_MAXIMUM_CHANNELS: u16 = 32;
30const APE_ONE_MILLION: u32 = 1_000_000;
31const APE_WAV_HEADER_OR_FOOTER_MAXIMUM_BYTES: u64 = 8 * 1024 * 1024;
32const FIND_DESCRIPTOR_MAX_SCAN: u64 = 1_048_576; const MAGIC_MAC_SPACE: &[u8; 4] = b"MAC ";
36const MAGIC_MACF: &[u8; 4] = b"MACF";
37
38const APE_DESCRIPTOR_BYTES: u32 = 52;
40const APE_HEADER_BYTES: u32 = 24;
41
42const APE_HEADER_OLD_BYTES: u32 = 32;
44
45#[derive(Debug, Clone)]
51#[allow(dead_code)]
52pub struct ApeDescriptor {
53 pub magic: [u8; 4],
54 pub version: u16,
55 pub padding: u16,
56 pub descriptor_bytes: u32,
57 pub header_bytes: u32,
58 pub seek_table_bytes: u32,
59 pub header_data_bytes: u32,
60 pub frame_data_bytes: u32,
61 pub frame_data_bytes_high: u32,
62 pub terminating_data_bytes: u32,
63 pub md5: [u8; 16],
64}
65
66#[derive(Debug, Clone)]
68pub struct ApeHeader {
69 pub compression_level: u16,
70 pub format_flags: u16,
71 pub blocks_per_frame: u32,
72 pub final_frame_blocks: u32,
73 pub total_frames: u32,
74 pub bits_per_sample: u16,
75 pub channels: u16,
76 pub sample_rate: u32,
77}
78
79#[derive(Debug, Clone)]
81#[allow(dead_code)]
82pub struct ApeFileInfo {
83 pub descriptor: ApeDescriptor,
85 pub header: ApeHeader,
86
87 pub seek_table: Vec<u64>,
89
90 pub junk_header_bytes: u32,
92 pub wav_header_data: Vec<u8>,
93
94 pub total_blocks: i64,
96 pub block_align: u16,
97 pub bytes_per_sample: u16,
98 pub wav_data_bytes: i64,
99 pub length_ms: i64,
100 pub average_bitrate: i64,
101 pub decompressed_bitrate: i64,
102 pub seek_table_elements: i32,
103 pub ape_frame_data_bytes: u64,
104 pub terminating_data_bytes: u32,
105
106 pub file_bytes: u64,
108}
109
110fn read_u16_le<R: Read>(r: &mut R) -> ApeResult<u16> {
115 let mut buf = [0u8; 2];
116 r.read_exact(&mut buf)?;
117 Ok(u16::from_le_bytes(buf))
118}
119
120fn read_u32_le<R: Read>(r: &mut R) -> ApeResult<u32> {
121 let mut buf = [0u8; 4];
122 r.read_exact(&mut buf)?;
123 Ok(u32::from_le_bytes(buf))
124}
125
126fn convert_32bit_seek_table(raw: &[u32]) -> Vec<u64> {
128 let mut result = Vec::with_capacity(raw.len());
129 let mut add: u64 = 0;
130 let mut previous: u32 = 0;
131 for &val in raw {
132 if val < previous {
133 add += 0x1_0000_0000_u64;
134 }
135 result.push(add + val as u64);
136 previous = val;
137 }
138 result
139}
140
141fn find_descriptor<R: Read + Seek>(reader: &mut R) -> ApeResult<u32> {
146 reader.seek(SeekFrom::Start(0))?;
147
148 let mut junk_bytes: u32 = 0;
149
150 let mut id3_header = [0u8; 10];
152 if reader.read_exact(&mut id3_header).is_ok() && &id3_header[0..3] == b"ID3" {
153 let flags = id3_header[5];
154 let sync_safe_len: u32 = ((id3_header[6] & 0x7F) as u32) << 21
155 | ((id3_header[7] & 0x7F) as u32) << 14
156 | ((id3_header[8] & 0x7F) as u32) << 7
157 | ((id3_header[9] & 0x7F) as u32);
158
159 let has_footer = flags & (1 << 4) != 0;
160 if has_footer {
161 junk_bytes = sync_safe_len + 20;
162 } else {
163 junk_bytes = sync_safe_len + 10;
164
165 reader.seek(SeekFrom::Start(junk_bytes as u64))?;
167 let mut byte = [0u8; 1];
168 loop {
169 match reader.read_exact(&mut byte) {
170 Ok(()) if byte[0] == 0x00 => junk_bytes += 1,
171 _ => break,
172 }
173 }
174 }
175 }
176
177 reader.seek(SeekFrom::Start(junk_bytes as u64))?;
179 let mut window = [0u8; 4];
180 reader.read_exact(&mut window)?;
181
182 if &window == MAGIC_MAC_SPACE || &window == MAGIC_MACF {
184 return Ok(junk_bytes);
185 }
186
187 let mut scanned: u64 = 4;
189 let mut byte = [0u8; 1];
190 while scanned < FIND_DESCRIPTOR_MAX_SCAN {
191 if reader.read_exact(&mut byte).is_err() {
192 break;
193 }
194 window[0] = window[1];
196 window[1] = window[2];
197 window[2] = window[3];
198 window[3] = byte[0];
199 scanned += 1;
200
201 if &window == MAGIC_MAC_SPACE || &window == MAGIC_MACF {
202 let offset = junk_bytes as u64 + scanned - 4;
204 return Ok(offset as u32);
205 }
206 }
207
208 Err(ApeError::InvalidFormat(
209 "could not find APE descriptor magic",
210 ))
211}
212
213fn read_descriptor<R: Read>(reader: &mut R) -> ApeResult<ApeDescriptor> {
218 let mut magic = [0u8; 4];
219 reader.read_exact(&mut magic)?;
220
221 let version = read_u16_le(reader)?;
222 let padding = read_u16_le(reader)?;
223 let descriptor_bytes = read_u32_le(reader)?;
224 let header_bytes = read_u32_le(reader)?;
225 let seek_table_bytes = read_u32_le(reader)?;
226 let header_data_bytes = read_u32_le(reader)?;
227 let frame_data_bytes = read_u32_le(reader)?;
228 let frame_data_bytes_high = read_u32_le(reader)?;
229 let terminating_data_bytes = read_u32_le(reader)?;
230
231 let mut md5 = [0u8; 16];
232 reader.read_exact(&mut md5)?;
233
234 Ok(ApeDescriptor {
235 magic,
236 version,
237 padding,
238 descriptor_bytes,
239 header_bytes,
240 seek_table_bytes,
241 header_data_bytes,
242 frame_data_bytes,
243 frame_data_bytes_high,
244 terminating_data_bytes,
245 md5,
246 })
247}
248
249fn read_header<R: Read>(reader: &mut R) -> ApeResult<ApeHeader> {
254 let compression_level = read_u16_le(reader)?;
255 let format_flags = read_u16_le(reader)?;
256 let blocks_per_frame = read_u32_le(reader)?;
257 let final_frame_blocks = read_u32_le(reader)?;
258 let total_frames = read_u32_le(reader)?;
259 let bits_per_sample = read_u16_le(reader)?;
260 let channels = read_u16_le(reader)?;
261 let sample_rate = read_u32_le(reader)?;
262
263 Ok(ApeHeader {
264 compression_level,
265 format_flags,
266 blocks_per_frame,
267 final_frame_blocks,
268 total_frames,
269 bits_per_sample,
270 channels,
271 sample_rate,
272 })
273}
274
275fn read_old_header<R: Read + Seek>(
280 reader: &mut R,
281 magic: [u8; 4],
282 version: u16,
283) -> ApeResult<(ApeDescriptor, ApeHeader, Vec<u8>)> {
284 let compression_level = read_u16_le(reader)?;
287 let format_flags = read_u16_le(reader)?;
288 let channels = read_u16_le(reader)?;
289 let sample_rate = read_u32_le(reader)?;
290 let wav_header_bytes = read_u32_le(reader)?;
291 let terminating_bytes = read_u32_le(reader)?;
292 let total_frames = read_u32_le(reader)?;
293 let final_frame_blocks = read_u32_le(reader)?;
294
295 if total_frames == 0 {
296 return Err(ApeError::InvalidFormat(
297 "old format: total frames is 0 (non-finalized file)",
298 ));
299 }
300
301 let bits_per_sample = if format_flags & APE_FORMAT_FLAG_8_BIT != 0 {
303 8u16
304 } else if format_flags & APE_FORMAT_FLAG_24_BIT != 0 {
305 24u16
306 } else {
307 16u16
308 };
309
310 let blocks_per_frame: u32 = if version >= 3950 {
312 73728 * 4
313 } else if version >= 3900 || (version >= 3800 && compression_level == 4000) {
314 73728
315 } else {
316 9216
317 };
318
319 let mut _peak_level: u32 = 0;
321 if format_flags & APE_FORMAT_FLAG_HAS_PEAK_LEVEL != 0 {
322 _peak_level = read_u32_le(reader)?;
323 }
324
325 let seek_table_elements: u32 = if format_flags & APE_FORMAT_FLAG_HAS_SEEK_ELEMENTS != 0 {
326 read_u32_le(reader)?
327 } else {
328 total_frames
329 };
330
331 if seek_table_elements > 1_000_000 {
333 return Err(ApeError::InvalidFormat("seek table too large"));
334 }
335
336 let mut wav_header_data = Vec::new();
338 if format_flags & APE_FORMAT_FLAG_CREATE_WAV_HEADER == 0 && wav_header_bytes > 0 {
339 if (wav_header_bytes as u64) > APE_WAV_HEADER_OR_FOOTER_MAXIMUM_BYTES {
340 return Err(ApeError::InvalidFormat(
341 "WAV header data exceeds 8 MB limit",
342 ));
343 }
344 wav_header_data.resize(wav_header_bytes as usize, 0);
345 reader.read_exact(&mut wav_header_data)?;
346 }
347
348 let seek_table_bytes = seek_table_elements * 4;
350 let mut seek_raw = vec![0u32; seek_table_elements as usize];
351 for entry in seek_raw.iter_mut() {
352 *entry = read_u32_le(reader)?;
353 }
354
355 if version <= 3800 {
357 reader.seek(SeekFrom::Current(seek_table_elements as i64))?;
359 }
360
361 let descriptor = ApeDescriptor {
363 magic,
364 version,
365 padding: 0,
366 descriptor_bytes: 0, header_bytes: APE_HEADER_OLD_BYTES,
368 seek_table_bytes,
369 header_data_bytes: wav_header_bytes,
370 frame_data_bytes: 0,
371 frame_data_bytes_high: 0,
372 terminating_data_bytes: terminating_bytes,
373 md5: [0u8; 16],
374 };
375
376 let header = ApeHeader {
377 compression_level,
378 format_flags,
379 blocks_per_frame,
380 final_frame_blocks,
381 total_frames,
382 bits_per_sample,
383 channels,
384 sample_rate,
385 };
386
387 Ok((descriptor, header, wav_header_data))
388}
389
390fn validate(descriptor: &ApeDescriptor, header: &ApeHeader, file_bytes: u64) -> ApeResult<()> {
395 if header.channels < APE_MINIMUM_CHANNELS || header.channels > APE_MAXIMUM_CHANNELS {
397 return Err(ApeError::InvalidFormat(
398 "channel count out of range (must be 1..=32)",
399 ));
400 }
401
402 if header.blocks_per_frame == 0 {
404 return Err(ApeError::InvalidFormat("blocks per frame is 0"));
405 }
406
407 if header.compression_level >= 5000 {
408 if header.blocks_per_frame > 10 * APE_ONE_MILLION {
409 return Err(ApeError::InvalidFormat(
410 "blocks per frame exceeds 10,000,000 for insane compression",
411 ));
412 }
413 } else if header.blocks_per_frame > APE_ONE_MILLION {
414 return Err(ApeError::InvalidFormat(
415 "blocks per frame exceeds 1,000,000",
416 ));
417 }
418
419 if header.final_frame_blocks > header.blocks_per_frame {
421 return Err(ApeError::InvalidFormat(
422 "final frame blocks exceeds blocks per frame",
423 ));
424 }
425
426 let seek_table_elements = descriptor.seek_table_bytes / 4;
428 if file_bytes > 0 && (seek_table_elements as u64) > file_bytes / 4 {
429 return Err(ApeError::InvalidFormat(
430 "seek table elements exceed file size / 4",
431 ));
432 }
433
434 if (descriptor.header_data_bytes as u64) > APE_WAV_HEADER_OR_FOOTER_MAXIMUM_BYTES {
436 return Err(ApeError::InvalidFormat(
437 "WAV header data exceeds 8 MB limit",
438 ));
439 }
440
441 Ok(())
442}
443
444pub fn parse<R: Read + Seek>(reader: &mut R) -> ApeResult<ApeFileInfo> {
452 let file_bytes = reader.seek(SeekFrom::End(0))?;
454
455 let junk_header_bytes = find_descriptor(reader)?;
457
458 reader.seek(SeekFrom::Start(junk_header_bytes as u64))?;
460
461 let mut peek_buf = [0u8; 6];
463 reader.read_exact(&mut peek_buf)?;
464 let magic: [u8; 4] = [peek_buf[0], peek_buf[1], peek_buf[2], peek_buf[3]];
465 let version = u16::from_le_bytes([peek_buf[4], peek_buf[5]]);
466
467 if version < 3980 {
468 let (descriptor, header, wav_header_data) = read_old_header(reader, magic, version)?;
470
471 let total_blocks: i64 = if header.total_frames == 0 {
473 0
474 } else {
475 (header.total_frames as i64 - 1) * header.blocks_per_frame as i64
476 + header.final_frame_blocks as i64
477 };
478
479 let bytes_per_sample = header.bits_per_sample / 8;
480 let block_align = (bytes_per_sample as u32 * header.channels as u32) as u16;
481 let wav_data_bytes = total_blocks.saturating_mul(block_align as i64);
482 let length_ms = if header.sample_rate > 0 {
483 total_blocks.saturating_mul(1000) / header.sample_rate as i64
484 } else {
485 0
486 };
487
488 let ape_frame_data_bytes: u64 =
489 (descriptor.frame_data_bytes_high as u64) << 32 | descriptor.frame_data_bytes as u64;
490
491 let ape_total_bytes = file_bytes as i64;
492 let average_bitrate = if length_ms > 0 {
493 ape_total_bytes.saturating_mul(8) / length_ms
494 } else {
495 0
496 };
497 let decompressed_bitrate = if header.sample_rate > 0 {
498 (block_align as i64)
499 .saturating_mul(header.sample_rate as i64)
500 .saturating_mul(8)
501 / 1000
502 } else {
503 0
504 };
505
506 let seek_table_elements = (descriptor.seek_table_bytes / 4) as i32;
507
508 let mut seek_offset = junk_header_bytes as u64 + APE_HEADER_OLD_BYTES as u64;
525 if header.format_flags & APE_FORMAT_FLAG_HAS_PEAK_LEVEL != 0 {
526 seek_offset += 4;
527 }
528 if header.format_flags & APE_FORMAT_FLAG_HAS_SEEK_ELEMENTS != 0 {
529 seek_offset += 4;
530 }
531 if header.format_flags & APE_FORMAT_FLAG_CREATE_WAV_HEADER == 0 {
532 seek_offset += descriptor.header_data_bytes as u64;
533 }
534
535 reader.seek(SeekFrom::Start(seek_offset))?;
536 let n_seek = seek_table_elements as usize;
537 let mut seek_raw = vec![0u32; n_seek];
538 for entry in seek_raw.iter_mut() {
539 *entry = read_u32_le(reader)?;
540 }
541 let seek_table = convert_32bit_seek_table(&seek_raw);
542
543 validate(&descriptor, &header, file_bytes)?;
544
545 return Ok(ApeFileInfo {
546 descriptor,
547 header,
548 seek_table,
549 junk_header_bytes,
550 wav_header_data,
551 total_blocks,
552 block_align,
553 bytes_per_sample,
554 wav_data_bytes,
555 length_ms,
556 average_bitrate,
557 decompressed_bitrate,
558 seek_table_elements,
559 ape_frame_data_bytes,
560 terminating_data_bytes: 0,
561 file_bytes,
562 });
563 }
564
565 reader.seek(SeekFrom::Start(junk_header_bytes as u64))?;
568
569 let descriptor = read_descriptor(reader)?;
571
572 if descriptor.descriptor_bytes > APE_DESCRIPTOR_BYTES {
574 reader.seek(SeekFrom::Current(
575 (descriptor.descriptor_bytes - APE_DESCRIPTOR_BYTES) as i64,
576 ))?;
577 }
578
579 let header = read_header(reader)?;
581
582 if descriptor.header_bytes > APE_HEADER_BYTES {
584 reader.seek(SeekFrom::Current(
585 (descriptor.header_bytes - APE_HEADER_BYTES) as i64,
586 ))?;
587 }
588
589 let seek_table_elements = (descriptor.seek_table_bytes / 4) as i32;
591 if seek_table_elements < 0 || seek_table_elements > 1_000_000 {
592 return Err(ApeError::InvalidFormat("seek table too large"));
593 }
594 if file_bytes > 0 && (seek_table_elements as u64) > file_bytes / 4 {
595 return Err(ApeError::InvalidFormat(
596 "seek table elements exceed file size",
597 ));
598 }
599 let mut seek_raw = vec![0u32; seek_table_elements as usize];
600 for entry in seek_raw.iter_mut() {
601 *entry = read_u32_le(reader)?;
602 }
603 let seek_table = convert_32bit_seek_table(&seek_raw);
604
605 let mut wav_header_data = Vec::new();
607 if descriptor.header_data_bytes > 0 {
608 if (descriptor.header_data_bytes as u64) > APE_WAV_HEADER_OR_FOOTER_MAXIMUM_BYTES {
609 return Err(ApeError::InvalidFormat(
610 "WAV header data exceeds 8 MB limit",
611 ));
612 }
613 wav_header_data.resize(descriptor.header_data_bytes as usize, 0);
614 reader.read_exact(&mut wav_header_data)?;
615 }
616
617 validate(&descriptor, &header, file_bytes)?;
619
620 let total_blocks: i64 = if header.total_frames == 0 {
622 0
623 } else {
624 (header.total_frames as i64 - 1) * header.blocks_per_frame as i64
625 + header.final_frame_blocks as i64
626 };
627
628 let bytes_per_sample = header.bits_per_sample / 8;
629 let block_align = (bytes_per_sample as u32 * header.channels as u32) as u16;
630 let wav_data_bytes = total_blocks.saturating_mul(block_align as i64);
631
632 let length_ms = if header.sample_rate > 0 {
633 total_blocks.saturating_mul(1000) / header.sample_rate as i64
634 } else {
635 0
636 };
637
638 let ape_frame_data_bytes: u64 =
639 (descriptor.frame_data_bytes_high as u64) << 32 | descriptor.frame_data_bytes as u64;
640
641 let ape_total_bytes = file_bytes as i64;
642 let average_bitrate = if length_ms > 0 {
643 ape_total_bytes.saturating_mul(8) / length_ms
644 } else {
645 0
646 };
647
648 let decompressed_bitrate = if header.sample_rate > 0 {
649 (block_align as i64)
650 .saturating_mul(header.sample_rate as i64)
651 .saturating_mul(8)
652 / 1000
653 } else {
654 0
655 };
656
657 Ok(ApeFileInfo {
658 descriptor,
659 header,
660 seek_table,
661 junk_header_bytes,
662 wav_header_data,
663 total_blocks,
664 block_align,
665 bytes_per_sample,
666 wav_data_bytes,
667 length_ms,
668 average_bitrate,
669 decompressed_bitrate,
670 seek_table_elements,
671 ape_frame_data_bytes,
672 terminating_data_bytes: 0,
673 file_bytes,
674 })
675}
676
677impl ApeFileInfo {
682 pub fn frame_block_count(&self, frame_idx: u32) -> u32 {
687 if self.header.total_frames == 0 {
688 return 0;
689 }
690 if frame_idx == self.header.total_frames - 1 {
691 self.header.final_frame_blocks
692 } else {
693 self.header.blocks_per_frame
694 }
695 }
696
697 pub fn frame_byte_count(&self, frame_idx: u32) -> u64 {
703 if self.header.total_frames == 0 {
704 return 0;
705 }
706 if frame_idx < self.header.total_frames - 1 {
707 self.seek_byte(frame_idx + 1) - self.seek_byte(frame_idx)
708 } else {
709 let end = self
714 .file_bytes
715 .saturating_sub(self.descriptor.terminating_data_bytes as u64);
716 let start = self.seek_byte(frame_idx);
717 if end > start {
718 end - start
719 } else {
720 0
721 }
722 }
723 }
724
725 pub fn seek_byte(&self, frame_idx: u32) -> u64 {
728 let idx = frame_idx as usize;
729 if idx < self.seek_table.len() {
730 self.seek_table[idx] + self.junk_header_bytes as u64
731 } else {
732 0
733 }
734 }
735}
736
737#[cfg(test)]
742mod tests {
743 use super::*;
744 use std::fs::File;
745 use std::path::PathBuf;
746
747 fn parse_test_file(name: &str) -> ApeFileInfo {
748 let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
749 .join("tests/fixtures/ape")
750 .join(name);
751 let mut file = File::open(&path).unwrap_or_else(|e| {
752 panic!("failed to open {}: {}", path.display(), e);
753 });
754 parse(&mut file).unwrap_or_else(|e| {
755 panic!("failed to parse {}: {}", path.display(), e);
756 })
757 }
758
759 #[test]
760 fn test_sine_16s_c2000_descriptor() {
761 let info = parse_test_file("sine_16s_c2000.ape");
762 assert_eq!(&info.descriptor.magic, b"MAC ");
763 assert!(info.descriptor.version >= 3980, "expected current format");
764 assert_eq!(info.descriptor.descriptor_bytes, APE_DESCRIPTOR_BYTES);
765 assert_eq!(info.descriptor.header_bytes, APE_HEADER_BYTES);
766 }
767
768 #[test]
769 fn test_sine_16s_c2000_header() {
770 let info = parse_test_file("sine_16s_c2000.ape");
771 assert_eq!(info.header.compression_level, 2000);
772 assert_eq!(info.header.bits_per_sample, 16);
773 assert_eq!(info.header.channels, 2);
774 assert_eq!(info.header.sample_rate, 44100);
775 }
776
777 #[test]
778 fn test_sine_16m_c2000_mono() {
779 let info = parse_test_file("sine_16m_c2000.ape");
780 assert_eq!(info.header.channels, 1);
781 assert_eq!(info.header.bits_per_sample, 16);
782 assert_eq!(info.header.sample_rate, 44100);
783 assert_eq!(info.header.compression_level, 2000);
784 assert_eq!(info.block_align, 2); }
786
787 #[test]
788 fn test_sine_8s_c2000() {
789 let info = parse_test_file("sine_8s_c2000.ape");
790 assert_eq!(info.header.bits_per_sample, 8);
791 assert_eq!(info.header.channels, 2);
792 assert_eq!(info.bytes_per_sample, 1);
793 assert_eq!(info.block_align, 2); }
795
796 #[test]
797 fn test_sine_24s_c2000() {
798 let info = parse_test_file("sine_24s_c2000.ape");
799 assert_eq!(info.header.bits_per_sample, 24);
800 assert_eq!(info.header.channels, 2);
801 assert_eq!(info.bytes_per_sample, 3);
802 assert_eq!(info.block_align, 6); }
804
805 #[test]
806 fn test_sine_32s_c2000() {
807 let info = parse_test_file("sine_32s_c2000.ape");
808 assert_eq!(info.header.bits_per_sample, 32);
809 assert_eq!(info.header.channels, 2);
810 assert_eq!(info.bytes_per_sample, 4);
811 assert_eq!(info.block_align, 8); }
813
814 #[test]
815 fn test_compression_levels() {
816 let c1000 = parse_test_file("sine_16s_c1000.ape");
817 assert_eq!(c1000.header.compression_level, 1000);
818
819 let c2000 = parse_test_file("sine_16s_c2000.ape");
820 assert_eq!(c2000.header.compression_level, 2000);
821
822 let c3000 = parse_test_file("sine_16s_c3000.ape");
823 assert_eq!(c3000.header.compression_level, 3000);
824
825 let c4000 = parse_test_file("sine_16s_c4000.ape");
826 assert_eq!(c4000.header.compression_level, 4000);
827
828 let c5000 = parse_test_file("sine_16s_c5000.ape");
829 assert_eq!(c5000.header.compression_level, 5000);
830 }
831
832 #[test]
833 fn test_derived_values() {
834 let info = parse_test_file("sine_16s_c2000.ape");
835
836 assert_eq!(info.block_align, 4);
838 assert_eq!(info.bytes_per_sample, 2);
839
840 if info.header.total_frames > 0 {
842 let expected = (info.header.total_frames as i64 - 1)
843 * info.header.blocks_per_frame as i64
844 + info.header.final_frame_blocks as i64;
845 assert_eq!(info.total_blocks, expected);
846 }
847
848 assert_eq!(
850 info.wav_data_bytes,
851 info.total_blocks * info.block_align as i64
852 );
853
854 assert!(info.length_ms > 0);
856
857 assert!(info.average_bitrate > 0);
859 assert!(info.decompressed_bitrate > 0);
860 }
861
862 #[test]
863 fn test_seek_table_populated() {
864 let info = parse_test_file("sine_16s_c2000.ape");
865 assert_eq!(info.seek_table.len(), info.header.total_frames as usize);
866 if !info.seek_table.is_empty() {
868 assert!(info.seek_table[0] > 0);
869 }
870 for w in info.seek_table.windows(2) {
872 assert!(
873 w[1] >= w[0],
874 "seek table not monotonic: {} < {}",
875 w[1],
876 w[0]
877 );
878 }
879 }
880
881 #[test]
882 fn test_frame_block_count() {
883 let info = parse_test_file("sine_16s_c2000.ape");
884 if info.header.total_frames > 1 {
885 assert_eq!(info.frame_block_count(0), info.header.blocks_per_frame);
886 assert_eq!(
887 info.frame_block_count(info.header.total_frames - 1),
888 info.header.final_frame_blocks
889 );
890 }
891 }
892
893 #[test]
894 fn test_seek_byte_includes_junk() {
895 let info = parse_test_file("sine_16s_c2000.ape");
896 if !info.seek_table.is_empty() {
897 let raw_first = info.seek_table[0];
898 let seek_first = info.seek_byte(0);
899 assert_eq!(seek_first, raw_first + info.junk_header_bytes as u64);
900 }
901 }
902
903 #[test]
904 fn test_frame_byte_count_positive() {
905 let info = parse_test_file("sine_16s_c2000.ape");
906 for i in 0..info.header.total_frames {
907 let bc = info.frame_byte_count(i);
908 assert!(bc > 0, "frame {} byte count is 0", i);
909 }
910 }
911
912 #[test]
913 fn test_multiframe_file() {
914 let info = parse_test_file("multiframe_16s_c2000.ape");
915 assert!(
917 info.header.total_frames > 1,
918 "expected multiple frames, got {}",
919 info.header.total_frames
920 );
921 assert_eq!(info.header.channels, 2);
922 assert_eq!(info.header.bits_per_sample, 16);
923 }
924
925 #[test]
926 fn test_silence_file() {
927 let info = parse_test_file("silence_16s_c2000.ape");
928 assert_eq!(info.header.channels, 2);
929 assert_eq!(info.header.bits_per_sample, 16);
930 assert!(info.total_blocks > 0);
931 }
932
933 #[test]
934 fn test_short_file() {
935 let info = parse_test_file("short_16s_c2000.ape");
936 assert_eq!(info.header.channels, 2);
937 assert_eq!(info.header.bits_per_sample, 16);
938 assert!(info.total_blocks > 0);
939 }
940
941 #[test]
942 fn test_all_files_parseable() {
943 let test_files = [
944 "dc_offset_16s_c2000.ape",
945 "identical_16s_c2000.ape",
946 "impulse_16s_c2000.ape",
947 "left_only_16s_c2000.ape",
948 "multiframe_16s_c2000.ape",
949 "noise_16s_c2000.ape",
950 "short_16s_c2000.ape",
951 "silence_16s_c2000.ape",
952 "sine_16m_c2000.ape",
953 "sine_16s_c1000.ape",
954 "sine_16s_c2000.ape",
955 "sine_16s_c3000.ape",
956 "sine_16s_c4000.ape",
957 "sine_16s_c5000.ape",
958 "sine_24s_c2000.ape",
959 "sine_32s_c2000.ape",
960 "sine_8s_c2000.ape",
961 ];
962 for name in &test_files {
963 let info = parse_test_file(name);
964 assert!(info.header.total_frames > 0, "{}: no frames", name);
965 assert!(info.total_blocks > 0, "{}: no blocks", name);
966 assert_eq!(
967 info.seek_table.len(),
968 info.header.total_frames as usize,
969 "{}: seek table length mismatch",
970 name
971 );
972 }
973 }
974
975 #[test]
976 fn test_junk_header_bytes_zero_for_clean_files() {
977 let info = parse_test_file("sine_16s_c2000.ape");
979 assert_eq!(info.junk_header_bytes, 0);
980 }
981}