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