Skip to main content

dsd_reader/
dsd_file.rs

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// Strongly typed container format
10#[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), // Should always be this value for DSF
113                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), // Should always be 1 for DFF
143                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}