1use dsf_meta::DSF_SAMPLE_DATA_OFFSET;
2use id3::Tag;
3use log::warn;
4use std::{
5 fs::File,
6 path::{Path, PathBuf},
7};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum DsdFileFormat {
12 Dsdiff,
13 Dsf,
14 Raw,
15}
16
17pub trait FormatExtensions {
18 fn is_container(&self) -> bool;
19}
20impl FormatExtensions for DsdFileFormat {
21 fn is_container(&self) -> bool {
22 match self {
23 DsdFileFormat::Dsf | DsdFileFormat::Dsdiff => true,
24 DsdFileFormat::Raw => false,
25 }
26 }
27}
28
29impl From<&PathBuf> for DsdFileFormat {
30 fn from(path: &PathBuf) -> Self {
31 if let Some(ext) = path.extension() {
32 match ext.to_ascii_lowercase().to_string_lossy().as_ref() {
33 "dsf" => DsdFileFormat::Dsf,
34 "dff" => DsdFileFormat::Dsdiff,
35 _ => DsdFileFormat::Raw,
36 }
37 } else {
38 DsdFileFormat::Raw
39 }
40 }
41}
42
43pub const DSD_64_RATE: u32 = 2822400;
44pub const DFF_BLOCK_SIZE: u32 = 1;
45pub const DSF_BLOCK_SIZE: u32 = 4096;
46
47pub struct DsdFile {
48 audio_length: u64,
49 audio_pos: u64,
50 channel_count: Option<usize>,
51 is_lsb: Option<bool>,
52 block_size: Option<u32>,
53 sample_rate: Option<u32>,
54 container_format: DsdFileFormat,
55 file: File,
56 tag: Option<Tag>,
57}
58
59impl DsdFile {
60 pub fn audio_length(&self) -> u64 {
61 self.audio_length
62 }
63 pub fn tag(&self) -> Option<&Tag> {
64 self.tag.as_ref()
65 }
66 pub fn file(&self) -> &File {
67 &self.file
68 }
69 pub fn audio_pos(&self) -> u64 {
70 self.audio_pos
71 }
72 pub fn channel_count(&self) -> Option<usize> {
73 self.channel_count
74 }
75 pub fn is_lsb(&self) -> Option<bool> {
76 self.is_lsb
77 }
78 pub fn block_size(&self) -> Option<u32> {
79 self.block_size
80 }
81 pub fn sample_rate(&self) -> Option<u32> {
82 self.sample_rate
83 }
84 pub fn container_format(&self) -> DsdFileFormat {
85 self.container_format
86 }
87
88 pub fn new(
89 path: &PathBuf,
90 file_format: DsdFileFormat,
91 ) -> Result<Self, Box<dyn std::error::Error>> {
92 if file_format == DsdFileFormat::Dsf {
93 use dsf_meta::DsfFile;
94 let file_path = Path::new(&path);
95 let dsf_file = DsfFile::open(file_path)?;
96 if let Some(e) = dsf_file.tag_read_err() {
97 warn!(
98 "Attempted read of ID3 tag failed. Partial read attempted: {}",
99 e
100 );
101 }
102 let file = dsf_file.file().try_clone()?;
103 Ok(Self {
104 sample_rate: Some(
105 dsf_file.fmt_chunk().sampling_frequency(),
106 ),
107 container_format: DsdFileFormat::Dsf,
108 channel_count: Some(
109 dsf_file.fmt_chunk().channel_num() as usize,
110 ),
111 is_lsb: Some(dsf_file.fmt_chunk().bits_per_sample() == 1),
112 block_size: Some(DSF_BLOCK_SIZE), audio_length: dsf_file.fmt_chunk().sample_count() / 8
114 * dsf_file.fmt_chunk().channel_num() as u64,
115 audio_pos: DSF_SAMPLE_DATA_OFFSET,
116 file,
117 tag: dsf_file.id3_tag().clone(),
118 })
119 } else if file_format == DsdFileFormat::Dsdiff {
120 use dff_meta::DffFile;
121 use dff_meta::model::*;
122 let file_path = Path::new(&path);
123 let dff_file = match DffFile::open(file_path) {
124 Ok(dff) => dff,
125 Err(Error::Id3Error(e, dff_file)) => {
126 warn!(
127 "Attempted read of ID3 tag failed. Partial read attempted: {}",
128 e
129 );
130 dff_file
131 }
132 Err(e) => {
133 return Err(e.into());
134 }
135 };
136 let file = dff_file.file().try_clone()?;
137 Ok(Self {
138 sample_rate: Some(dff_file.get_sample_rate()?),
139 container_format: DsdFileFormat::Dsdiff,
140 channel_count: Some(dff_file.get_num_channels()?),
141 is_lsb: Some(false),
142 block_size: Some(DFF_BLOCK_SIZE), audio_length: dff_file.get_audio_length(),
144 audio_pos: dff_file.get_dsd_data_offset(),
145 file,
146 tag: dff_file.id3_tag().clone(),
147 })
148 } else if file_format == DsdFileFormat::Raw {
149 let Ok(meta) = std::fs::metadata(path) else {
150 return Err("Failed to read input file metadata".into());
151 };
152 Ok(Self {
153 sample_rate: None,
154 container_format: DsdFileFormat::Raw,
155 channel_count: None,
156 is_lsb: None,
157 block_size: None,
158 audio_length: meta.len(),
159 audio_pos: 0,
160 file: File::open(path)?,
161 tag: None,
162 })
163 } else {
164 Err("Unsupported file type; only dsf, dff, and raw dsd files are supported"
165 .into())
166 }
167 }
168}