1 /***************************************************************************
2 copyright : (C) 2013 - 2018 by Stephen F. Booth
3 email : me@sbooth.org
4 ***************************************************************************/
5
6 /***************************************************************************
7 * This library is free software; you can redistribute it and/or modify *
8 * it under the terms of the GNU Lesser General Public License version *
9 * 2.1 as published by the Free Software Foundation. *
10 * *
11 * This library is distributed in the hope that it will be useful, but *
12 * WITHOUT ANY WARRANTY; without even the implied warranty of *
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
14 * Lesser General Public License for more details. *
15 * *
16 * You should have received a copy of the GNU Lesser General Public *
17 * License along with this library; if not, write to the Free Software *
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
19 * 02110-1301 USA *
20 * *
21 * Alternatively, this file is available under the Mozilla Public *
22 * License Version 1.1. You may obtain a copy of the License at *
23 * http://www.mozilla.org/MPL/ *
24 ***************************************************************************/
25
26 #include <tbytevector.h>
27 #include <tdebug.h>
28 #include <id3v2tag.h>
29 #include <tstringlist.h>
30 #include <tpropertymap.h>
31 #include <tagutils.h>
32
33 #include "dsffile.h"
34
35 using namespace TagLib;
36
37 // The DSF specification is located at http://dsd-guide.com/sites/default/files/white-papers/DSFFileFormatSpec_E.pdf
38
39 class DSF::File::FilePrivate
40 {
41 public:
FilePrivate()42 FilePrivate() :
43 properties(0),
44 tag(0)
45 {
46 }
47
~FilePrivate()48 ~FilePrivate()
49 {
50 delete properties;
51 delete tag;
52 }
53
54 long long fileSize;
55 long long metadataOffset;
56 Properties *properties;
57 ID3v2::Tag *tag;
58 };
59
60 ////////////////////////////////////////////////////////////////////////////////
61 // static members
62 ////////////////////////////////////////////////////////////////////////////////
63
isSupported(IOStream * stream)64 bool DSF::File::isSupported(IOStream *stream)
65 {
66 // A DSF file has to start with "DSD "
67 const ByteVector id = Utils::readHeader(stream, 4, false);
68 return id.startsWith("DSD ");
69 }
70
71 ////////////////////////////////////////////////////////////////////////////////
72 // public members
73 ////////////////////////////////////////////////////////////////////////////////
74
File(FileName file,bool readProperties,Properties::ReadStyle propertiesStyle)75 DSF::File::File(FileName file, bool readProperties,
76 Properties::ReadStyle propertiesStyle) :
77 TagLib::File(file),
78 d(new FilePrivate())
79 {
80 if(isOpen())
81 read(readProperties, propertiesStyle);
82 }
83
File(IOStream * stream,bool readProperties,Properties::ReadStyle propertiesStyle)84 DSF::File::File(IOStream *stream, bool readProperties,
85 Properties::ReadStyle propertiesStyle) :
86 TagLib::File(stream),
87 d(new FilePrivate())
88 {
89 if(isOpen())
90 read(readProperties, propertiesStyle);
91 }
92
~File()93 DSF::File::~File()
94 {
95 delete d;
96 }
97
tag() const98 ID3v2::Tag *DSF::File::tag() const
99 {
100 return d->tag;
101 }
102
properties() const103 PropertyMap DSF::File::properties() const
104 {
105 return d->tag->properties();
106 }
107
setProperties(const PropertyMap & properties)108 PropertyMap DSF::File::setProperties(const PropertyMap &properties)
109 {
110 return d->tag->setProperties(properties);
111 }
112
audioProperties() const113 DSF::Properties *DSF::File::audioProperties() const
114 {
115 return d->properties;
116 }
117
save()118 bool DSF::File::save()
119 {
120 if(readOnly()) {
121 debug("DSF::File::save() -- File is read only.");
122 return false;
123 }
124
125 if(!isValid()) {
126 debug("DSF::File::save() -- Trying to save invalid file.");
127 return false;
128 }
129
130 // Three things must be updated: the file size, the tag data, and the metadata offset
131
132 if(d->tag->isEmpty()) {
133 long long newFileSize = d->metadataOffset ? d->metadataOffset : d->fileSize;
134
135 // Update the file size
136 if(d->fileSize != newFileSize) {
137 insert(ByteVector::fromLongLong(newFileSize, false), 12, 8);
138 d->fileSize = newFileSize;
139 }
140
141 // Update the metadata offset to 0 since there is no longer a tag
142 if(d->metadataOffset) {
143 insert(ByteVector::fromLongLong(0ULL, false), 20, 8);
144 d->metadataOffset = 0;
145 }
146
147 // Delete the old tag
148 truncate(newFileSize);
149 }
150 else {
151 ByteVector tagData = d->tag->render();
152
153 long long newMetadataOffset = d->metadataOffset ? d->metadataOffset : d->fileSize;
154 long long newFileSize = newMetadataOffset + tagData.size();
155 long long oldTagSize = d->fileSize - newMetadataOffset;
156
157 // Update the file size
158 if(d->fileSize != newFileSize) {
159 insert(ByteVector::fromLongLong(newFileSize, false), 12, 8);
160 d->fileSize = newFileSize;
161 }
162
163 // Update the metadata offset
164 if(d->metadataOffset != newMetadataOffset) {
165 insert(ByteVector::fromLongLong(newMetadataOffset, false), 20, 8);
166 d->metadataOffset = newMetadataOffset;
167 }
168
169 // Delete the old tag and write the new one
170 insert(tagData, newMetadataOffset, static_cast<size_t>(oldTagSize));
171 }
172
173 return true;
174 }
175
176 ////////////////////////////////////////////////////////////////////////////////
177 // private members
178 ////////////////////////////////////////////////////////////////////////////////
179
180
read(bool readProperties,Properties::ReadStyle propertiesStyle)181 void DSF::File::read(bool readProperties, Properties::ReadStyle propertiesStyle)
182 {
183 // A DSF file consists of four chunks: DSD chunk, format chunk, data chunk, and metadata chunk
184 // The file format is not chunked in the sense of a RIFF File, though
185
186 // DSD chunk
187 ByteVector chunkName = readBlock(4);
188 if(chunkName != "DSD ") {
189 debug("DSF::File::read() -- Not a DSF file.");
190 setValid(false);
191 return;
192 }
193
194 long long chunkSize = readBlock(8).toLongLong(false);
195
196 // Integrity check
197 if(28 != chunkSize) {
198 debug("DSF::File::read() -- File is corrupted, wrong chunk size");
199 setValid(false);
200 return;
201 }
202
203 d->fileSize = readBlock(8).toLongLong(false);
204
205 // File is malformed or corrupted
206 if(d->fileSize != length()) {
207 debug("DSF::File::read() -- File is corrupted wrong length");
208 setValid(false);
209 return;
210 }
211
212 d->metadataOffset = readBlock(8).toLongLong(false);
213
214 // File is malformed or corrupted
215 if(d->metadataOffset > d->fileSize) {
216 debug("DSF::File::read() -- Invalid metadata offset.");
217 setValid(false);
218 return;
219 }
220
221 // Format chunk
222 chunkName = readBlock(4);
223 if(chunkName != "fmt ") {
224 debug("DSF::File::read() -- Missing 'fmt ' chunk.");
225 setValid(false);
226 return;
227 }
228
229 chunkSize = readBlock(8).toLongLong(false);
230
231 d->properties = new Properties(readBlock(chunkSize), propertiesStyle);
232
233 // Skip the data chunk
234
235 // A metadata offset of 0 indicates the absence of an ID3v2 tag
236 if(0 == d->metadataOffset)
237 d->tag = new ID3v2::Tag();
238 else
239 d->tag = new ID3v2::Tag(this, d->metadataOffset);
240 }
241
242