1 /***************************************************************************
2 copyright : (C) 2002 - 2008 by Scott Wheeler
3 email : wheeler@kde.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 <tagunion.h>
27 #include <tagutils.h>
28 #include <id3v2tag.h>
29 #include <id3v2header.h>
30 #include <id3v1tag.h>
31 #include <apefooter.h>
32 #include <apetag.h>
33 #include <tdebug.h>
34
35 #include "mpegfile.h"
36 #include "mpegheader.h"
37 #include "mpegutils.h"
38 #include "tpropertymap.h"
39
40 using namespace TagLib;
41
42 namespace
43 {
44 enum { ID3v2Index = 0, APEIndex = 1, ID3v1Index = 2 };
45 }
46
47 class MPEG::File::FilePrivate
48 {
49 public:
FilePrivate(const ID3v2::FrameFactory * frameFactory=ID3v2::FrameFactory::instance ())50 FilePrivate(const ID3v2::FrameFactory *frameFactory = ID3v2::FrameFactory::instance()) :
51 ID3v2FrameFactory(frameFactory),
52 ID3v2Location(-1),
53 ID3v2OriginalSize(0),
54 APELocation(-1),
55 APEOriginalSize(0),
56 ID3v1Location(-1),
57 properties(0) {}
58
~FilePrivate()59 ~FilePrivate()
60 {
61 delete properties;
62 }
63
64 const ID3v2::FrameFactory *ID3v2FrameFactory;
65
66 long ID3v2Location;
67 long ID3v2OriginalSize;
68
69 long APELocation;
70 long APEOriginalSize;
71
72 long ID3v1Location;
73
74 TagUnion tag;
75
76 Properties *properties;
77 };
78
79 ////////////////////////////////////////////////////////////////////////////////
80 // static members
81 ////////////////////////////////////////////////////////////////////////////////
82
83 namespace
84 {
85 // Dummy file class to make a stream work with MPEG::Header.
86
87 class AdapterFile : public TagLib::File
88 {
89 public:
AdapterFile(IOStream * stream)90 AdapterFile(IOStream *stream) : File(stream) {}
91
tag() const92 Tag *tag() const { return 0; }
audioProperties() const93 AudioProperties *audioProperties() const { return 0; }
save()94 bool save() { return false; }
95 };
96 }
97
isSupported(IOStream * stream)98 bool MPEG::File::isSupported(IOStream *stream)
99 {
100 if(!stream || !stream->isOpen())
101 return false;
102
103 // An MPEG file has MPEG frame headers. An ID3v2 tag may precede.
104
105 // MPEG frame headers are really confusing with irrelevant binary data.
106 // So we check if a frame header is really valid.
107
108 long headerOffset;
109 const ByteVector buffer = Utils::readHeader(stream, bufferSize(), true, &headerOffset);
110
111 if(buffer.isEmpty())
112 return false;
113
114 const long originalPosition = stream->tell();
115 AdapterFile file(stream);
116
117 for(unsigned int i = 0; i < buffer.size() - 1; ++i) {
118 if(isFrameSync(buffer, i)) {
119 const Header header(&file, headerOffset + i, true);
120 if(header.isValid()) {
121 stream->seek(originalPosition);
122 return true;
123 }
124 }
125 }
126
127 stream->seek(originalPosition);
128 return false;
129 }
130
131 ////////////////////////////////////////////////////////////////////////////////
132 // public members
133 ////////////////////////////////////////////////////////////////////////////////
134
File(FileName file,bool readProperties,Properties::ReadStyle)135 MPEG::File::File(FileName file, bool readProperties, Properties::ReadStyle) :
136 TagLib::File(file),
137 d(new FilePrivate())
138 {
139 if(isOpen())
140 read(readProperties);
141 }
142
File(FileName file,ID3v2::FrameFactory * frameFactory,bool readProperties,Properties::ReadStyle)143 MPEG::File::File(FileName file, ID3v2::FrameFactory *frameFactory,
144 bool readProperties, Properties::ReadStyle) :
145 TagLib::File(file),
146 d(new FilePrivate(frameFactory))
147 {
148 if(isOpen())
149 read(readProperties);
150 }
151
File(IOStream * stream,ID3v2::FrameFactory * frameFactory,bool readProperties,Properties::ReadStyle)152 MPEG::File::File(IOStream *stream, ID3v2::FrameFactory *frameFactory,
153 bool readProperties, Properties::ReadStyle) :
154 TagLib::File(stream),
155 d(new FilePrivate(frameFactory))
156 {
157 if(isOpen())
158 read(readProperties);
159 }
160
~File()161 MPEG::File::~File()
162 {
163 delete d;
164 }
165
tag() const166 TagLib::Tag *MPEG::File::tag() const
167 {
168 return &d->tag;
169 }
170
properties() const171 PropertyMap MPEG::File::properties() const
172 {
173 return d->tag.properties();
174 }
175
removeUnsupportedProperties(const StringList & properties)176 void MPEG::File::removeUnsupportedProperties(const StringList &properties)
177 {
178 d->tag.removeUnsupportedProperties(properties);
179 }
180
setProperties(const PropertyMap & properties)181 PropertyMap MPEG::File::setProperties(const PropertyMap &properties)
182 {
183 // update ID3v1 tag if it exists, but ignore the return value
184
185 if(ID3v1Tag())
186 ID3v1Tag()->setProperties(properties);
187
188 return ID3v2Tag(true)->setProperties(properties);
189 }
190
audioProperties() const191 MPEG::Properties *MPEG::File::audioProperties() const
192 {
193 return d->properties;
194 }
195
save()196 bool MPEG::File::save()
197 {
198 return save(AllTags);
199 }
200
save(int tags)201 bool MPEG::File::save(int tags)
202 {
203 return save(tags, true);
204 }
205
save(int tags,bool stripOthers)206 bool MPEG::File::save(int tags, bool stripOthers)
207 {
208 return save(tags, stripOthers, 4);
209 }
210
save(int tags,bool stripOthers,int id3v2Version)211 bool MPEG::File::save(int tags, bool stripOthers, int id3v2Version)
212 {
213 return save(tags, stripOthers, id3v2Version, true);
214 }
215
save(int tags,bool stripOthers,int id3v2Version,bool duplicateTags)216 bool MPEG::File::save(int tags, bool stripOthers, int id3v2Version, bool duplicateTags)
217 {
218 if(readOnly()) {
219 debug("MPEG::File::save() -- File is read only.");
220 return false;
221 }
222
223 // Create the tags if we've been asked to.
224
225 if(duplicateTags) {
226
227 // Copy the values from the tag that does exist into the new tag,
228 // except if the existing tag is to be stripped.
229
230 if((tags & ID3v2) && ID3v1Tag() && !(stripOthers && !(tags & ID3v1)))
231 Tag::duplicate(ID3v1Tag(), ID3v2Tag(true), false);
232
233 if((tags & ID3v1) && d->tag[ID3v2Index] && !(stripOthers && !(tags & ID3v2)))
234 Tag::duplicate(ID3v2Tag(), ID3v1Tag(true), false);
235 }
236
237 // Remove all the tags not going to be saved.
238
239 if(stripOthers)
240 strip(~tags, false);
241
242 if(ID3v2 & tags) {
243
244 if(ID3v2Tag() && !ID3v2Tag()->isEmpty()) {
245
246 // ID3v2 tag is not empty. Update the old one or create a new one.
247
248 if(d->ID3v2Location < 0)
249 d->ID3v2Location = 0;
250
251 const ByteVector data = ID3v2Tag()->render(id3v2Version);
252 insert(data, d->ID3v2Location, d->ID3v2OriginalSize);
253
254 if(d->APELocation >= 0)
255 d->APELocation += (static_cast<long>(data.size()) - d->ID3v2OriginalSize);
256
257 if(d->ID3v1Location >= 0)
258 d->ID3v1Location += (static_cast<long>(data.size()) - d->ID3v2OriginalSize);
259
260 d->ID3v2OriginalSize = data.size();
261 }
262 else {
263
264 // ID3v2 tag is empty. Remove the old one.
265
266 strip(ID3v2, false);
267 }
268 }
269
270 if(ID3v1 & tags) {
271
272 if(ID3v1Tag() && !ID3v1Tag()->isEmpty()) {
273
274 // ID3v1 tag is not empty. Update the old one or create a new one.
275
276 if(d->ID3v1Location >= 0) {
277 seek(d->ID3v1Location);
278 }
279 else {
280 seek(0, End);
281 d->ID3v1Location = tell();
282 }
283
284 writeBlock(ID3v1Tag()->render());
285 }
286 else {
287
288 // ID3v1 tag is empty. Remove the old one.
289
290 strip(ID3v1, false);
291 }
292 }
293
294 if(APE & tags) {
295
296 if(APETag() && !APETag()->isEmpty()) {
297
298 // APE tag is not empty. Update the old one or create a new one.
299
300 if(d->APELocation < 0) {
301 if(d->ID3v1Location >= 0)
302 d->APELocation = d->ID3v1Location;
303 else
304 d->APELocation = length();
305 }
306
307 const ByteVector data = APETag()->render();
308 insert(data, d->APELocation, d->APEOriginalSize);
309
310 if(d->ID3v1Location >= 0)
311 d->ID3v1Location += (static_cast<long>(data.size()) - d->APEOriginalSize);
312
313 d->APEOriginalSize = data.size();
314 }
315 else {
316
317 // APE tag is empty. Remove the old one.
318
319 strip(APE, false);
320 }
321 }
322
323 return true;
324 }
325
ID3v2Tag(bool create)326 ID3v2::Tag *MPEG::File::ID3v2Tag(bool create)
327 {
328 return d->tag.access<ID3v2::Tag>(ID3v2Index, create);
329 }
330
ID3v1Tag(bool create)331 ID3v1::Tag *MPEG::File::ID3v1Tag(bool create)
332 {
333 return d->tag.access<ID3v1::Tag>(ID3v1Index, create);
334 }
335
APETag(bool create)336 APE::Tag *MPEG::File::APETag(bool create)
337 {
338 return d->tag.access<APE::Tag>(APEIndex, create);
339 }
340
strip(int tags)341 bool MPEG::File::strip(int tags)
342 {
343 return strip(tags, true);
344 }
345
strip(int tags,bool freeMemory)346 bool MPEG::File::strip(int tags, bool freeMemory)
347 {
348 if(readOnly()) {
349 debug("MPEG::File::strip() - Cannot strip tags from a read only file.");
350 return false;
351 }
352
353 if((tags & ID3v2) && d->ID3v2Location >= 0) {
354 removeBlock(d->ID3v2Location, d->ID3v2OriginalSize);
355
356 if(d->APELocation >= 0)
357 d->APELocation -= d->ID3v2OriginalSize;
358
359 if(d->ID3v1Location >= 0)
360 d->ID3v1Location -= d->ID3v2OriginalSize;
361
362 d->ID3v2Location = -1;
363 d->ID3v2OriginalSize = 0;
364
365 if(freeMemory)
366 d->tag.set(ID3v2Index, 0);
367 }
368
369 if((tags & ID3v1) && d->ID3v1Location >= 0) {
370 truncate(d->ID3v1Location);
371
372 d->ID3v1Location = -1;
373
374 if(freeMemory)
375 d->tag.set(ID3v1Index, 0);
376 }
377
378 if((tags & APE) && d->APELocation >= 0) {
379 removeBlock(d->APELocation, d->APEOriginalSize);
380
381 if(d->ID3v1Location >= 0)
382 d->ID3v1Location -= d->APEOriginalSize;
383
384 d->APELocation = -1;
385 d->APEOriginalSize = 0;
386
387 if(freeMemory)
388 d->tag.set(APEIndex, 0);
389 }
390
391 return true;
392 }
393
setID3v2FrameFactory(const ID3v2::FrameFactory * factory)394 void MPEG::File::setID3v2FrameFactory(const ID3v2::FrameFactory *factory)
395 {
396 d->ID3v2FrameFactory = factory;
397 }
398
nextFrameOffset(long position)399 long MPEG::File::nextFrameOffset(long position)
400 {
401 ByteVector frameSyncBytes(2, '\0');
402
403 while(true) {
404 seek(position);
405 const ByteVector buffer = readBlock(bufferSize());
406 if(buffer.isEmpty())
407 return -1;
408
409 for(unsigned int i = 0; i < buffer.size(); ++i) {
410 frameSyncBytes[0] = frameSyncBytes[1];
411 frameSyncBytes[1] = buffer[i];
412 if(isFrameSync(frameSyncBytes)) {
413 const Header header(this, position + i - 1, true);
414 if(header.isValid())
415 return position + i - 1;
416 }
417 }
418
419 position += bufferSize();
420 }
421 }
422
previousFrameOffset(long position)423 long MPEG::File::previousFrameOffset(long position)
424 {
425 ByteVector frameSyncBytes(2, '\0');
426
427 while(position > 0) {
428 const long bufferLength = std::min<long>(position, bufferSize());
429 position -= bufferLength;
430
431 seek(position);
432 const ByteVector buffer = readBlock(bufferLength);
433
434 for(int i = buffer.size() - 1; i >= 0; --i) {
435 frameSyncBytes[1] = frameSyncBytes[0];
436 frameSyncBytes[0] = buffer[i];
437 if(isFrameSync(frameSyncBytes)) {
438 const Header header(this, position + i, true);
439 if(header.isValid())
440 return position + i + header.frameLength();
441 }
442 }
443 }
444
445 return -1;
446 }
447
firstFrameOffset()448 long MPEG::File::firstFrameOffset()
449 {
450 long position = 0;
451
452 if(hasID3v2Tag())
453 position = d->ID3v2Location + ID3v2Tag()->header()->completeTagSize();
454
455 return nextFrameOffset(position);
456 }
457
lastFrameOffset()458 long MPEG::File::lastFrameOffset()
459 {
460 long position;
461
462 if(hasAPETag())
463 position = d->APELocation - 1;
464 else if(hasID3v1Tag())
465 position = d->ID3v1Location - 1;
466 else
467 position = length();
468
469 return previousFrameOffset(position);
470 }
471
hasID3v1Tag() const472 bool MPEG::File::hasID3v1Tag() const
473 {
474 return (d->ID3v1Location >= 0);
475 }
476
hasID3v2Tag() const477 bool MPEG::File::hasID3v2Tag() const
478 {
479 return (d->ID3v2Location >= 0);
480 }
481
hasAPETag() const482 bool MPEG::File::hasAPETag() const
483 {
484 return (d->APELocation >= 0);
485 }
486
487 ////////////////////////////////////////////////////////////////////////////////
488 // private members
489 ////////////////////////////////////////////////////////////////////////////////
490
read(bool readProperties)491 void MPEG::File::read(bool readProperties)
492 {
493 // Look for an ID3v2 tag
494
495 d->ID3v2Location = findID3v2();
496
497 if(d->ID3v2Location >= 0) {
498 d->tag.set(ID3v2Index, new ID3v2::Tag(this, d->ID3v2Location, d->ID3v2FrameFactory));
499 d->ID3v2OriginalSize = ID3v2Tag()->header()->completeTagSize();
500 }
501
502 // Look for an ID3v1 tag
503
504 d->ID3v1Location = Utils::findID3v1(this);
505
506 if(d->ID3v1Location >= 0)
507 d->tag.set(ID3v1Index, new ID3v1::Tag(this, d->ID3v1Location));
508
509 // Look for an APE tag
510
511 d->APELocation = Utils::findAPE(this, d->ID3v1Location);
512
513 if(d->APELocation >= 0) {
514 d->tag.set(APEIndex, new APE::Tag(this, d->APELocation));
515 d->APEOriginalSize = APETag()->footer()->completeTagSize();
516 d->APELocation = d->APELocation + APE::Footer::size() - d->APEOriginalSize;
517 }
518
519 if(readProperties)
520 d->properties = new Properties(this);
521
522 // Make sure that we have our default tag types available.
523
524 ID3v2Tag(true);
525 ID3v1Tag(true);
526 }
527
findID3v2()528 long MPEG::File::findID3v2()
529 {
530 if(!isValid())
531 return -1;
532
533 // An ID3v2 tag or MPEG frame is most likely be at the beginning of the file.
534
535 const ByteVector headerID = ID3v2::Header::fileIdentifier();
536
537 seek(0);
538 if(readBlock(headerID.size()) == headerID)
539 return 0;
540
541 const Header firstHeader(this, 0, true);
542 if(firstHeader.isValid())
543 return -1;
544
545 // Look for an ID3v2 tag until reaching the first valid MPEG frame.
546
547 ByteVector frameSyncBytes(2, '\0');
548 ByteVector tagHeaderBytes(3, '\0');
549 long position = 0;
550
551 while(true) {
552 seek(position);
553 const ByteVector buffer = readBlock(bufferSize());
554 if(buffer.isEmpty())
555 return -1;
556
557 for(unsigned int i = 0; i < buffer.size(); ++i) {
558 frameSyncBytes[0] = frameSyncBytes[1];
559 frameSyncBytes[1] = buffer[i];
560 if(isFrameSync(frameSyncBytes)) {
561 const Header header(this, position + i - 1, true);
562 if(header.isValid())
563 return -1;
564 }
565
566 tagHeaderBytes[0] = tagHeaderBytes[1];
567 tagHeaderBytes[1] = tagHeaderBytes[2];
568 tagHeaderBytes[2] = buffer[i];
569 if(tagHeaderBytes == headerID)
570 return position + i - 2;
571 }
572
573 position += bufferSize();
574 }
575 }
576