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