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