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);