1 /***************************************************************************
2 copyright : (C) 2010 by Alex Novichkov
3 email : novichko@atnet.ru
4
5 copyright : (C) 2006 by Lukáš Lalinský
6 email : lalinsky@gmail.com
7 (original WavPack implementation)
8
9 copyright : (C) 2004 by Allan Sandfeld Jensen
10 email : kde@carewolf.org
11 (original MPC implementation)
12 ***************************************************************************/
13
14 /***************************************************************************
15 * This library is free software; you can redistribute it and/or modify *
16 * it under the terms of the GNU Lesser General Public License version *
17 * 2.1 as published by the Free Software Foundation. *
18 * *
19 * This library is distributed in the hope that it will be useful, but *
20 * WITHOUT ANY WARRANTY; without even the implied warranty of *
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
22 * Lesser General Public License for more details. *
23 * *
24 * You should have received a copy of the GNU Lesser General Public *
25 * License along with this library; if not, write to the Free Software *
26 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
27 * 02110-1301 USA *
28 * *
29 * Alternatively, this file is available under the Mozilla Public *
30 * License Version 1.1. You may obtain a copy of the License at *
31 * http://www.mozilla.org/MPL/ *
32 ***************************************************************************/
33
34 #include <tbytevector.h>
35 #include <tstring.h>
36 #include <tdebug.h>
37 #include <tagunion.h>
38 #include <id3v1tag.h>
39 #include <id3v2header.h>
40 #include <tpropertymap.h>
41 #include <tagutils.h>
42
43 #include "apefile.h"
44 #include "apetag.h"
45 #include "apefooter.h"
46
47 using namespace TagLib;
48
49 namespace
50 {
51 enum { ApeAPEIndex = 0, ApeID3v1Index = 1 };
52 }
53
54 class APE::File::FilePrivate
55 {
56 public:
FilePrivate()57 FilePrivate() :
58 APELocation(-1),
59 APESize(0),
60 ID3v1Location(-1),
61 ID3v2Header(0),
62 ID3v2Location(-1),
63 ID3v2Size(0),
64 properties(0) {}
65
~FilePrivate()66 ~FilePrivate()
67 {
68 delete ID3v2Header;
69 delete properties;
70 }
71
72 long APELocation;
73 long APESize;
74
75 long ID3v1Location;
76
77 ID3v2::Header *ID3v2Header;
78 long ID3v2Location;
79 long ID3v2Size;
80
81 TagUnion tag;
82
83 Properties *properties;
84 };
85
86 ////////////////////////////////////////////////////////////////////////////////
87 // static members
88 ////////////////////////////////////////////////////////////////////////////////
89
isSupported(IOStream * stream)90 bool APE::File::isSupported(IOStream *stream)
91 {
92 // An APE file has an ID "MAC " somewhere. An ID3v2 tag may precede.
93
94 const ByteVector buffer = Utils::readHeader(stream, bufferSize(), true);
95 return (buffer.find("MAC ") >= 0);
96 }
97
98 ////////////////////////////////////////////////////////////////////////////////
99 // public members
100 ////////////////////////////////////////////////////////////////////////////////
101
File(FileName file,bool readProperties,Properties::ReadStyle)102 APE::File::File(FileName file, bool readProperties, Properties::ReadStyle) :
103 TagLib::File(file),
104 d(new FilePrivate())
105 {
106 if(isOpen())
107 read(readProperties);
108 }
109
File(IOStream * stream,bool readProperties,Properties::ReadStyle)110 APE::File::File(IOStream *stream, bool readProperties, Properties::ReadStyle) :
111 TagLib::File(stream),
112 d(new FilePrivate())
113 {
114 if(isOpen())
115 read(readProperties);
116 }
117
~File()118 APE::File::~File()
119 {
120 delete d;
121 }
122
tag() const123 TagLib::Tag *APE::File::tag() const
124 {
125 return &d->tag;
126 }
127
properties() const128 PropertyMap APE::File::properties() const
129 {
130 return d->tag.properties();
131 }
132
removeUnsupportedProperties(const StringList & properties)133 void APE::File::removeUnsupportedProperties(const StringList &properties)
134 {
135 d->tag.removeUnsupportedProperties(properties);
136 }
137
setProperties(const PropertyMap & properties)138 PropertyMap APE::File::setProperties(const PropertyMap &properties)
139 {
140 if(ID3v1Tag())
141 ID3v1Tag()->setProperties(properties);
142
143 return APETag(true)->setProperties(properties);
144 }
145
audioProperties() const146 APE::Properties *APE::File::audioProperties() const
147 {
148 return d->properties;
149 }
150
save()151 bool APE::File::save()
152 {
153 if(readOnly()) {
154 debug("APE::File::save() -- File is read only.");
155 return false;
156 }
157
158 // Update ID3v1 tag
159
160 if(ID3v1Tag() && !ID3v1Tag()->isEmpty()) {
161
162 // ID3v1 tag is not empty. Update the old one or create a new one.
163
164 if(d->ID3v1Location >= 0) {
165 seek(d->ID3v1Location);
166 }
167 else {
168 seek(0, End);
169 d->ID3v1Location = tell();
170 }
171
172 writeBlock(ID3v1Tag()->render());
173 }
174 else {
175
176 // ID3v1 tag is empty. Remove the old one.
177
178 if(d->ID3v1Location >= 0) {
179 truncate(d->ID3v1Location);
180 d->ID3v1Location = -1;
181 }
182 }
183
184 // Update APE tag
185
186 if(APETag() && !APETag()->isEmpty()) {
187
188 // APE tag is not empty. Update the old one or create a new one.
189
190 if(d->APELocation < 0) {
191 if(d->ID3v1Location >= 0)
192 d->APELocation = d->ID3v1Location;
193 else
194 d->APELocation = length();
195 }
196
197 const ByteVector data = APETag()->render();
198 insert(data, d->APELocation, d->APESize);
199
200 if(d->ID3v1Location >= 0)
201 d->ID3v1Location += (static_cast<long>(data.size()) - d->APESize);
202
203 d->APESize = data.size();
204 }
205 else {
206
207 // APE tag is empty. Remove the old one.
208
209 if(d->APELocation >= 0) {
210 removeBlock(d->APELocation, d->APESize);
211
212 if(d->ID3v1Location >= 0)
213 d->ID3v1Location -= d->APESize;
214
215 d->APELocation = -1;
216 d->APESize = 0;
217 }
218 }
219
220 return true;
221 }
222
ID3v1Tag(bool create)223 ID3v1::Tag *APE::File::ID3v1Tag(bool create)
224 {
225 return d->tag.access<ID3v1::Tag>(ApeID3v1Index, create);
226 }
227
APETag(bool create)228 APE::Tag *APE::File::APETag(bool create)
229 {
230 return d->tag.access<APE::Tag>(ApeAPEIndex, create);
231 }
232
strip(int tags)233 void APE::File::strip(int tags)
234 {
235 if(tags & ID3v1)
236 d->tag.set(ApeID3v1Index, 0);
237
238 if(tags & APE)
239 d->tag.set(ApeAPEIndex, 0);
240
241 if(!ID3v1Tag())
242 APETag(true);
243 }
244
hasAPETag() const245 bool APE::File::hasAPETag() const
246 {
247 return (d->APELocation >= 0);
248 }
249
hasID3v1Tag() const250 bool APE::File::hasID3v1Tag() const
251 {
252 return (d->ID3v1Location >= 0);
253 }
254
255 ////////////////////////////////////////////////////////////////////////////////
256 // private members
257 ////////////////////////////////////////////////////////////////////////////////
258
read(bool readProperties)259 void APE::File::read(bool readProperties)
260 {
261 // Look for an ID3v2 tag
262
263 d->ID3v2Location = Utils::findID3v2(this);
264
265 if(d->ID3v2Location >= 0) {
266 seek(d->ID3v2Location);
267 d->ID3v2Header = new ID3v2::Header(readBlock(ID3v2::Header::size()));
268 d->ID3v2Size = d->ID3v2Header->completeTagSize();
269 }
270
271 // Look for an ID3v1 tag
272
273 d->ID3v1Location = Utils::findID3v1(this);
274
275 if(d->ID3v1Location >= 0)
276 d->tag.set(ApeID3v1Index, new ID3v1::Tag(this, d->ID3v1Location));
277
278 // Look for an APE tag
279
280 d->APELocation = Utils::findAPE(this, d->ID3v1Location);
281
282 if(d->APELocation >= 0) {
283 d->tag.set(ApeAPEIndex, new APE::Tag(this, d->APELocation));
284 d->APESize = APETag()->footer()->completeTagSize();
285 d->APELocation = d->APELocation + APE::Footer::size() - d->APESize;
286 }
287
288 if(d->ID3v1Location < 0)
289 APETag(true);
290
291 // Look for APE audio properties
292
293 if(readProperties) {
294
295 long streamLength;
296
297 if(d->APELocation >= 0)
298 streamLength = d->APELocation;
299 else if(d->ID3v1Location >= 0)
300 streamLength = d->ID3v1Location;
301 else
302 streamLength = length();
303
304 if(d->ID3v2Location >= 0) {
305 seek(d->ID3v2Location + d->ID3v2Size);
306 streamLength -= (d->ID3v2Location + d->ID3v2Size);
307 }
308 else {
309 seek(0);
310 }
311
312 d->properties = new Properties(this, streamLength);
313 }
314 }
315