1 /***************************************************************************
2     copyright            : (C) 2004 by Allan Sandfeld Jensen
3     email                : kde@carewolf.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 <tstring.h>
28 #include <tagunion.h>
29 #include <tdebug.h>
30 #include <tpropertymap.h>
31 #include <tagutils.h>
32 
33 #include "mpcfile.h"
34 #include "id3v1tag.h"
35 #include "id3v2header.h"
36 #include "apetag.h"
37 #include "apefooter.h"
38 
39 using namespace TagLib;
40 
41 namespace
42 {
43   enum { MPCAPEIndex = 0, MPCID3v1Index = 1 };
44 }
45 
46 class MPC::File::FilePrivate
47 {
48 public:
FilePrivate()49   FilePrivate() :
50     APELocation(-1),
51     APESize(0),
52     ID3v1Location(-1),
53     ID3v2Header(0),
54     ID3v2Location(-1),
55     ID3v2Size(0),
56     properties(0) {}
57 
~FilePrivate()58   ~FilePrivate()
59   {
60     delete ID3v2Header;
61     delete properties;
62   }
63 
64   long APELocation;
65   long APESize;
66 
67   long ID3v1Location;
68 
69   ID3v2::Header *ID3v2Header;
70   long ID3v2Location;
71   long ID3v2Size;
72 
73   TagUnion tag;
74 
75   Properties *properties;
76 };
77 
78 ////////////////////////////////////////////////////////////////////////////////
79 // static members
80 ////////////////////////////////////////////////////////////////////////////////
81 
isSupported(IOStream * stream)82 bool MPC::File::isSupported(IOStream *stream)
83 {
84   // A newer MPC file has to start with "MPCK" or "MP+", but older files don't
85   // have keys to do a quick check.
86 
87   const ByteVector id = Utils::readHeader(stream, 4, false);
88   return (id == "MPCK" || id.startsWith("MP+"));
89 }
90 
91 ////////////////////////////////////////////////////////////////////////////////
92 // public members
93 ////////////////////////////////////////////////////////////////////////////////
94 
File(FileName file,bool readProperties,Properties::ReadStyle)95 MPC::File::File(FileName file, bool readProperties, Properties::ReadStyle) :
96   TagLib::File(file),
97   d(new FilePrivate())
98 {
99   if(isOpen())
100     read(readProperties);
101 }
102 
File(IOStream * stream,bool readProperties,Properties::ReadStyle)103 MPC::File::File(IOStream *stream, bool readProperties, Properties::ReadStyle) :
104   TagLib::File(stream),
105   d(new FilePrivate())
106 {
107   if(isOpen())
108     read(readProperties);
109 }
110 
~File()111 MPC::File::~File()
112 {
113   delete d;
114 }
115 
tag() const116 TagLib::Tag *MPC::File::tag() const
117 {
118   return &d->tag;
119 }
120 
properties() const121 PropertyMap MPC::File::properties() const
122 {
123   return d->tag.properties();
124 }
125 
removeUnsupportedProperties(const StringList & properties)126 void MPC::File::removeUnsupportedProperties(const StringList &properties)
127 {
128   d->tag.removeUnsupportedProperties(properties);
129 }
130 
setProperties(const PropertyMap & properties)131 PropertyMap MPC::File::setProperties(const PropertyMap &properties)
132 {
133   if(ID3v1Tag())
134     ID3v1Tag()->setProperties(properties);
135 
136   return APETag(true)->setProperties(properties);
137 }
138 
audioProperties() const139 MPC::Properties *MPC::File::audioProperties() const
140 {
141   return d->properties;
142 }
143 
save()144 bool MPC::File::save()
145 {
146   if(readOnly()) {
147     debug("MPC::File::save() -- File is read only.");
148     return false;
149   }
150 
151   // Possibly strip ID3v2 tag
152 
153   if(!d->ID3v2Header && d->ID3v2Location >= 0) {
154     removeBlock(d->ID3v2Location, d->ID3v2Size);
155 
156     if(d->APELocation >= 0)
157       d->APELocation -= d->ID3v2Size;
158 
159     if(d->ID3v1Location >= 0)
160       d->ID3v1Location -= d->ID3v2Size;
161 
162     d->ID3v2Location = -1;
163     d->ID3v2Size = 0;
164   }
165 
166   // Update ID3v1 tag
167 
168   if(ID3v1Tag() && !ID3v1Tag()->isEmpty()) {
169 
170     // ID3v1 tag is not empty. Update the old one or create a new one.
171 
172     if(d->ID3v1Location >= 0) {
173       seek(d->ID3v1Location);
174     }
175     else {
176       seek(0, End);
177       d->ID3v1Location = tell();
178     }
179 
180     writeBlock(ID3v1Tag()->render());
181   }
182   else {
183 
184     // ID3v1 tag is empty. Remove the old one.
185 
186     if(d->ID3v1Location >= 0) {
187       truncate(d->ID3v1Location);
188       d->ID3v1Location = -1;
189     }
190   }
191 
192   // Update APE tag
193 
194   if(APETag() && !APETag()->isEmpty()) {
195 
196     // APE tag is not empty. Update the old one or create a new one.
197 
198     if(d->APELocation < 0) {
199       if(d->ID3v1Location >= 0)
200         d->APELocation = d->ID3v1Location;
201       else
202         d->APELocation = length();
203     }
204 
205     const ByteVector data = APETag()->render();
206     insert(data, d->APELocation, d->APESize);
207 
208     if(d->ID3v1Location >= 0)
209       d->ID3v1Location += (static_cast<long>(data.size()) - d->APESize);
210 
211     d->APESize = data.size();
212   }
213   else {
214 
215     // APE tag is empty. Remove the old one.
216 
217     if(d->APELocation >= 0) {
218       removeBlock(d->APELocation, d->APESize);
219 
220       if(d->ID3v1Location >= 0)
221         d->ID3v1Location -= d->APESize;
222 
223       d->APELocation = -1;
224       d->APESize = 0;
225     }
226   }
227 
228   return true;
229 }
230 
ID3v1Tag(bool create)231 ID3v1::Tag *MPC::File::ID3v1Tag(bool create)
232 {
233   return d->tag.access<ID3v1::Tag>(MPCID3v1Index, create);
234 }
235 
APETag(bool create)236 APE::Tag *MPC::File::APETag(bool create)
237 {
238   return d->tag.access<APE::Tag>(MPCAPEIndex, create);
239 }
240 
strip(int tags)241 void MPC::File::strip(int tags)
242 {
243   if(tags & ID3v1)
244     d->tag.set(MPCID3v1Index, 0);
245 
246   if(tags & APE)
247     d->tag.set(MPCAPEIndex, 0);
248 
249   if(!ID3v1Tag())
250     APETag(true);
251 
252   if(tags & ID3v2) {
253     delete d->ID3v2Header;
254     d->ID3v2Header = 0;
255   }
256 }
257 
remove(int tags)258 void MPC::File::remove(int tags)
259 {
260   strip(tags);
261 }
262 
hasID3v1Tag() const263 bool MPC::File::hasID3v1Tag() const
264 {
265   return (d->ID3v1Location >= 0);
266 }
267 
hasAPETag() const268 bool MPC::File::hasAPETag() const
269 {
270   return (d->APELocation >= 0);
271 }
272 
273 ////////////////////////////////////////////////////////////////////////////////
274 // private members
275 ////////////////////////////////////////////////////////////////////////////////
276 
read(bool readProperties)277 void MPC::File::read(bool readProperties)
278 {
279   // Look for an ID3v2 tag
280 
281   d->ID3v2Location = Utils::findID3v2(this);
282 
283   if(d->ID3v2Location >= 0) {
284     seek(d->ID3v2Location);
285     d->ID3v2Header = new ID3v2::Header(readBlock(ID3v2::Header::size()));
286     d->ID3v2Size = d->ID3v2Header->completeTagSize();
287   }
288 
289   // Look for an ID3v1 tag
290 
291   d->ID3v1Location = Utils::findID3v1(this);
292 
293   if(d->ID3v1Location >= 0)
294     d->tag.set(MPCID3v1Index, new ID3v1::Tag(this, d->ID3v1Location));
295 
296   // Look for an APE tag
297 
298   d->APELocation = Utils::findAPE(this, d->ID3v1Location);
299 
300   if(d->APELocation >= 0) {
301     d->tag.set(MPCAPEIndex, new APE::Tag(this, d->APELocation));
302     d->APESize = APETag()->footer()->completeTagSize();
303     d->APELocation = d->APELocation + APE::Footer::size() - d->APESize;
304   }
305 
306   if(d->ID3v1Location < 0)
307     APETag(true);
308 
309   // Look for MPC metadata
310 
311   if(readProperties) {
312 
313     long streamLength;
314 
315     if(d->APELocation >= 0)
316       streamLength = d->APELocation;
317     else if(d->ID3v1Location >= 0)
318       streamLength = d->ID3v1Location;
319     else
320       streamLength = length();
321 
322     if(d->ID3v2Location >= 0) {
323       seek(d->ID3v2Location + d->ID3v2Size);
324       streamLength -= (d->ID3v2Location + d->ID3v2Size);
325     }
326     else {
327       seek(0);
328     }
329 
330     d->properties = new Properties(this, streamLength);
331   }
332 }
333