1 /***************************************************************************
2 copyright : (C) 2002 - 2008 by Scott Wheeler
3 email : wheeler@kde.org
4 ***************************************************************************/
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. *
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. *
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
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 ***************************************************************************/
28 #include <id3v2header.h>
30 #include <apefooter.h>
37 #include "mpegheader.h"
39 using namespace TagLib;
43 enum { ID3v2Index = 0, APEIndex = 1, ID3v1Index = 2 };
46 class MPEG::File::FilePrivate
49 FilePrivate(ID3v2::FrameFactory *frameFactory = ID3v2::FrameFactory::instance()) :
50 ID3v2FrameFactory(frameFactory),
54 APEFooterLocation(-1),
70 const ID3v2::FrameFactory *ID3v2FrameFactory;
73 uint ID3v2OriginalSize;
76 long APEFooterLocation;
83 // These indicate whether the file *on disk* has these tags, not if
84 // this data structure does. This is used in computing offsets.
90 Properties *properties;
93 ////////////////////////////////////////////////////////////////////////////////
95 ////////////////////////////////////////////////////////////////////////////////
97 MPEG::File::File(FileName file, bool readProperties,
98 Properties::ReadStyle propertiesStyle) : TagLib::File(file)
103 read(readProperties, propertiesStyle);
106 MPEG::File::File(FileName file, ID3v2::FrameFactory *frameFactory,
107 bool readProperties, Properties::ReadStyle propertiesStyle) :
110 d = new FilePrivate(frameFactory);
113 read(readProperties, propertiesStyle);
121 TagLib::Tag *MPEG::File::tag() const
126 MPEG::Properties *MPEG::File::audioProperties() const
128 return d->properties;
131 bool MPEG::File::save()
133 return save(AllTags);
136 bool MPEG::File::save(int tags)
138 return save(tags, true);
141 bool MPEG::File::save(int tags, bool stripOthers)
143 if(tags == NoTags && stripOthers)
144 return strip(AllTags);
146 if(!ID3v2Tag() && !ID3v1Tag() && !APETag()) {
148 if((d->hasID3v1 || d->hasID3v2 || d->hasAPE) && stripOthers)
149 return strip(AllTags);
155 debug("MPEG::File::save() -- File is read only.");
159 // Create the tags if we've been asked to. Copy the values from the tag that
160 // does exist into the new tag.
162 if((tags & ID3v2) && ID3v1Tag())
163 Tag::duplicate(ID3v1Tag(), ID3v2Tag(true), false);
165 if((tags & ID3v1) && d->tag[ID3v2Index])
166 Tag::duplicate(ID3v2Tag(), ID3v1Tag(true), false);
172 if(ID3v2Tag() && !ID3v2Tag()->isEmpty()) {
175 d->ID3v2Location = 0;
177 insert(ID3v2Tag()->render(), d->ID3v2Location, d->ID3v2OriginalSize);
181 // v1 tag location has changed, update if it exists
184 d->ID3v1Location = findID3v1();
186 // APE tag location has changed, update if it exists
192 success = strip(ID3v2, false) && success;
194 else if(d->hasID3v2 && stripOthers)
195 success = strip(ID3v2) && success;
198 if(ID3v1Tag() && !ID3v1Tag()->isEmpty()) {
199 int offset = d->hasID3v1 ? -128 : 0;
201 writeBlock(ID3v1Tag()->render());
203 d->ID3v1Location = findID3v1();
206 success = strip(ID3v1) && success;
208 else if(d->hasID3v1 && stripOthers)
209 success = strip(ID3v1, false) && success;
211 // Dont save an APE-tag unless one has been created
213 if((APE & tags) && APETag()) {
215 insert(APETag()->render(), d->APELocation, d->APEOriginalSize);
218 insert(APETag()->render(), d->ID3v1Location, 0);
219 d->APEOriginalSize = APETag()->footer()->completeTagSize();
221 d->APELocation = d->ID3v1Location;
222 d->ID3v1Location += d->APEOriginalSize;
226 d->APELocation = tell();
227 d->APEFooterLocation = d->APELocation
228 + d->tag.access<APE::Tag>(APEIndex, false)->footer()->completeTagSize()
229 - APE::Footer::size();
230 writeBlock(APETag()->render());
231 d->APEOriginalSize = APETag()->footer()->completeTagSize();
236 else if(d->hasAPE && stripOthers)
237 success = strip(APE, false) && success;
242 ID3v2::Tag *MPEG::File::ID3v2Tag(bool create)
244 return d->tag.access<ID3v2::Tag>(ID3v2Index, create);
247 ID3v1::Tag *MPEG::File::ID3v1Tag(bool create)
249 return d->tag.access<ID3v1::Tag>(ID3v1Index, create);
252 APE::Tag *MPEG::File::APETag(bool create)
254 return d->tag.access<APE::Tag>(APEIndex, create);
257 bool MPEG::File::strip(int tags)
259 return strip(tags, true);
262 bool MPEG::File::strip(int tags, bool freeMemory)
265 debug("MPEG::File::strip() - Cannot strip tags from a read only file.");
269 if((tags & ID3v2) && d->hasID3v2) {
270 removeBlock(d->ID3v2Location, d->ID3v2OriginalSize);
271 d->ID3v2Location = -1;
272 d->ID3v2OriginalSize = 0;
276 d->tag.set(ID3v2Index, 0);
278 // v1 tag location has changed, update if it exists
281 d->ID3v1Location = findID3v1();
283 // APE tag location has changed, update if it exists
289 if((tags & ID3v1) && d->hasID3v1) {
290 truncate(d->ID3v1Location);
291 d->ID3v1Location = -1;
295 d->tag.set(ID3v1Index, 0);
298 if((tags & APE) && d->hasAPE) {
299 removeBlock(d->APELocation, d->APEOriginalSize);
301 d->APEFooterLocation = -1;
304 if(d->ID3v1Location > d->APELocation)
305 d->ID3v1Location -= d->APEOriginalSize;
309 d->tag.set(APEIndex, 0);
315 void MPEG::File::setID3v2FrameFactory(const ID3v2::FrameFactory *factory)
317 d->ID3v2FrameFactory = factory;
320 long MPEG::File::nextFrameOffset(long position)
322 bool foundLastSyncPattern = false;
328 buffer = readBlock(bufferSize());
330 if(buffer.size() <= 0)
333 if(foundLastSyncPattern && secondSynchByte(buffer[0]))
336 for(uint i = 0; i < buffer.size() - 1; i++) {
337 if(uchar(buffer[i]) == 0xff && secondSynchByte(buffer[i + 1]))
341 foundLastSyncPattern = uchar(buffer[buffer.size() - 1]) == 0xff;
342 position += buffer.size();
346 long MPEG::File::previousFrameOffset(long position)
348 bool foundFirstSyncPattern = false;
351 while (position > 0) {
352 long size = ulong(position) < bufferSize() ? position : bufferSize();
356 buffer = readBlock(size);
358 if(buffer.size() <= 0)
361 if(foundFirstSyncPattern && uchar(buffer[buffer.size() - 1]) == 0xff)
362 return position + buffer.size() - 1;
364 for(int i = buffer.size() - 2; i >= 0; i--) {
365 if(uchar(buffer[i]) == 0xff && secondSynchByte(buffer[i + 1]))
369 foundFirstSyncPattern = secondSynchByte(buffer[0]);
374 long MPEG::File::firstFrameOffset()
379 position = d->ID3v2Location + ID3v2Tag()->header()->completeTagSize();
381 return nextFrameOffset(position);
384 long MPEG::File::lastFrameOffset()
386 return previousFrameOffset(ID3v1Tag() ? d->ID3v1Location - 1 : length());
389 ////////////////////////////////////////////////////////////////////////////////
391 ////////////////////////////////////////////////////////////////////////////////
393 void MPEG::File::read(bool readProperties, Properties::ReadStyle propertiesStyle)
395 // Look for an ID3v2 tag
397 d->ID3v2Location = findID3v2();
399 if(d->ID3v2Location >= 0) {
401 d->tag.set(ID3v2Index, new ID3v2::Tag(this, d->ID3v2Location, d->ID3v2FrameFactory));
403 d->ID3v2OriginalSize = ID3v2Tag()->header()->completeTagSize();
405 if(ID3v2Tag()->header()->tagSize() <= 0)
406 d->tag.set(ID3v2Index, 0);
411 // Look for an ID3v1 tag
413 d->ID3v1Location = findID3v1();
415 if(d->ID3v1Location >= 0) {
416 d->tag.set(ID3v1Index, new ID3v1::Tag(this, d->ID3v1Location));
420 // Look for an APE tag
424 if(d->APELocation >= 0) {
426 d->tag.set(APEIndex, new APE::Tag(this, d->APEFooterLocation));
427 d->APEOriginalSize = APETag()->footer()->completeTagSize();
432 d->properties = new Properties(this, propertiesStyle);
434 // Make sure that we have our default tag types available.
440 long MPEG::File::findID3v2()
442 // This method is based on the contents of TagLib::File::find(), but because
443 // of some subtlteies -- specifically the need to look for the bit pattern of
444 // an MPEG sync, it has been modified for use here.
446 if(isValid() && ID3v2::Header::fileIdentifier().size() <= bufferSize()) {
448 // The position in the file that the current buffer starts at.
450 long bufferOffset = 0;
453 // These variables are used to keep track of a partial match that happens at
454 // the end of a buffer.
456 int previousPartialMatch = -1;
457 bool previousPartialSynchMatch = false;
459 // Save the location of the current read pointer. We will restore the
460 // position using seek() before all returns.
462 long originalPosition = tell();
464 // Start the search at the beginning of the file.
468 // This loop is the crux of the find method. There are three cases that we
469 // want to account for:
470 // (1) The previously searched buffer contained a partial match of the search
471 // pattern and we want to see if the next one starts with the remainder of
474 // (2) The search pattern is wholly contained within the current buffer.
476 // (3) The current buffer ends with a partial match of the pattern. We will
477 // note this for use in the next itteration, where we will check for the rest
480 for(buffer = readBlock(bufferSize()); buffer.size() > 0; buffer = readBlock(bufferSize())) {
482 // (1) previous partial match
484 if(previousPartialSynchMatch && secondSynchByte(buffer[0]))
487 if(previousPartialMatch >= 0 && int(bufferSize()) > previousPartialMatch) {
488 const int patternOffset = (bufferSize() - previousPartialMatch);
489 if(buffer.containsAt(ID3v2::Header::fileIdentifier(), 0, patternOffset)) {
490 seek(originalPosition);
491 return bufferOffset - bufferSize() + previousPartialMatch;
495 // (2) pattern contained in current buffer
497 long location = buffer.find(ID3v2::Header::fileIdentifier());
499 seek(originalPosition);
500 return bufferOffset + location;
503 int firstSynchByte = buffer.find(char(uchar(255)));
505 // Here we have to loop because there could be several of the first
506 // (11111111) byte, and we want to check all such instances until we find
507 // a full match (11111111 111) or hit the end of the buffer.
509 while(firstSynchByte >= 0) {
511 // if this *is not* at the end of the buffer
513 if(firstSynchByte < int(buffer.size()) - 1) {
514 if(secondSynchByte(buffer[firstSynchByte + 1])) {
515 // We've found the frame synch pattern.
516 seek(originalPosition);
521 // We found 11111111 at the end of the current buffer indicating a
522 // partial match of the synch pattern. The find() below should
523 // return -1 and break out of the loop.
525 previousPartialSynchMatch = true;
529 // Check in the rest of the buffer.
531 firstSynchByte = buffer.find(char(uchar(255)), firstSynchByte + 1);
536 previousPartialMatch = buffer.endsWithPartialMatch(ID3v2::Header::fileIdentifier());
538 bufferOffset += bufferSize();
541 // Since we hit the end of the file, reset the status before continuing.
545 seek(originalPosition);
551 long MPEG::File::findID3v1()
557 if(readBlock(3) == ID3v1::Tag::fileIdentifier())
563 void MPEG::File::findAPE()
566 seek(d->hasID3v1 ? -160 : -32, End);
570 if(readBlock(8) == APE::Tag::fileIdentifier()) {
571 d->APEFooterLocation = p;
572 seek(d->APEFooterLocation);
573 APE::Footer footer(readBlock(APE::Footer::size()));
574 d->APELocation = d->APEFooterLocation - footer.completeTagSize()
575 + APE::Footer::size();
581 d->APEFooterLocation = -1;
584 bool MPEG::File::secondSynchByte(char byte)
586 if(uchar(byte) == 0xff)
589 std::bitset<8> b(byte);
591 // check to see if the byte matches 111xxxxx
592 return b.test(7) && b.test(6) && b.test(5);