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