1mod id3_display;
67pub mod model;
68
69use crate::model::*;
70use id3::Tag;
71use std::collections::HashMap;
72use std::convert::TryFrom;
73use std::fmt;
74use std::fs::File;
75use std::io;
76use std::io::Read;
77use std::io::SeekFrom;
78use std::io::prelude::*;
79use std::path::Path;
80use std::u64;
81
82#[derive(Debug)]
83pub struct DffFile {
84 file: File,
85 frm_chunk: FormDsdChunk,
86 dsd_data_offset: u64,
87 dsd_audio_size: u64,
88}
89
90impl DffFile {
91 pub fn open(path: &Path) -> Result<DffFile, Error> {
101 let mut file = File::open(path)?;
102 let mut chunk_buf16 = [0u8; 16];
103
104 file.read_exact(&mut chunk_buf16)?;
106 let mut frm_chunk = FormDsdChunk::try_from(chunk_buf16)?;
107
108 file.read_exact(&mut chunk_buf16)?;
110 let fver_chunk = FormatVersionChunk::try_from(chunk_buf16)?;
111 frm_chunk
112 .chunk
113 .local_chunks
114 .insert(FVER_LABEL, LocalChunk::FormatVersion(fver_chunk));
115
116 let mut hdr_buf = scan_until(&mut file, PROP_LABEL, Some(DSD_LABEL))?;
118 chunk_buf16[0..12].copy_from_slice(&hdr_buf);
119 let mut prop_buf4 = [0u8; 4];
121 file.read_exact(&mut prop_buf4)?;
122 chunk_buf16[12..16].copy_from_slice(&prop_buf4);
123
124 let pc = PropertyChunk::try_from(chunk_buf16)?;
125 frm_chunk
126 .chunk
127 .local_chunks
128 .insert(PROP_LABEL, LocalChunk::Property(pc));
129
130 let prop_data_size = match frm_chunk.chunk.local_chunks.get(&PROP_LABEL) {
131 Some(LocalChunk::Property(prop)) => prop.chunk.header.ck_data_size,
132 _ => return Err(Error::PropChunkHeader),
133 };
134 let Some(LocalChunk::Property(prop_chunk_inner)) =
135 frm_chunk.chunk.local_chunks.get_mut(&PROP_LABEL)
136 else {
137 return Err(Error::PropChunkHeader);
138 };
139 let prop_data_offset = file.stream_position()? - 4;
140
141 let mut try_insert_prop = |hdr_buf: &[u8], file: &mut File| -> Result<(), Error> {
142 let ck_id = u32_from_byte_buffer(&hdr_buf, 0);
143 let ck_data_size = u64_from_byte_buffer(&hdr_buf, 4);
144 if ![FS_LABEL, CHNL_LABEL, COMP_LABEL, ABS_TIME_LABEL, LS_CONF_LABEL]
145 .contains(&ck_id)
146 {
147 skip_and_pad(file, ck_data_size)?;
148 return Ok(());
149 }
150 let mut data_buf = vec![0u8; ck_data_size as usize];
151 file.read_exact(&mut data_buf)?;
152 let mut buf = Vec::with_capacity(CHUNK_HEADER_SIZE as usize + data_buf.len());
153 buf.extend_from_slice(&hdr_buf);
154 buf.extend_from_slice(&data_buf);
155
156 match ck_id {
157 FS_LABEL => {
158 let fs_chunk = SampleRateChunk::try_from(buf.as_slice())?;
159 prop_chunk_inner
160 .chunk
161 .local_chunks
162 .insert(FS_LABEL, LocalChunk::SampleRate(fs_chunk));
163 }
164 CHNL_LABEL => {
165 let chnl_chunk = ChannelsChunk::try_from(buf.as_slice())?;
166 prop_chunk_inner
167 .chunk
168 .local_chunks
169 .insert(CHNL_LABEL, LocalChunk::Channels(chnl_chunk));
170 }
171 COMP_LABEL => {
172 let cmpr_chunk = CompressionTypeChunk::try_from(buf.as_slice())?;
173 prop_chunk_inner
174 .chunk
175 .local_chunks
176 .insert(COMP_LABEL, LocalChunk::CompressionType(cmpr_chunk));
177 }
178 ABS_TIME_LABEL => {
179 let abs_chunk = AbsoluteStartTimeChunk::try_from(buf.as_slice())?;
180 prop_chunk_inner
181 .chunk
182 .local_chunks
183 .insert(ABS_TIME_LABEL, LocalChunk::AbsoluteStartTime(abs_chunk));
184 }
185 LS_CONF_LABEL => {
186 let lsco_chunk = LoudspeakerConfigChunk::try_from(buf.as_slice())?;
187 prop_chunk_inner
188 .chunk
189 .local_chunks
190 .insert(LS_CONF_LABEL, LocalChunk::LoudspeakerConfig(lsco_chunk));
191 }
192 _ => {}
193 }
194 Ok(())
195 };
196
197 while file.stream_position()? < prop_data_offset + prop_data_size as u64
199 && file.read_exact(&mut hdr_buf).is_ok()
200 {
201 let hdr_buf = hdr_buf.clone();
202 let mut prop_reader_file = file.try_clone()?;
203
204 try_insert_prop(&hdr_buf, &mut prop_reader_file)?;
205 }
206
207 hdr_buf = scan_until(&mut file, DSD_LABEL, None)?;
208 let dsd_data_offset = file.stream_position()?;
209 let dsd_chunk = DsdChunk::try_from(hdr_buf)?;
210 let dsd_audio_size = dsd_chunk.chunk.header.ck_data_size;
211 frm_chunk
212 .chunk
213 .local_chunks
214 .insert(DSD_LABEL, LocalChunk::Dsd(dsd_chunk));
215
216 skip_and_pad(&mut file, dsd_audio_size)?;
218
219 let mut dff_file = DffFile {
221 file,
222 frm_chunk,
223 dsd_data_offset,
224 dsd_audio_size,
225 };
226
227 hdr_buf = match scan_until(&mut dff_file.file, ID3_LABEL, None) {
229 Ok(buf) => buf,
230 Err(_e) => {
231 return Ok(dff_file);
232 }
233 };
234
235 match dff_file.add_id3_chunk(hdr_buf) {
236 Ok(()) => return Ok(dff_file),
237 Err(e) => return Err(Error::Id3Error(e, dff_file)),
238 };
239 }
240
241 #[must_use]
243 pub fn file(&self) -> &File {
244 &self.file
245 }
246
247 pub fn get_dsd_data_offset(&self) -> u64 {
249 self.dsd_data_offset
250 }
251
252 pub fn get_audio_length(&self) -> u64 {
254 self.dsd_audio_size
255 }
256
257 pub fn get_num_channels(&self) -> Result<usize, Error> {
259 let prop_chunk = match self.frm_chunk.chunk.local_chunks.get(&PROP_LABEL) {
260 Some(LocalChunk::Property(prop)) => prop,
261 _ => return Err(Error::PropChunkHeader),
262 };
263 match prop_chunk.chunk.local_chunks.get(&CHNL_LABEL) {
264 Some(LocalChunk::Channels(chnl)) => Ok(chnl.num_channels as usize),
265 _ => return Err(Error::ChnlNumber),
266 }
267 }
268
269 pub fn get_sample_rate(&self) -> Result<u32, Error> {
271 let prop_chunk = match self.frm_chunk.chunk.local_chunks.get(&PROP_LABEL) {
272 Some(LocalChunk::Property(prop)) => prop,
273 _ => return Err(Error::PropChunkHeader),
274 };
275 match prop_chunk.chunk.local_chunks.get(&FS_LABEL) {
276 Some(LocalChunk::SampleRate(fs)) => Ok(fs.sample_rate),
277 _ => return Err(Error::FsChunkHeader),
278 }
279 }
280
281 pub fn get_form_chunk_size(&self) -> u64 {
283 self.frm_chunk.chunk.header.ck_data_size + CHUNK_HEADER_SIZE
284 }
285
286 pub fn get_file_size(&self) -> Result<u64, io::Error> {
288 let metadata = self.file.metadata()?;
289 Ok(metadata.len())
290 }
291
292 pub fn id3_tag(&self) -> &Option<Tag> {
294 match self.frm_chunk.chunk.local_chunks.get(&ID3_LABEL) {
295 Some(LocalChunk::Id3(id3_chunk)) => &id3_chunk.tag,
296 _ => &None,
297 }
298 }
299
300 pub fn get_dff_version(&self) -> Result<u32, Error> {
302 let fver_chunk = match self.frm_chunk.chunk.local_chunks.get(&FVER_LABEL) {
303 Some(LocalChunk::FormatVersion(fver)) => fver,
304 _ => return Err(Error::FverChunkHeader),
305 };
306 Ok(fver_chunk.format_version)
307 }
308
309 fn add_id3_chunk(
311 &mut self,
312 hdr_buf: [u8; CHUNK_HEADER_SIZE as usize],
313 ) -> Result<(), id3::Error> {
314 let ck_id = u32_from_byte_buffer(&hdr_buf, 0);
315 let ck_data_size = u64_from_byte_buffer(&hdr_buf, std::mem::size_of::<ID>());
316 let mut data = vec![0u8; ck_data_size as usize];
317 let mut tag_read_err: Option<id3::Error> = None;
318
319 if let Err(_e) = self.file.read_exact(&mut data) {
320 tag_read_err = Some(id3::Error::new(
321 id3::ErrorKind::Io(std::io::Error::new(
322 std::io::ErrorKind::Other,
323 "Failed to read complete ID3 chunk data",
324 )),
325 "Couldn't fill buffer",
326 ));
327 }
328
329 let mut cursor = std::io::Cursor::new(&data);
330 let tag = match id3::Tag::read_from2(&mut cursor) {
331 Ok(t) => Some(t),
332 Err(e) => {
333 let partial_tag = e.partial_tag.clone();
334 tag_read_err = Some(e);
335 partial_tag
336 }
337 };
338
339 if tag.is_some() {
340 let id3_chunk = Id3Chunk {
341 chunk: Chunk::new(ChunkHeader {
342 ck_id,
343 ck_data_size,
344 }),
345 tag,
346 };
347
348 self.frm_chunk
349 .chunk
350 .local_chunks
351 .insert(ID3_LABEL, LocalChunk::Id3(id3_chunk));
352 }
353
354 if tag_read_err.is_some() {
355 return Err(tag_read_err.unwrap());
356 }
357 Ok(())
358 }
359}
360
361impl fmt::Display for DffFile {
362 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
363 write!(
364 f,
365 "File size: {} bytes\nForm Chunk Size: {} bytes\nDSD Audio Offset: {} bytes\nAudio Length: {} bytes\nChannels: {}\nSample Rate: {} Hz\nDFF Version: {}\nID3 Tag:\n{}",
366 self.get_file_size().unwrap_or(0),
367 self.get_form_chunk_size(),
368 self.get_dsd_data_offset(),
369 self.get_audio_length(),
370 self.get_num_channels().unwrap_or(0),
371 self.get_sample_rate().unwrap_or(0),
372 self.get_dff_version().unwrap_or(0),
373 if let Some(tag) = &self.id3_tag() {
374 id3_display::id3_tag_to_string(tag)
375 } else {
376 String::from("No ID3 tag present.")
377 }
378 )
379 }
380}
381
382fn skip_and_pad(file: &mut File, size: u64) -> Result<(), io::Error> {
384 file.seek(SeekFrom::Current(
385 if size & 1 == 1 { size + 1 } else { size } as i64,
386 ))?;
387 Ok(())
388}
389
390fn scan_until(
393 file: &mut File,
394 want_label: u32,
395 abort_label: Option<u32>,
396) -> Result<[u8; CHUNK_HEADER_SIZE as usize], Error> {
397 loop {
398 let mut hdr = [0u8; CHUNK_HEADER_SIZE as usize];
399 match file.read_exact(&mut hdr) {
400 Ok(_) => {}
401 Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => {
402 return Err(Error::Eof);
403 }
404 Err(e) => return Err(Error::IoError(e)),
405 }
406 let ck_id = u32_from_byte_buffer(&hdr, 0);
407 let ck_data_size = u64_from_byte_buffer(&hdr, std::mem::size_of::<ID>());
408 if ck_id == want_label {
409 return Ok(hdr);
410 } else if Some(ck_id) == abort_label {
411 return Err(Error::PrematureTagFound(
412 String::from_utf8_lossy(&ck_id.to_be_bytes()).to_string(),
413 ));
414 } else {
415 skip_and_pad(file, ck_data_size)?;
417 }
418 }
419}
420
421fn u64_from_byte_buffer(buffer: &[u8], index: usize) -> u64 {
424 let mut byte_array: [u8; 8] = [0; 8];
425 byte_array.copy_from_slice(&buffer[index..index + 8]);
426
427 u64::from_be_bytes(byte_array)
428}
429
430fn u32_from_byte_buffer(buffer: &[u8], index: usize) -> u32 {
433 let mut byte_array: [u8; 4] = [0; 4];
434 byte_array.copy_from_slice(&buffer[index..index + 4]);
435
436 u32::from_be_bytes(byte_array)
437}
438
439fn u16_from_byte_buffer(buffer: &[u8], index: usize) -> u16 {
442 let mut byte_array: [u8; 2] = [0; 2];
443 byte_array.copy_from_slice(&buffer[index..index + 2]);
444
445 u16::from_be_bytes(byte_array)
446}
447
448impl Chunk {
449 pub fn new(header: ChunkHeader) -> Chunk {
450 Chunk {
451 header,
452 local_chunks: HashMap::new(),
453 }
454 }
455}
456
457impl FormDsdChunk {
458 #[inline]
459 pub fn is_valid(&self) -> bool {
460 self.chunk.header.ck_id == u32::from_be_bytes(*b"FRM8") && self.form_type == DSD_LABEL
461 }
462}
463
464impl TryFrom<[u8; 16]> for FormDsdChunk {
466 type Error = Error;
467
468 fn try_from(buf: [u8; 16]) -> Result<Self, Self::Error> {
469 let ck_id = u32_from_byte_buffer(&buf, 0);
470 let ck_data_size = u64_from_byte_buffer(&buf, 4);
471 let form_type = u32_from_byte_buffer(&buf, 12);
472 let header = ChunkHeader {
473 ck_id,
474 ck_data_size,
475 };
476 let chunk = FormDsdChunk {
477 chunk: Chunk::new(header),
478 form_type,
479 };
480
481 if !chunk.is_valid() {
482 return Err(Error::FormChunkHeader);
483 }
484 if chunk.form_type != DSD_LABEL {
485 return Err(Error::FormTypeMismatch);
486 }
487
488 Ok(chunk)
489 }
490}
491
492impl TryFrom<[u8; 16]> for FormatVersionChunk {
493 type Error = Error;
494
495 fn try_from(buf: [u8; 16]) -> Result<Self, Self::Error> {
496 let ck_id = u32_from_byte_buffer(&buf, 0);
497 let ck_data_size = u64_from_byte_buffer(&buf, 4);
498 let version = u32_from_byte_buffer(&buf, 12);
499 let header = ChunkHeader {
500 ck_id,
501 ck_data_size,
502 };
503 let chunk = FormatVersionChunk {
504 chunk: Chunk::new(header),
505 format_version: version,
506 };
507
508 if chunk.chunk.header.ck_id != FVER_LABEL {
509 return Err(Error::FverChunkHeader);
510 }
511 if chunk.chunk.header.ck_data_size != 4 {
512 return Err(Error::FverChunkSize);
513 }
514
515 Ok(chunk)
516 }
517}
518
519impl TryFrom<[u8; 16]> for PropertyChunk {
520 type Error = Error;
521 fn try_from(buf: [u8; 16]) -> Result<Self, Self::Error> {
522 let ck_id = u32_from_byte_buffer(&buf, 0);
523 let ck_data_size = u64_from_byte_buffer(&buf, 4);
524 if ck_data_size < 4 {
526 return Err(Error::ChnlChunkSize); }
528 let property_type = u32_from_byte_buffer(&buf, 12);
529
530 let header = ChunkHeader {
531 ck_id,
532 ck_data_size,
533 };
534 let chunk = PropertyChunk {
535 chunk: Chunk::new(header),
536 property_type,
537 };
538
539 if chunk.chunk.header.ck_id != PROP_LABEL {
540 return Err(Error::PropChunkHeader);
541 }
542 if chunk.property_type != SND_LABEL {
543 return Err(Error::PropChunkType);
544 }
545 Ok(chunk)
546 }
547}
548
549impl TryFrom<&[u8]> for SampleRateChunk {
550 type Error = Error;
551 fn try_from(buf: &[u8]) -> Result<Self, Self::Error> {
552 let ck_id = u32_from_byte_buffer(&buf, 0);
553 let ck_data_size = u64_from_byte_buffer(&buf, 4);
554 let sample_rate = u32_from_byte_buffer(&buf, 12);
555
556 let header = ChunkHeader {
557 ck_id,
558 ck_data_size,
559 };
560 let chunk = SampleRateChunk {
561 chunk: Chunk::new(header),
562 sample_rate,
563 };
564
565 if chunk.chunk.header.ck_id != FS_LABEL {
566 return Err(Error::FsChunkHeader);
567 }
568 if chunk.chunk.header.ck_data_size != 4 {
569 return Err(Error::FsChunkSize);
570 }
571 Ok(chunk)
572 }
573}
574
575impl TryFrom<&[u8]> for ChannelsChunk {
576 type Error = Error;
577 fn try_from(buf: &[u8]) -> Result<Self, Self::Error> {
578 if buf.len() < 14 {
579 return Err(Error::ChnlChunkSize);
580 }
581
582 let ck_id = u32_from_byte_buffer(&buf, 0);
583 let ck_data_size = u64_from_byte_buffer(&buf, 4);
584
585 if buf.len() as u64 != 12 + ck_data_size {
587 return Err(Error::ChnlChunkSize);
588 }
589
590 let num_channels = u16_from_byte_buffer(&buf, 12);
591
592 if num_channels != 1 && num_channels != 2 {
593 return Err(Error::ChnlNumber);
594 }
595
596 let expected_ids_bytes = (num_channels as usize) * 4;
598 if 14 + expected_ids_bytes != buf.len() {
599 return Err(Error::ChnlChunkSize);
600 }
601
602 let mut channel_ids = Vec::with_capacity(num_channels as usize);
603 let mut idx = 14;
604 for _ in 0..num_channels {
605 let mut a = [0u8; 4];
606 a.copy_from_slice(&buf[idx..idx + 4]);
607 channel_ids.push(u32::from_be_bytes(a));
608 idx += 4;
609 }
610
611 let header = ChunkHeader {
612 ck_id,
613 ck_data_size,
614 };
615
616 let chunk = ChannelsChunk {
617 chunk: Chunk::new(header),
618 num_channels,
619 ch_id: channel_ids,
620 };
621
622 if chunk.chunk.header.ck_id != CHNL_LABEL {
623 return Err(Error::ChnlChunkHeader);
624 }
625 if expected_ids_bytes != chunk.ch_id.len() * 4 {
626 return Err(Error::ChnlChunkSize);
627 }
628 Ok(chunk)
629 }
630}
631
632impl TryFrom<&[u8]> for CompressionTypeChunk {
633 type Error = Error;
634 fn try_from(buf: &[u8]) -> Result<Self, Self::Error> {
635 if buf.len() < 16 {
637 return Err(Error::CmprChunkSize);
638 }
639
640 let ck_id = u32_from_byte_buffer(&buf, 0);
642
643 let ck_data_size = u64_from_byte_buffer(&buf, 4);
644
645 if buf.len() as u64 != 12 + ck_data_size || ck_data_size < 4 {
646 return Err(Error::CmprChunkSize);
647 }
648
649 let compression_type = u32_from_byte_buffer(&buf, 12);
650
651 let name_bytes = if ck_data_size > 4 {
653 &buf[16..(12 + ck_data_size as usize)]
654 } else {
655 &[]
656 };
657
658 let compression_name = match std::str::from_utf8(name_bytes) {
659 Ok(inner_str) => inner_str.to_string(),
660 Err(_) => String::new(),
661 };
662
663 let chunk = CompressionTypeChunk {
664 chunk: Chunk::new(ChunkHeader {
665 ck_id,
666 ck_data_size,
667 }),
668 compression_type,
669 compression_name,
670 };
671
672 if chunk.chunk.header.ck_id != COMP_LABEL {
673 return Err(Error::CmprChunkHeader);
674 }
675 if chunk.compression_type != DSD_LABEL || chunk.compression_name == "DST Encoded" {
678 return Err(Error::CmprTypeMismatch);
679 }
680 Ok(chunk)
681 }
682}
683
684impl TryFrom<&[u8]> for AbsoluteStartTimeChunk {
685 type Error = Error;
686 fn try_from(buf: &[u8]) -> Result<Self, Self::Error> {
687 if buf.len() < 20 {
689 return Err(Error::AbssChunkSize);
690 }
691 let ck_id = u32_from_byte_buffer(&buf, 0);
693 let ck_data_size = u64_from_byte_buffer(&buf, 4);
695
696 if ck_data_size != 8 || buf.len() as u64 != 12 + ck_data_size {
697 return Err(Error::AbssChunkSize);
698 }
699
700 let hours = u16_from_byte_buffer(&buf, 12);
706 let minutes = buf[14];
707 let seconds = buf[15];
708 let samples = u32_from_byte_buffer(&buf, 16);
709
710 let chunk = AbsoluteStartTimeChunk {
711 chunk: Chunk::new(ChunkHeader {
712 ck_id,
713 ck_data_size,
714 }),
715 hours,
716 minutes,
717 seconds,
718 samples,
719 };
720
721 if chunk.chunk.header.ck_id != ABS_TIME_LABEL {
722 return Err(Error::AbssChunkHeader);
723 }
724 Ok(chunk)
725 }
726}
727
728impl TryFrom<&[u8]> for LoudspeakerConfigChunk {
729 type Error = Error;
730 fn try_from(buf: &[u8]) -> Result<Self, Self::Error> {
731 if buf.len() < 14 {
733 return Err(Error::LscoChunkSize);
734 }
735 let ck_id = u32_from_byte_buffer(&buf, 0);
736 if ck_id != LS_CONF_LABEL {
737 return Err(Error::LscoChunkHeader);
738 }
739 let ck_data_size = u64_from_byte_buffer(&buf, 4);
740
741 if buf.len() as u64 != 12 + ck_data_size {
742 return Err(Error::LscoChunkSize);
743 }
744
745 let ls_config = u16_from_byte_buffer(&buf, 12);
746
747 let chunk = LoudspeakerConfigChunk {
748 chunk: Chunk::new(ChunkHeader {
749 ck_id,
750 ck_data_size,
751 }),
752 ls_config,
753 };
754
755 if chunk.chunk.header.ck_data_size != 2 {
757 return Err(Error::LscoChunkSize);
758 }
759 Ok(chunk)
760 }
761}
762
763impl fmt::Display for DsdChunk {
764 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
765 let size = self.chunk.header.ck_data_size;
767 write!(f, "Audio Length = {} bytes", size)
768 }
769}
770
771impl TryFrom<[u8; CHUNK_HEADER_SIZE as usize]> for DsdChunk {
772 type Error = Error;
773
774 fn try_from(buf: [u8; CHUNK_HEADER_SIZE as usize]) -> Result<Self, Self::Error> {
775 let ck_id = {
776 let mut a = [0u8; 4];
777 a.copy_from_slice(&buf[0..4]);
778 u32::from_be_bytes(a)
779 };
780 if ck_id != DSD_LABEL {
781 return Err(Error::DsdChunkHeader);
782 }
783
784 let ck_data_size = u64_from_byte_buffer(&buf, 4);
785 if ck_data_size == 0 {
786 return Err(Error::DsdChunkSize);
787 }
788 Ok(DsdChunk {
789 chunk: Chunk::new(ChunkHeader {
790 ck_id,
791 ck_data_size,
792 }),
793 })
794 }
795}
796
797impl fmt::Display for Error {
798 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
799 match self {
800 Error::DsdChunkHeader => f.write_str("A DSD chunk must start with the bytes 'DSD '."),
801 Error::DsdChunkSize => f.write_str("A DSD chunk must not have size 0."),
802 Error::Id3Error(id3_error, dff_file) => {
803 write!(f, "Id3 error: {} in file: {}", id3_error, dff_file)
804 }
805 Error::IoError(io_error) => {
806 write!(f, "IO error: {}", io_error)
807 }
808 Error::PrematureTagFound(e) => {
809 write!(f, "Chunk {} was found before it should have been.", e)
810 }
811 Error::FormChunkHeader => f.write_str("FORM chunk must start with 'FRM8'."),
812 Error::FormTypeMismatch => f.write_str("FORM chunk form type must be 'DSD '."),
813 Error::FverChunkHeader => f.write_str("Format Version chunk must start with 'FVER'."),
814 Error::FverChunkSize => f.write_str("FVER chunk data size must be 4."),
815 Error::FverUnsupportedVersion => {
816 f.write_str("Unsupported format version in FVER chunk.")
817 }
818 Error::PropChunkHeader => f.write_str("Property chunk must start with 'PROP'."),
819 Error::PropChunkType => f.write_str("Property chunk type must be 'SND '."),
820 Error::FsChunkHeader => f.write_str("Sample rate chunk must start with 'FS '."),
821 Error::FsChunkSize => f.write_str("FS chunk size must be 4."),
822 Error::ChnlChunkHeader => f.write_str("Channels chunk must start with 'CHNL'."),
823 Error::ChnlChunkSize => f.write_str("CHNL chunk size does not match channel data."),
824 Error::ChnlNumber => f.write_str("CHNL number not found or is unsupported."),
825 Error::CmprChunkHeader => f.write_str("Compression type chunk must start with 'CMPR'."),
826 Error::CmprChunkSize => f.write_str("CMPR chunk size invalid or inconsistent."),
827 Error::AbssChunkHeader => {
828 f.write_str("Absolute start time chunk must start with 'ABSS'.")
829 }
830 Error::AbssChunkSize => f.write_str("ABSS chunk size invalid."),
831 Error::LscoChunkHeader => {
832 f.write_str("Loudspeaker config chunk must start with 'LSCO'.")
833 }
834 Error::LscoChunkSize => f.write_str("LSCO chunk size invalid or inconsistent."),
835 Error::CmprTypeMismatch => {
836 f.write_str("Compression type must be 'DSD '. DST not supported.")
837 }
838 Error::Eof => f.write_str("Unexpected end of file."),
839 }
840 }
841}
842
843impl std::error::Error for Error {
844 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
845 match self {
846 Error::IoError(io_error) => Some(io_error),
847 _ => None,
848 }
849 }
850}
851
852impl From<io::Error> for Error {
853 fn from(error: io::Error) -> Self {
854 Error::IoError(error)
855 }
856}
857
858#[cfg(test)]
859mod tests {
860 use id3::TagLike;
861
862 use super::*;
863
864 #[test]
865 fn read_file() {
866 let filename = "1kHz.dff";
867 let path = Path::new(filename);
868 let dff_file = DffFile::open(path).unwrap();
869
870 assert_eq!(dff_file.get_file_size().unwrap(), 36592);
871 assert_eq!(dff_file.get_form_chunk_size(), 71872);
872 assert_eq!(dff_file.get_dsd_data_offset(), 130);
873 assert_eq!(dff_file.get_audio_length(), 35280);
874 assert_eq!(dff_file.get_num_channels().unwrap(), 2);
875 assert_eq!(dff_file.get_sample_rate().unwrap(), 2822400);
876 assert_eq!(dff_file.get_dff_version().unwrap(), 17104896);
877
878 let tag = dff_file.id3_tag().clone().unwrap();
879
880 assert_eq!(tag.title().unwrap(), ".05 sec 1kHz");
881 assert_eq!(tag.artist().unwrap(), "dff");
882 assert_eq!(tag.album().unwrap(), "Test Tones");
883 assert_eq!(tag.year().unwrap(), 2025);
884 assert_eq!(tag.genre().unwrap(), "Test");
885 assert_eq!(tag.track().unwrap(), 1);
886 }
887}