Merge branch 'master' into merge-master-to-taglib2

# Conflicts:
#	taglib/ape/apefile.cpp
#	taglib/flac/flacfile.cpp
#	taglib/mpc/mpcfile.cpp
#	taglib/mpeg/mpegfile.cpp
#	taglib/mpeg/mpegfile.h
#	taglib/trueaudio/trueaudiofile.cpp
#	taglib/wavpack/wavpackfile.cpp
This commit is contained in:
Tsuda Kageyu 2015-12-21 14:56:13 +09:00
commit ed0305bd3f
22 changed files with 666 additions and 560 deletions

View File

@ -243,7 +243,7 @@ check_cxx_source_compiles("
int main() {
_strdup(0);
return 0;
}
}
" HAVE_ISO_STRDUP)
# Determine whether zlib is installed.

5
NEWS
View File

@ -10,11 +10,16 @@
* Better handling of duplicate ID3v2 tags in all kinds of files.
* Better handling of duplicate tag chunks in WAV files.
* Better handling of duplicate tag chunks in AIFF files.
* Better handling of duplicate Vorbis comment blocks in FLAC files.
* Fixed crash when calling File::properties() after strip().
* Fixed crash when parsing certain MPEG files.
* Fixed possible file corruptions when saving ASF files.
* Fixed possible file corruptions when saving FLAC files.
* Fixed possible file corruptions when saving MP4 files.
* Fixed possible file corruptions when saving MPEG files.
* Fixed possible file corruptions when saving APE files.
* Fixed possible file corruptions when saving Musepack files.
* Fixed possible file corruptions when saving WavPack files.
* Fixed updating the comment field of Vorbis comments.
* Fixed reading date and time in ID3v2.3 tags.
* Replaced ENABLE_STATIC build option with BUILD_SHARED_LIBS.

View File

@ -29,10 +29,10 @@
#cmakedefine HAVE_VSNPRINTF 1
#cmakedefine HAVE_VSPRINTF_S 1
/* Defined if your compiler supports ISO _strdup. */
/* Defined if your compiler supports ISO _strdup */
#cmakedefine HAVE_ISO_STRDUP 1
/* Defined if you have libz */
/* Defined if zlib is installed */
#cmakedefine HAVE_ZLIB 1
/* Indicates whether debug messages are shown even in release mode */

View File

@ -61,10 +61,7 @@ public:
ID3v2Header(0),
ID3v2Location(-1),
ID3v2Size(0),
properties(0),
hasAPE(false),
hasID3v1(false),
hasID3v2(false) {}
properties(0) {}
~FilePrivate()
{
@ -72,25 +69,18 @@ public:
delete properties;
}
long long APELocation;
unsigned int APESize;
long long APELocation;
long long APESize;
long long ID3v1Location;
ID3v2::Header *ID3v2Header;
long long ID3v2Location;
unsigned int ID3v2Size;
long long ID3v2Location;
long long ID3v2Size;
DoubleTagUnion tag;
AudioProperties *properties;
// These indicate whether the file *on disk* has these tags, not if
// this data structure does. This is used in computing offsets.
bool hasAPE;
bool hasID3v1;
bool hasID3v2;
};
////////////////////////////////////////////////////////////////////////////////
@ -145,64 +135,67 @@ bool APE::File::save()
// Update ID3v1 tag
if(ID3v1Tag()) {
if(d->hasID3v1) {
if(ID3v1Tag() && !ID3v1Tag()->isEmpty()) {
// ID3v1 tag is not empty. Update the old one or create a new one.
if(d->ID3v1Location >= 0) {
seek(d->ID3v1Location);
writeBlock(ID3v1Tag()->render());
}
else {
seek(0, End);
d->ID3v1Location = tell();
writeBlock(ID3v1Tag()->render());
d->hasID3v1 = true;
}
writeBlock(ID3v1Tag()->render());
}
else {
if(d->hasID3v1) {
removeBlock(d->ID3v1Location, 128);
d->hasID3v1 = false;
if(d->hasAPE) {
if(d->APELocation > d->ID3v1Location)
d->APELocation -= 128;
}
// ID3v1 tag is empty. Remove the old one.
if(d->ID3v1Location >= 0) {
truncate(d->ID3v1Location);
d->ID3v1Location = -1;
}
}
// Update APE tag
if(APETag()) {
if(d->hasAPE)
insert(APETag()->render(), d->APELocation, d->APESize);
else {
if(d->hasID3v1) {
insert(APETag()->render(), d->ID3v1Location, 0);
d->APESize = APETag()->footer()->completeTagSize();
d->hasAPE = true;
if(APETag() && !APETag()->isEmpty()) {
// APE tag is not empty. Update the old one or create a new one.
if(d->APELocation < 0) {
if(d->ID3v1Location >= 0)
d->APELocation = d->ID3v1Location;
d->ID3v1Location += d->APESize;
}
else {
seek(0, End);
d->APELocation = tell();
writeBlock(APETag()->render());
d->APESize = APETag()->footer()->completeTagSize();
d->hasAPE = true;
}
else
d->APELocation = length();
}
const ByteVector data = APETag()->render();
insert(data, d->APELocation, static_cast<size_t>(d->APESize));
if(d->ID3v1Location >= 0)
d->ID3v1Location += (static_cast<long>(data.size()) - d->APESize);
d->APESize = data.size();
}
else {
if(d->hasAPE) {
removeBlock(d->APELocation, d->APESize);
d->hasAPE = false;
if(d->hasID3v1) {
if(d->ID3v1Location > d->APELocation) {
d->ID3v1Location -= d->APESize;
}
}
// APE tag is empty. Remove the old one.
if(d->APELocation >= 0) {
removeBlock(d->APELocation, static_cast<size_t>(d->APESize));
if(d->ID3v1Location >= 0)
d->ID3v1Location -= d->APESize;
d->APELocation = -1;
d->APESize = 0;
}
}
return true;
return true;
}
ID3v1::Tag *APE::File::ID3v1Tag(bool create)
@ -217,27 +210,24 @@ APE::Tag *APE::File::APETag(bool create)
void APE::File::strip(int tags)
{
if(tags & ID3v1) {
if(tags & ID3v1)
d->tag.set(ApeID3v1Index, 0);
APETag(true);
}
if(tags & APE) {
if(tags & APE)
d->tag.set(ApeAPEIndex, 0);
if(!ID3v1Tag())
APETag(true);
}
if(!ID3v1Tag())
APETag(true);
}
bool APE::File::hasAPETag() const
{
return d->hasAPE;
return (d->APELocation >= 0);
}
bool APE::File::hasID3v1Tag() const
{
return d->hasID3v1;
return (d->ID3v1Location >= 0);
}
////////////////////////////////////////////////////////////////////////////////
@ -254,17 +244,14 @@ void APE::File::read(bool readProperties)
seek(d->ID3v2Location);
d->ID3v2Header = new ID3v2::Header(readBlock(ID3v2::Header::size()));
d->ID3v2Size = d->ID3v2Header->completeTagSize();
d->hasID3v2 = true;
}
// Look for an ID3v1 tag
d->ID3v1Location = Utils::findID3v1(this);
if(d->ID3v1Location >= 0) {
if(d->ID3v1Location >= 0)
d->tag.set(ApeID3v1Index, new ID3v1::Tag(this, d->ID3v1Location));
d->hasID3v1 = true;
}
// Look for an APE tag
@ -274,10 +261,9 @@ void APE::File::read(bool readProperties)
d->tag.set(ApeAPEIndex, new APE::Tag(this, d->APELocation));
d->APESize = APETag()->footer()->completeTagSize();
d->APELocation = d->APELocation + APE::Footer::size() - d->APESize;
d->hasAPE = true;
}
if(!d->hasID3v1)
if(d->ID3v1Location < 0)
APETag(true);
// Look for APE audio properties
@ -286,14 +272,14 @@ void APE::File::read(bool readProperties)
long long streamLength;
if(d->hasAPE)
if(d->APELocation >= 0)
streamLength = d->APELocation;
else if(d->hasID3v1)
else if(d->ID3v1Location >= 0)
streamLength = d->ID3v1Location;
else
streamLength = length();
if(d->hasID3v2) {
if(d->ID3v2Location >= 0) {
seek(d->ID3v2Location + d->ID3v2Size);
streamLength -= (d->ID3v2Location + d->ID3v2Size);
}

View File

@ -133,9 +133,6 @@ namespace TagLib {
*
* \note According to the official Monkey's Audio SDK, an APE file
* can only have either ID3V1 or APE tags, so a parameter is used here.
*
* \warning In the current implementation, it's dangerous to call save()
* repeatedly. At worst it will corrupt the file.
*/
virtual bool save();

View File

@ -82,8 +82,8 @@ public:
}
const ID3v2::FrameFactory *ID3v2FrameFactory;
long long ID3v2Location;
unsigned int ID3v2OriginalSize;
long long ID3v2Location;
long long ID3v2OriginalSize;
long long ID3v1Location;
@ -160,21 +160,16 @@ bool FLAC::File::save()
// Replace metadata blocks
bool foundVorbisCommentBlock = false;
for(BlockIterator it = d->blocks.begin(); it != d->blocks.end(); ++it) {
if((*it)->code() == MetadataBlock::VorbisComment) {
// Set the new Vorbis Comment block
delete *it;
*it = new UnknownMetadataBlock(MetadataBlock::VorbisComment, d->xiphCommentData);
foundVorbisCommentBlock = true;
d->blocks.erase(it);
break;
}
}
if(!foundVorbisCommentBlock) {
d->blocks.append(new UnknownMetadataBlock(MetadataBlock::VorbisComment, d->xiphCommentData));
foundVorbisCommentBlock = true;
}
d->blocks.append(new UnknownMetadataBlock(MetadataBlock::VorbisComment, d->xiphCommentData));
// Render data for the metadata blocks
@ -216,10 +211,10 @@ bool FLAC::File::save()
insert(data, d->flacStart, static_cast<size_t>(originalLength));
d->streamStart += (data.size() - originalLength);
d->streamStart += (static_cast<long>(data.size()) - originalLength);
if(d->ID3v1Location >= 0)
d->ID3v1Location += (data.size() - originalLength);
d->ID3v1Location += (static_cast<long>(data.size()) - originalLength);
// Update ID3 tags
@ -231,13 +226,13 @@ bool FLAC::File::save()
d->ID3v2Location = 0;
data = ID3v2Tag()->render();
insert(data, d->ID3v2Location, d->ID3v2OriginalSize);
insert(data, d->ID3v2Location, static_cast<size_t>(d->ID3v2OriginalSize));
d->flacStart += (data.size() - d->ID3v2OriginalSize);
d->streamStart += (data.size() - d->ID3v2OriginalSize);
d->flacStart += (static_cast<long>(data.size()) - d->ID3v2OriginalSize);
d->streamStart += (static_cast<long>(data.size()) - d->ID3v2OriginalSize);
if(d->ID3v1Location >= 0)
d->ID3v1Location += (data.size() - d->ID3v2OriginalSize);
d->ID3v1Location += (static_cast<long>(data.size()) - d->ID3v2OriginalSize);
d->ID3v2OriginalSize = data.size();
}
@ -246,7 +241,7 @@ bool FLAC::File::save()
// ID3v2 tag is empty. Remove the old one.
if(d->ID3v2Location >= 0) {
removeBlock(d->ID3v2Location, d->ID3v2OriginalSize);
removeBlock(d->ID3v2Location, static_cast<size_t>(d->ID3v2OriginalSize));
d->flacStart -= d->ID3v2OriginalSize;
d->streamStart -= d->ID3v2OriginalSize;
@ -438,51 +433,43 @@ void FLAC::File::scan()
nextBlockOffset += 4;
d->flacStart = nextBlockOffset;
seek(nextBlockOffset);
while(true) {
ByteVector header = readBlock(4);
seek(nextBlockOffset);
const ByteVector header = readBlock(4);
// Header format (from spec):
// <1> Last-metadata-block flag
// <7> BLOCK_TYPE
// 0 : STREAMINFO
// 1 : PADDING
// ..
// 4 : VORBIS_COMMENT
// ..
// <24> Length of metadata to follow
// Header format (from spec):
// <1> Last-metadata-block flag
// <7> BLOCK_TYPE
// 0 : STREAMINFO
// 1 : PADDING
// ..
// 4 : VORBIS_COMMENT
// ..
// 6 : PICTURE
// ..
// <24> Length of metadata to follow
char blockType = header[0] & 0x7f;
bool isLastBlock = (header[0] & 0x80) != 0;
size_t length = header.toUInt24BE(1);
const char blockType = header[0] & ~LastBlockFlag;
const bool isLastBlock = (header[0] & LastBlockFlag) != 0;
const size_t blockLength = header.toUInt24BE(1);
// First block should be the stream_info metadata
// First block should be the stream_info metadata
if(blockType != MetadataBlock::StreamInfo) {
debug("FLAC::File::scan() -- invalid FLAC stream");
setValid(false);
return;
}
if(d->blocks.isEmpty() && blockType != MetadataBlock::StreamInfo) {
debug("FLAC::File::scan() -- First block should be the stream_info metadata");
setValid(false);
return;
}
d->blocks.append(new UnknownMetadataBlock(blockType, readBlock(length)));
nextBlockOffset += length + 4;
// Search through the remaining metadata
while(!isLastBlock) {
header = readBlock(4);
blockType = header[0] & 0x7f;
isLastBlock = (header[0] & 0x80) != 0;
length = header.toUInt24BE(1);
if(length == 0 && blockType != MetadataBlock::Padding) {
if(blockLength == 0 && blockType != MetadataBlock::Padding) {
debug("FLAC::File::scan() -- Zero-sized metadata block found");
setValid(false);
return;
}
const ByteVector data = readBlock(length);
if(data.size() != length) {
const ByteVector data = readBlock(blockLength);
if(data.size() != blockLength) {
debug("FLAC::File::scan() -- Failed to read a metadata block");
setValid(false);
return;
@ -494,9 +481,10 @@ void FLAC::File::scan()
if(blockType == MetadataBlock::VorbisComment) {
if(d->xiphCommentData.isEmpty()) {
d->xiphCommentData = data;
block = new UnknownMetadataBlock(MetadataBlock::VorbisComment, data);
}
else {
debug("FLAC::File::scan() -- multiple Vorbis Comment blocks found, using the first one");
debug("FLAC::File::scan() -- multiple Vorbis Comment blocks found, discarding");
}
}
else if(blockType == MetadataBlock::Picture) {
@ -509,25 +497,20 @@ void FLAC::File::scan()
delete picture;
}
}
if(!block) {
block = new UnknownMetadataBlock(blockType, data);
}
if(block->code() != MetadataBlock::Padding) {
d->blocks.append(block);
else if(blockType == MetadataBlock::Padding) {
// Skip all padding blocks.
}
else {
delete block;
block = new UnknownMetadataBlock(blockType, data);
}
nextBlockOffset += length + 4;
if(block)
d->blocks.append(block);
if(nextBlockOffset >= File::length()) {
debug("FLAC::File::scan() -- FLAC stream corrupted");
setValid(false);
return;
}
seek(nextBlockOffset);
nextBlockOffset += blockLength + 4;
if(isLastBlock)
break;
}
// End of metadata, now comes the datastream

View File

@ -53,10 +53,7 @@ public:
ID3v2Header(0),
ID3v2Location(-1),
ID3v2Size(0),
properties(0),
hasAPE(false),
hasID3v1(false),
hasID3v2(false) {}
properties(0) {}
~FilePrivate()
{
@ -64,25 +61,18 @@ public:
delete properties;
}
long long APELocation;
unsigned int APESize;
long long APELocation;
long long APESize;
long long ID3v1Location;
ID3v2::Header *ID3v2Header;
long long ID3v2Location;
unsigned int ID3v2Size;
long long ID3v2Location;
long long ID3v2Size;
DoubleTagUnion tag;
AudioProperties *properties;
// These indicate whether the file *on disk* has these tags, not if
// this data structure does. This is used in computing offsets.
bool hasAPE;
bool hasID3v1;
bool hasID3v2;
};
////////////////////////////////////////////////////////////////////////////////
@ -137,69 +127,80 @@ bool MPC::File::save()
// Possibly strip ID3v2 tag
if(d->hasID3v2 && !d->ID3v2Header) {
removeBlock(d->ID3v2Location, d->ID3v2Size);
d->hasID3v2 = false;
if(d->hasID3v1)
d->ID3v1Location -= d->ID3v2Size;
if(d->hasAPE)
if(!d->ID3v2Header && d->ID3v2Location >= 0) {
removeBlock(d->ID3v2Location, static_cast<size_t>(d->ID3v2Size));
if(d->APELocation >= 0)
d->APELocation -= d->ID3v2Size;
if(d->ID3v1Location >= 0)
d->ID3v1Location -= d->ID3v2Size;
d->ID3v2Location = -1;
d->ID3v2Size = 0;
}
// Update ID3v1 tag
if(ID3v1Tag()) {
if(d->hasID3v1) {
if(ID3v1Tag() && !ID3v1Tag()->isEmpty()) {
// ID3v1 tag is not empty. Update the old one or create a new one.
if(d->ID3v1Location >= 0) {
seek(d->ID3v1Location);
writeBlock(ID3v1Tag()->render());
}
else {
seek(0, End);
d->ID3v1Location = tell();
writeBlock(ID3v1Tag()->render());
d->hasID3v1 = true;
}
} else
if(d->hasID3v1) {
removeBlock(d->ID3v1Location, 128);
d->hasID3v1 = false;
if(d->hasAPE) {
if(d->APELocation > d->ID3v1Location)
d->APELocation -= 128;
}
writeBlock(ID3v1Tag()->render());
}
else {
// ID3v1 tag is empty. Remove the old one.
if(d->ID3v1Location >= 0) {
truncate(d->ID3v1Location);
d->ID3v1Location = -1;
}
}
// Update APE tag
if(APETag()) {
if(d->hasAPE)
insert(APETag()->render(), d->APELocation, d->APESize);
else {
if(d->hasID3v1) {
insert(APETag()->render(), d->ID3v1Location, 0);
d->APESize = APETag()->footer()->completeTagSize();
d->hasAPE = true;
if(APETag() && !APETag()->isEmpty()) {
// APE tag is not empty. Update the old one or create a new one.
if(d->APELocation < 0) {
if(d->ID3v1Location >= 0)
d->APELocation = d->ID3v1Location;
d->ID3v1Location += d->APESize;
}
else {
seek(0, End);
d->APELocation = tell();
writeBlock(APETag()->render());
d->APESize = APETag()->footer()->completeTagSize();
d->hasAPE = true;
}
else
d->APELocation = length();
}
const ByteVector data = APETag()->render();
insert(data, d->APELocation, static_cast<size_t>(d->APESize));
if(d->ID3v1Location >= 0)
d->ID3v1Location += (static_cast<long>(data.size()) - d->APESize);
d->APESize = data.size();
}
else {
// APE tag is empty. Remove the old one.
if(d->APELocation >= 0) {
removeBlock(d->APELocation, static_cast<size_t>(d->APESize));
if(d->ID3v1Location >= 0)
d->ID3v1Location -= d->APESize;
d->APELocation = -1;
d->APESize = 0;
}
}
else
if(d->hasAPE) {
removeBlock(d->APELocation, d->APESize);
d->hasAPE = false;
if(d->hasID3v1) {
if(d->ID3v1Location > d->APELocation)
d->ID3v1Location -= d->APESize;
}
}
return true;
}
@ -216,22 +217,19 @@ APE::Tag *MPC::File::APETag(bool create)
void MPC::File::strip(int tags)
{
if(tags & ID3v1) {
if(tags & ID3v1)
d->tag.set(MPCID3v1Index, 0);
if(tags & APE)
d->tag.set(MPCAPEIndex, 0);
if(!ID3v1Tag())
APETag(true);
}
if(tags & ID3v2) {
delete d->ID3v2Header;
d->ID3v2Header = 0;
}
if(tags & APE) {
d->tag.set(MPCAPEIndex, 0);
if(!ID3v1Tag())
APETag(true);
}
}
void MPC::File::remove(int tags)
@ -241,12 +239,12 @@ void MPC::File::remove(int tags)
bool MPC::File::hasID3v1Tag() const
{
return d->hasID3v1;
return (d->ID3v1Location >= 0);
}
bool MPC::File::hasAPETag() const
{
return d->hasAPE;
return (d->APELocation >= 0);
}
////////////////////////////////////////////////////////////////////////////////
@ -255,30 +253,6 @@ bool MPC::File::hasAPETag() const
void MPC::File::read(bool readProperties)
{
// Look for an ID3v1 tag
d->ID3v1Location = Utils::findID3v1(this);
if(d->ID3v1Location >= 0) {
d->tag.set(MPCID3v1Index, new ID3v1::Tag(this, d->ID3v1Location));
d->hasID3v1 = true;
}
// Look for an APE tag
d->APELocation = Utils::findAPE(this, d->ID3v1Location);
if(d->APELocation >= 0) {
d->tag.set(MPCAPEIndex, new APE::Tag(this, d->APELocation));
d->APESize = APETag()->footer()->completeTagSize();
d->APELocation = d->APELocation + APE::Footer::size() - d->APESize;
d->hasAPE = true;
}
if(!d->hasID3v1)
APETag(true);
// Look for an ID3v2 tag
d->ID3v2Location = Utils::findID3v2(this);
@ -287,23 +261,42 @@ void MPC::File::read(bool readProperties)
seek(d->ID3v2Location);
d->ID3v2Header = new ID3v2::Header(readBlock(ID3v2::Header::size()));
d->ID3v2Size = d->ID3v2Header->completeTagSize();
d->hasID3v2 = true;
}
// Look for an ID3v1 tag
d->ID3v1Location = Utils::findID3v1(this);
if(d->ID3v1Location >= 0)
d->tag.set(MPCID3v1Index, new ID3v1::Tag(this, d->ID3v1Location));
// Look for an APE tag
d->APELocation = Utils::findAPE(this, d->ID3v1Location);
if(d->APELocation >= 0) {
d->tag.set(MPCAPEIndex, new APE::Tag(this, d->APELocation));
d->APESize = APETag()->footer()->completeTagSize();
d->APELocation = d->APELocation + APE::Footer::size() - d->APESize;
}
if(d->ID3v1Location < 0)
APETag(true);
// Look for MPC metadata
if(readProperties) {
long long streamLength;
if(d->hasAPE)
if(d->APELocation >= 0)
streamLength = d->APELocation;
else if(d->hasID3v1)
else if(d->ID3v1Location >= 0)
streamLength = d->ID3v1Location;
else
streamLength = length();
if(d->hasID3v2) {
if(d->ID3v2Location >= 0) {
seek(d->ID3v2Location + d->ID3v2Size);
streamLength -= (d->ID3v2Location + d->ID3v2Size);
}

View File

@ -132,9 +132,6 @@ namespace TagLib {
* Saves the file.
*
* This returns true if the save was successful.
*
* \warning In the current implementation, it's dangerous to call save()
* repeatedly. At worst it will corrupt the file.
*/
virtual bool save();

View File

@ -265,17 +265,19 @@ ByteVector Frame::fieldData(const ByteVector &frameData) const
if(inflateInit(&stream) != Z_OK)
return ByteVector();
stream.avail_in = (uLongf) frameData.size() - frameDataOffset;
stream.next_in = (Bytef *) frameData.data() + frameDataOffset;
ByteVector inData = frameData;
stream.avail_in = static_cast<uInt>(inData.size() - frameDataOffset);
stream.next_in = reinterpret_cast<Bytef *>(inData.data() + frameDataOffset);
static const size_t chunkSize = 1024;
ByteVector data;
ByteVector outData;
ByteVector chunk(chunkSize);
do {
stream.avail_out = (uLongf) chunk.size();
stream.next_out = (Bytef *) chunk.data();
stream.avail_out = static_cast<uInt>(chunk.size());
stream.next_out = reinterpret_cast<Bytef *>(chunk.data());
int result = inflate(&stream, Z_NO_FLUSH);
@ -290,15 +292,15 @@ ByteVector Frame::fieldData(const ByteVector &frameData) const
return ByteVector();
}
data.append(stream.avail_out == 0 ? chunk : chunk.mid(0, chunk.size() - stream.avail_out));
outData.append(stream.avail_out == 0 ? chunk : chunk.mid(0, chunk.size() - stream.avail_out));
} while(stream.avail_out == 0);
inflateEnd(&stream);
if(frameDataLength != data.size())
if(frameDataLength != outData.size())
debug("frameDataLength does not match the data length returned by zlib");
return data;
return outData;
}
else
#endif

View File

@ -47,9 +47,9 @@ namespace TagLib {
* Reimplementing this factory is the key to adding support for frame types
* not directly supported by TagLib to your application. To do so you would
* subclass this factory reimplement createFrame(). Then by setting your
* factory to be the default factory in ID3v2::Tag constructor or with
* MPEG::File::setID3v2FrameFactory() you can implement behavior that will
* allow for new ID3v2::Frame subclasses (also provided by you) to be used.
* factory to be the default factory in ID3v2::Tag constructor you can
* implement behavior that will allow for new ID3v2::Frame subclasses (also
* provided by you) to be used.
*
* This implements both <i>abstract factory</i> and <i>singleton</i> patterns
* of which more information is available on the web and in software design

View File

@ -24,6 +24,7 @@
***************************************************************************/
#include <tagunion.h>
#include <tagutils.h>
#include <id3v2tag.h>
#include <id3v2header.h>
#include <id3v1tag.h>
@ -64,20 +65,14 @@ namespace
class MPEG::File::FilePrivate
{
public:
FilePrivate(ID3v2::FrameFactory *frameFactory = ID3v2::FrameFactory::instance()) :
FilePrivate(const ID3v2::FrameFactory *frameFactory = ID3v2::FrameFactory::instance()) :
ID3v2FrameFactory(frameFactory),
ID3v2Location(-1),
ID3v2OriginalSize(0),
APELocation(-1),
APEFooterLocation(-1),
APEOriginalSize(0),
ID3v1Location(-1),
hasID3v2(false),
hasID3v1(false),
hasAPE(false),
properties(0)
{
}
properties(0) {}
~FilePrivate()
{
@ -86,24 +81,16 @@ public:
const ID3v2::FrameFactory *ID3v2FrameFactory;
long long ID3v2Location;
unsigned int ID3v2OriginalSize;
long long ID3v2Location;
long long ID3v2OriginalSize;
long long APELocation;
long long APEFooterLocation;
unsigned int APEOriginalSize;
long long APELocation;
long long APEOriginalSize;
long long ID3v1Location;
TripleTagUnion tag;
// These indicate whether the file *on disk* has these tags, not if
// this data structure does. This is used in computing offsets.
bool hasID3v2;
bool hasID3v1;
bool hasAPE;
AudioProperties *properties;
};
@ -169,17 +156,6 @@ bool MPEG::File::save()
bool MPEG::File::save(int tags, bool stripOthers, int id3v2Version, bool duplicateTags)
{
if(tags == NoTags && stripOthers)
return strip(AllTags);
if(!ID3v2Tag() && !ID3v1Tag() && !APETag()) {
if((d->hasID3v1 || d->hasID3v2 || d->hasAPE) && stripOthers)
return strip(AllTags);
return true;
}
if(readOnly()) {
debug("MPEG::File::save() -- File is read only.");
return false;
@ -187,7 +163,7 @@ bool MPEG::File::save(int tags, bool stripOthers, int id3v2Version, bool duplica
// Create the tags if we've been asked to.
if (duplicateTags) {
if(duplicateTags) {
// Copy the values from the tag that does exist into the new tag,
// except if the existing tag is to be stripped.
@ -199,79 +175,93 @@ bool MPEG::File::save(int tags, bool stripOthers, int id3v2Version, bool duplica
Tag::duplicate(ID3v2Tag(), ID3v1Tag(true), false);
}
bool success = true;
// Remove all the tags not going to be saved.
if(stripOthers)
strip(~tags, false);
if(ID3v2 & tags) {
if(ID3v2Tag() && !ID3v2Tag()->isEmpty()) {
if(!d->hasID3v2)
// ID3v2 tag is not empty. Update the old one or create a new one.
if(d->ID3v2Location < 0)
d->ID3v2Location = 0;
insert(ID3v2Tag()->render(id3v2Version), d->ID3v2Location, d->ID3v2OriginalSize);
const ByteVector data = ID3v2Tag()->render(id3v2Version);
insert(data, d->ID3v2Location, static_cast<size_t>(d->ID3v2OriginalSize));
d->hasID3v2 = true;
if(d->APELocation >= 0)
d->APELocation += (static_cast<long>(data.size()) - d->ID3v2OriginalSize);
// v1 tag location has changed, update if it exists
if(d->ID3v1Location >= 0)
d->ID3v1Location += (static_cast<long>(data.size()) - d->ID3v2OriginalSize);
if(ID3v1Tag())
d->ID3v1Location = findID3v1();
// APE tag location has changed, update if it exists
if(APETag())
findAPE();
d->ID3v2OriginalSize = data.size();
}
else {
// ID3v2 tag is empty. Remove the old one.
strip(ID3v2, false);
}
else if(stripOthers)
success = strip(ID3v2, false) && success;
}
else if(d->hasID3v2 && stripOthers)
success = strip(ID3v2) && success;
if(ID3v1 & tags) {
if(ID3v1Tag() && !ID3v1Tag()->isEmpty()) {
int offset = d->hasID3v1 ? -128 : 0;
seek(offset, End);
writeBlock(ID3v1Tag()->render());
d->hasID3v1 = true;
d->ID3v1Location = findID3v1();
}
else if(stripOthers)
success = strip(ID3v1) && success;
}
else if(d->hasID3v1 && stripOthers)
success = strip(ID3v1, false) && success;
// Dont save an APE-tag unless one has been created
// ID3v1 tag is not empty. Update the old one or create a new one.
if((APE & tags) && APETag()) {
if(d->hasAPE)
insert(APETag()->render(), d->APELocation, d->APEOriginalSize);
else {
if(d->hasID3v1) {
insert(APETag()->render(), d->ID3v1Location, 0);
d->APEOriginalSize = APETag()->footer()->completeTagSize();
d->hasAPE = true;
d->APELocation = d->ID3v1Location;
d->ID3v1Location += d->APEOriginalSize;
if(d->ID3v1Location >= 0) {
seek(d->ID3v1Location);
}
else {
seek(0, End);
d->APELocation = tell();
APE::Tag *apeTag = d->tag.access<APE::Tag>(APEIndex, false);
d->APEFooterLocation = d->APELocation
+ apeTag->footer()->completeTagSize()
- APE::Footer::size();
writeBlock(APETag()->render());
d->APEOriginalSize = APETag()->footer()->completeTagSize();
d->hasAPE = true;
d->ID3v1Location = tell();
}
writeBlock(ID3v1Tag()->render());
}
else {
// ID3v1 tag is empty. Remove the old one.
strip(ID3v1, false);
}
}
else if(d->hasAPE && stripOthers)
success = strip(APE, false) && success;
return success;
if(APE & tags) {
if(APETag() && !APETag()->isEmpty()) {
// APE tag is not empty. Update the old one or create a new one.
if(d->APELocation < 0) {
if(d->ID3v1Location >= 0)
d->APELocation = d->ID3v1Location;
else
d->APELocation = length();
}
const ByteVector data = APETag()->render();
insert(data, d->APELocation, static_cast<size_t>(d->APEOriginalSize));
if(d->ID3v1Location >= 0)
d->ID3v1Location += (static_cast<long>(data.size()) - d->APEOriginalSize);
d->APEOriginalSize = data.size();
}
else {
// APE tag is empty. Remove the old one.
strip(APE, false);
}
}
return true;
}
ID3v2::Tag *MPEG::File::ID3v2Tag(bool create)
@ -301,44 +291,39 @@ bool MPEG::File::strip(int tags, bool freeMemory)
return false;
}
if((tags & ID3v2) && d->hasID3v2) {
removeBlock(d->ID3v2Location, d->ID3v2OriginalSize);
if((tags & ID3v2) && d->ID3v2Location >= 0) {
removeBlock(d->ID3v2Location, static_cast<size_t>(d->ID3v2OriginalSize));
if(d->APELocation >= 0)
d->APELocation -= d->ID3v2OriginalSize;
if(d->ID3v1Location >= 0)
d->ID3v1Location -= d->ID3v2OriginalSize;
d->ID3v2Location = -1;
d->ID3v2OriginalSize = 0;
d->hasID3v2 = false;
if(freeMemory)
d->tag.set(ID3v2Index, 0);
// v1 tag location has changed, update if it exists
if(ID3v1Tag())
d->ID3v1Location = findID3v1();
// APE tag location has changed, update if it exists
if(APETag())
findAPE();
}
if((tags & ID3v1) && d->hasID3v1) {
if((tags & ID3v1) && d->ID3v1Location >= 0) {
truncate(d->ID3v1Location);
d->ID3v1Location = -1;
d->hasID3v1 = false;
if(freeMemory)
d->tag.set(ID3v1Index, 0);
}
if((tags & APE) && d->hasAPE) {
removeBlock(d->APELocation, d->APEOriginalSize);
if((tags & APE) && d->APELocation >= 0) {
removeBlock(d->APELocation, static_cast<size_t>(d->APEOriginalSize));
if(d->ID3v1Location >= 0)
d->ID3v1Location -= d->APEOriginalSize;
d->APELocation = -1;
d->APEFooterLocation = -1;
d->hasAPE = false;
if(d->hasID3v1) {
if(d->ID3v1Location > d->APELocation)
d->ID3v1Location -= d->APEOriginalSize;
}
d->APEOriginalSize = 0;
if(freeMemory)
d->tag.set(APEIndex, 0);
@ -432,17 +417,17 @@ long long MPEG::File::lastFrameOffset()
bool MPEG::File::hasID3v1Tag() const
{
return d->hasID3v1;
return (d->ID3v1Location >= 0);
}
bool MPEG::File::hasID3v2Tag() const
{
return d->hasID3v2;
return (d->ID3v2Location >= 0);
}
bool MPEG::File::hasAPETag() const
{
return d->hasAPE;
return (d->APELocation >= 0);
}
////////////////////////////////////////////////////////////////////////////////
@ -456,35 +441,25 @@ void MPEG::File::read(bool readProperties)
d->ID3v2Location = findID3v2();
if(d->ID3v2Location >= 0) {
d->tag.set(ID3v2Index, new ID3v2::Tag(this, d->ID3v2Location, d->ID3v2FrameFactory));
d->ID3v2OriginalSize = ID3v2Tag()->header()->completeTagSize();
if(ID3v2Tag()->header()->tagSize() <= 0)
d->tag.set(ID3v2Index, 0);
else
d->hasID3v2 = true;
}
// Look for an ID3v1 tag
d->ID3v1Location = findID3v1();
d->ID3v1Location = Utils::findID3v1(this);
if(d->ID3v1Location >= 0) {
if(d->ID3v1Location >= 0)
d->tag.set(ID3v1Index, new ID3v1::Tag(this, d->ID3v1Location));
d->hasID3v1 = true;
}
// Look for an APE tag
findAPE();
d->APELocation = Utils::findAPE(this, d->ID3v1Location);
if(d->APELocation >= 0) {
d->tag.set(APEIndex, new APE::Tag(this, d->APEFooterLocation));
d->tag.set(APEIndex, new APE::Tag(this, d->APELocation));
d->APEOriginalSize = APETag()->footer()->completeTagSize();
d->hasAPE = true;
d->APELocation = d->APELocation + APE::Footer::size() - d->APEOriginalSize;
}
if(readProperties)
@ -531,36 +506,3 @@ long long MPEG::File::findID3v2()
return tagOffset;
}
long long MPEG::File::findID3v1()
{
if(isValid()) {
seek(-128, End);
long long p = tell();
if(readBlock(3) == ID3v1::Tag::fileIdentifier())
return p;
}
return -1;
}
void MPEG::File::findAPE()
{
if(isValid()) {
seek(d->hasID3v1 ? -160 : -32, End);
long long p = tell();
if(readBlock(8) == APE::Tag::fileIdentifier()) {
d->APEFooterLocation = p;
seek(d->APEFooterLocation);
APE::Footer footer(readBlock(APE::Footer::size()));
d->APELocation = d->APEFooterLocation - footer.completeTagSize()
+ APE::Footer::size();
return;
}
}
d->APELocation = -1;
d->APEFooterLocation = -1;
}

View File

@ -165,9 +165,6 @@ namespace TagLib {
* If you would like more granular control over the content of the tags,
* with the concession of generality, use parameterized save call below.
*
* \warning In the current implementation, it's dangerous to call save()
* repeatedly. At worst it will corrupt the file.
*
* \see save(int tags)
*/
virtual bool save();
@ -184,8 +181,8 @@ namespace TagLib {
* The \a id3v2Version parameter specifies the version of the saved
* ID3v2 tag. It can be either 4 or 3.
*
* \warning In the current implementation, it's dangerous to call save()
* repeatedly. At worst it will corrupt the file.
* If \a duplicateTags is true and at least one tag -- ID3v1 or ID3v2 --
* exists this will duplicate its content into the other tag.
*/
bool save(int tags, bool stripOthers = true, int id3v2Version = 4, bool duplicateTags = true);
@ -330,8 +327,6 @@ namespace TagLib {
void read(bool readProperties);
long long findID3v2();
long long findID3v1();
void findAPE();
class FilePrivate;
FilePrivate *d;

View File

@ -57,9 +57,7 @@ public:
ID3v2Location(-1),
ID3v2OriginalSize(0),
ID3v1Location(-1),
properties(0),
hasID3v1(false),
hasID3v2(false) {}
properties(0) {}
~FilePrivate()
{
@ -67,20 +65,14 @@ public:
}
const ID3v2::FrameFactory *ID3v2FrameFactory;
long long ID3v2Location;
unsigned int ID3v2OriginalSize;
long long ID3v2Location;
long long ID3v2OriginalSize;
long long ID3v1Location;
DoubleTagUnion tag;
AudioProperties *properties;
// These indicate whether the file *on disk* has these tags, not if
// this data structure does. This is used in computing offsets.
bool hasID3v1;
bool hasID3v2;
};
////////////////////////////////////////////////////////////////////////////////
@ -159,40 +151,59 @@ bool TrueAudio::File::save()
// Update ID3v2 tag
if(ID3v2Tag() && !ID3v2Tag()->isEmpty()) {
if(!d->hasID3v2) {
// ID3v2 tag is not empty. Update the old one or create a new one.
if(d->ID3v2Location < 0)
d->ID3v2Location = 0;
const ByteVector data = ID3v2Tag()->render();
insert(data, d->ID3v2Location, static_cast<size_t>(d->ID3v2OriginalSize));
if(d->ID3v1Location >= 0)
d->ID3v1Location += (static_cast<long>(data.size()) - d->ID3v2OriginalSize);
d->ID3v2OriginalSize = data.size();
}
else {
// ID3v2 tag is empty. Remove the old one.
if(d->ID3v2Location >= 0) {
removeBlock(d->ID3v2Location, static_cast<size_t>(d->ID3v2OriginalSize));
if(d->ID3v1Location >= 0)
d->ID3v1Location -= d->ID3v2OriginalSize;
d->ID3v2Location = -1;
d->ID3v2OriginalSize = 0;
}
ByteVector data = ID3v2Tag()->render();
insert(data, d->ID3v2Location, d->ID3v2OriginalSize);
d->ID3v1Location -= d->ID3v2OriginalSize - data.size();
d->ID3v2OriginalSize = static_cast<unsigned int>(data.size());
d->hasID3v2 = true;
}
else if(d->hasID3v2) {
removeBlock(d->ID3v2Location, d->ID3v2OriginalSize);
d->ID3v1Location -= d->ID3v2OriginalSize;
d->ID3v2Location = -1;
d->ID3v2OriginalSize = 0;
d->hasID3v2 = false;
}
// Update ID3v1 tag
if(ID3v1Tag() && !ID3v1Tag()->isEmpty()) {
if(!d->hasID3v1) {
// ID3v1 tag is not empty. Update the old one or create a new one.
if(d->ID3v1Location >= 0) {
seek(d->ID3v1Location);
}
else {
seek(0, End);
d->ID3v1Location = tell();
}
else
seek(d->ID3v1Location);
writeBlock(ID3v1Tag()->render());
d->hasID3v1 = true;
}
else if(d->hasID3v1) {
removeBlock(d->ID3v1Location, 128);
d->ID3v1Location = -1;
d->hasID3v1 = false;
else {
// ID3v1 tag is empty. Remove the old one.
if(d->ID3v1Location >= 0) {
truncate(d->ID3v1Location);
d->ID3v1Location = -1;
}
}
return true;
@ -210,27 +221,24 @@ ID3v2::Tag *TrueAudio::File::ID3v2Tag(bool create)
void TrueAudio::File::strip(int tags)
{
if(tags & ID3v1) {
if(tags & ID3v1)
d->tag.set(TrueAudioID3v1Index, 0);
ID3v2Tag(true);
}
if(tags & ID3v2) {
if(tags & ID3v2)
d->tag.set(TrueAudioID3v2Index, 0);
if(!ID3v1Tag())
ID3v2Tag(true);
}
if(!ID3v1Tag())
ID3v2Tag(true);
}
bool TrueAudio::File::hasID3v1Tag() const
{
return d->hasID3v1;
return (d->ID3v1Location >= 0);
}
bool TrueAudio::File::hasID3v2Tag() const
{
return d->hasID3v2;
return (d->ID3v2Location >= 0);
}
////////////////////////////////////////////////////////////////////////////////
@ -244,27 +252,18 @@ void TrueAudio::File::read(bool readProperties)
d->ID3v2Location = Utils::findID3v2(this);
if(d->ID3v2Location >= 0) {
d->tag.set(TrueAudioID3v2Index, new ID3v2::Tag(this, d->ID3v2Location, d->ID3v2FrameFactory));
d->ID3v2OriginalSize = ID3v2Tag()->header()->completeTagSize();
if(ID3v2Tag()->header()->tagSize() <= 0)
d->tag.set(TrueAudioID3v2Index, 0);
else
d->hasID3v2 = true;
}
// Look for an ID3v1 tag
d->ID3v1Location = Utils::findID3v1(this);
if(d->ID3v1Location >= 0) {
if(d->ID3v1Location >= 0)
d->tag.set(TrueAudioID3v1Index, new ID3v1::Tag(this, d->ID3v1Location));
d->hasID3v1 = true;
}
if(!d->hasID3v1)
if(d->ID3v1Location < 0)
ID3v2Tag(true);
// Look for TrueAudio metadata
@ -273,12 +272,12 @@ void TrueAudio::File::read(bool readProperties)
long long streamLength;
if(d->hasID3v1)
if(d->ID3v1Location >= 0)
streamLength = d->ID3v1Location;
else
streamLength = length();
if(d->hasID3v2) {
if(d->ID3v2Location >= 0) {
seek(d->ID3v2Location + d->ID3v2OriginalSize);
streamLength -= (d->ID3v2Location + d->ID3v2OriginalSize);
}

View File

@ -54,29 +54,21 @@ public:
APELocation(-1),
APESize(0),
ID3v1Location(-1),
properties(0),
hasAPE(false),
hasID3v1(false) {}
properties(0) {}
~FilePrivate()
{
delete properties;
}
long long APELocation;
unsigned int APESize;
long long APELocation;
long long APESize;
long long ID3v1Location;
DoubleTagUnion tag;
AudioProperties *properties;
// These indicate whether the file *on disk* has these tags, not if
// this data structure does. This is used in computing offsets.
bool hasAPE;
bool hasID3v1;
};
////////////////////////////////////////////////////////////////////////////////
@ -131,64 +123,67 @@ bool WavPack::File::save()
// Update ID3v1 tag
if(ID3v1Tag()) {
if(d->hasID3v1) {
if(ID3v1Tag() && !ID3v1Tag()->isEmpty()) {
// ID3v1 tag is not empty. Update the old one or create a new one.
if(d->ID3v1Location >= 0) {
seek(d->ID3v1Location);
writeBlock(ID3v1Tag()->render());
}
else {
seek(0, End);
d->ID3v1Location = tell();
writeBlock(ID3v1Tag()->render());
d->hasID3v1 = true;
}
writeBlock(ID3v1Tag()->render());
}
else {
if(d->hasID3v1) {
removeBlock(d->ID3v1Location, 128);
d->hasID3v1 = false;
if(d->hasAPE) {
if(d->APELocation > d->ID3v1Location)
d->APELocation -= 128;
}
// ID3v1 tag is empty. Remove the old one.
if(d->ID3v1Location >= 0) {
truncate(d->ID3v1Location);
d->ID3v1Location = -1;
}
}
// Update APE tag
if(APETag()) {
if(d->hasAPE)
insert(APETag()->render(), d->APELocation, d->APESize);
else {
if(d->hasID3v1) {
insert(APETag()->render(), d->ID3v1Location, 0);
d->APESize = APETag()->footer()->completeTagSize();
d->hasAPE = true;
if(APETag() && !APETag()->isEmpty()) {
// APE tag is not empty. Update the old one or create a new one.
if(d->APELocation < 0) {
if(d->ID3v1Location >= 0)
d->APELocation = d->ID3v1Location;
d->ID3v1Location += d->APESize;
}
else {
seek(0, End);
d->APELocation = tell();
writeBlock(APETag()->render());
d->APESize = APETag()->footer()->completeTagSize();
d->hasAPE = true;
}
else
d->APELocation = length();
}
const ByteVector data = APETag()->render();
insert(data, d->APELocation, static_cast<size_t>(d->APESize));
if(d->ID3v1Location >= 0)
d->ID3v1Location += (static_cast<long>(data.size()) - d->APESize);
d->APESize = data.size();
}
else {
if(d->hasAPE) {
removeBlock(d->APELocation, d->APESize);
d->hasAPE = false;
if(d->hasID3v1) {
if(d->ID3v1Location > d->APELocation) {
d->ID3v1Location -= d->APESize;
}
}
// APE tag is empty. Remove the old one.
if(d->APELocation >= 0) {
removeBlock(d->APELocation, static_cast<size_t>(d->APESize));
if(d->ID3v1Location >= 0)
d->ID3v1Location -= d->APESize;
d->APELocation = -1;
d->APESize = 0;
}
}
return true;
return true;
}
ID3v1::Tag *WavPack::File::ID3v1Tag(bool create)
@ -203,27 +198,24 @@ APE::Tag *WavPack::File::APETag(bool create)
void WavPack::File::strip(int tags)
{
if(tags & ID3v1) {
if(tags & ID3v1)
d->tag.set(WavID3v1Index, 0);
APETag(true);
}
if(tags & APE) {
if(tags & APE)
d->tag.set(WavAPEIndex, 0);
if(!ID3v1Tag())
APETag(true);
}
if(!ID3v1Tag())
APETag(true);
}
bool WavPack::File::hasID3v1Tag() const
{
return d->hasID3v1;
return (d->ID3v1Location >= 0);
}
bool WavPack::File::hasAPETag() const
{
return d->hasAPE;
return (d->APELocation >= 0);
}
////////////////////////////////////////////////////////////////////////////////
@ -236,10 +228,8 @@ void WavPack::File::read(bool readProperties)
d->ID3v1Location = Utils::findID3v1(this);
if(d->ID3v1Location >= 0) {
if(d->ID3v1Location >= 0)
d->tag.set(WavID3v1Index, new ID3v1::Tag(this, d->ID3v1Location));
d->hasID3v1 = true;
}
// Look for an APE tag
@ -249,10 +239,9 @@ void WavPack::File::read(bool readProperties)
d->tag.set(WavAPEIndex, new APE::Tag(this, d->APELocation));
d->APESize = APETag()->footer()->completeTagSize();
d->APELocation = d->APELocation + APE::Footer::size() - d->APESize;
d->hasAPE = true;
}
if(!d->hasID3v1)
if(d->ID3v1Location >= 0)
APETag(true);
// Look for WavPack audio properties
@ -261,9 +250,9 @@ void WavPack::File::read(bool readProperties)
long long streamLength;
if(d->hasAPE)
if(d->APELocation >= 0)
streamLength = d->APELocation;
else if(d->hasID3v1)
else if(d->ID3v1Location >= 0)
streamLength = d->ID3v1Location;
else
streamLength = length();

View File

@ -126,9 +126,6 @@ namespace TagLib {
* Saves the file.
*
* This returns true if the save was successful.
*
* \warning In the current implementation, it's dangerous to call save()
* repeatedly. At worst it will corrupt the file.
*/
virtual bool save();

View File

@ -23,6 +23,7 @@ class TestAPE : public CppUnit::TestFixture
CPPUNIT_TEST(testFuzzedFile1);
CPPUNIT_TEST(testFuzzedFile2);
CPPUNIT_TEST(testStripAndProperties);
CPPUNIT_TEST(testRepeatedSave);
CPPUNIT_TEST_SUITE_END();
public:
@ -134,6 +135,32 @@ public:
}
}
void testRepeatedSave()
{
ScopedFileCopy copy("mac-399", ".ape");
{
APE::File f(copy.fileName().c_str());
CPPUNIT_ASSERT(!f.hasAPETag());
CPPUNIT_ASSERT(!f.hasID3v1Tag());
f.APETag(true)->setTitle("01234 56789 ABCDE FGHIJ");
f.save();
f.APETag()->setTitle("0");
f.save();
f.ID3v1Tag(true)->setTitle("01234 56789 ABCDE FGHIJ");
f.APETag()->setTitle("01234 56789 ABCDE FGHIJ 01234 56789 ABCDE FGHIJ 01234 56789");
f.save();
}
{
APE::File f(copy.fileName().c_str());
CPPUNIT_ASSERT(f.hasAPETag());
CPPUNIT_ASSERT(f.hasID3v1Tag());
}
}
};
CPPUNIT_TEST_SUITE_REGISTRATION(TestAPE);

View File

@ -60,6 +60,8 @@ public:
{
FLAC::File f(newname.c_str());
CPPUNIT_ASSERT_EQUAL(String("The Artist"), f.tag()->artist());
CPPUNIT_ASSERT_EQUAL(69LL, f.find("Artist"));
CPPUNIT_ASSERT_EQUAL(-1LL, f.find("Artist", 70));
}
}

View File

@ -1073,29 +1073,20 @@ public:
{
MPEG::File f(newname.c_str());
ID3v2::Tag *tag = f.ID3v2Tag(true);
ID3v2::TextIdentificationFrame *frame1 = new ID3v2::TextIdentificationFrame("TIT2");
frame1->setText("Title");
tag->addFrame(frame1);
ID3v2::AttachedPictureFrame *frame2 = new ID3v2::AttachedPictureFrame();
frame2->setPicture(ByteVector(100 * 1024, '\xff'));
tag->addFrame(frame2);
f.save();
CPPUNIT_ASSERT(f.length() > 100 * 1024);
f.ID3v2Tag()->setTitle(std::wstring(64 * 1024, L'X').c_str());
f.save(MPEG::File::ID3v2, true);
}
{
MPEG::File f(newname.c_str());
CPPUNIT_ASSERT_EQUAL(true, f.hasID3v2Tag());
ID3v2::Tag *tag = f.ID3v2Tag();
tag->removeFrames("APIC");
f.save();
CPPUNIT_ASSERT(f.length() < 10 * 1024);
CPPUNIT_ASSERT(f.hasID3v2Tag());
CPPUNIT_ASSERT_EQUAL(74789LL, f.length());
f.ID3v2Tag()->setTitle("ABCDEFGHIJ");
f.save(MPEG::File::ID3v2, true);
}
{
MPEG::File f(newname.c_str());
CPPUNIT_ASSERT(f.hasID3v2Tag());
CPPUNIT_ASSERT_EQUAL(9263LL, f.length());
}
}

View File

@ -24,6 +24,7 @@ class TestMPC : public CppUnit::TestFixture
CPPUNIT_TEST(testFuzzedFile3);
CPPUNIT_TEST(testFuzzedFile4);
CPPUNIT_TEST(testStripAndProperties);
CPPUNIT_TEST(testRepeatedSave);
CPPUNIT_TEST_SUITE_END();
public:
@ -132,6 +133,32 @@ public:
}
}
void testRepeatedSave()
{
ScopedFileCopy copy("click", ".mpc");
{
MPC::File f(copy.fileName().c_str());
CPPUNIT_ASSERT(!f.hasAPETag());
CPPUNIT_ASSERT(!f.hasID3v1Tag());
f.APETag(true)->setTitle("01234 56789 ABCDE FGHIJ");
f.save();
f.APETag()->setTitle("0");
f.save();
f.ID3v1Tag(true)->setTitle("01234 56789 ABCDE FGHIJ");
f.APETag()->setTitle("01234 56789 ABCDE FGHIJ 01234 56789 ABCDE FGHIJ 01234 56789");
f.save();
}
{
MPC::File f(copy.fileName().c_str());
CPPUNIT_ASSERT(f.hasAPETag());
CPPUNIT_ASSERT(f.hasID3v1Tag());
}
}
};
CPPUNIT_TEST_SUITE_REGISTRATION(TestMPC);

View File

@ -31,6 +31,12 @@ class TestMPEG : public CppUnit::TestFixture
CPPUNIT_TEST(testFuzzedFile);
CPPUNIT_TEST(testFrameOffset);
CPPUNIT_TEST(testStripAndProperties);
CPPUNIT_TEST(testRepeatedSave1);
CPPUNIT_TEST(testRepeatedSave2);
CPPUNIT_TEST(testRepeatedSave3);
CPPUNIT_TEST(testEmptyID3v2);
CPPUNIT_TEST(testEmptyID3v1);
CPPUNIT_TEST(testEmptyAPE);
CPPUNIT_TEST_SUITE_END();
public:
@ -253,6 +259,120 @@ public:
}
}
void testRepeatedSave1()
{
ScopedFileCopy copy("xing", ".mp3");
{
MPEG::File f(copy.fileName().c_str());
f.ID3v2Tag(true)->setTitle(std::string(4096, 'X').c_str());
f.save();
}
{
MPEG::File f(copy.fileName().c_str());
f.ID3v2Tag(true)->setTitle("");
f.save();
f.ID3v2Tag(true)->setTitle(std::string(4096, 'X').c_str());
f.save();
CPPUNIT_ASSERT_EQUAL(5141LL, f.firstFrameOffset());
}
}
void testRepeatedSave2()
{
ScopedFileCopy copy("xing", ".mp3");
MPEG::File f(copy.fileName().c_str());
f.ID3v2Tag(true)->setTitle("0123456789");
f.save();
f.save();
CPPUNIT_ASSERT_EQUAL(-1LL, f.find("ID3", 3));
}
void testRepeatedSave3()
{
ScopedFileCopy copy("xing", ".mp3");
{
MPEG::File f(copy.fileName().c_str());
CPPUNIT_ASSERT(!f.hasAPETag());
CPPUNIT_ASSERT(!f.hasID3v1Tag());
f.APETag(true)->setTitle("01234 56789 ABCDE FGHIJ");
f.save();
f.APETag()->setTitle("0");
f.save();
f.ID3v1Tag(true)->setTitle("01234 56789 ABCDE FGHIJ");
f.APETag()->setTitle("01234 56789 ABCDE FGHIJ 01234 56789 ABCDE FGHIJ 01234 56789");
f.save();
}
{
MPEG::File f(copy.fileName().c_str());
CPPUNIT_ASSERT(f.hasAPETag());
CPPUNIT_ASSERT(f.hasID3v1Tag());
}
}
void testEmptyID3v2()
{
ScopedFileCopy copy("xing", ".mp3");
{
MPEG::File f(copy.fileName().c_str());
f.ID3v2Tag(true)->setTitle("0123456789");
f.save(MPEG::File::ID3v2);
}
{
MPEG::File f(copy.fileName().c_str());
f.ID3v2Tag(true)->setTitle("");
f.save(MPEG::File::ID3v2, false);
}
{
MPEG::File f(copy.fileName().c_str());
CPPUNIT_ASSERT(!f.hasID3v2Tag());
}
}
void testEmptyID3v1()
{
ScopedFileCopy copy("xing", ".mp3");
{
MPEG::File f(copy.fileName().c_str());
f.ID3v1Tag(true)->setTitle("0123456789");
f.save(MPEG::File::ID3v1);
}
{
MPEG::File f(copy.fileName().c_str());
f.ID3v1Tag(true)->setTitle("");
f.save(MPEG::File::ID3v1, false);
}
{
MPEG::File f(copy.fileName().c_str());
CPPUNIT_ASSERT(!f.hasID3v1Tag());
}
}
void testEmptyAPE()
{
ScopedFileCopy copy("xing", ".mp3");
{
MPEG::File f(copy.fileName().c_str());
f.APETag(true)->setTitle("0123456789");
f.save(MPEG::File::APE);
}
{
MPEG::File f(copy.fileName().c_str());
f.APETag(true)->setTitle("");
f.save(MPEG::File::APE, false);
}
{
MPEG::File f(copy.fileName().c_str());
CPPUNIT_ASSERT(!f.hasAPETag());
}
}
};
CPPUNIT_TEST_SUITE_REGISTRATION(TestMPEG);

View File

@ -16,6 +16,7 @@ class TestTrueAudio : public CppUnit::TestFixture
CPPUNIT_TEST(testReadPropertiesWithoutID3v2);
CPPUNIT_TEST(testReadPropertiesWithTags);
CPPUNIT_TEST(testStripAndProperties);
CPPUNIT_TEST(testRepeatedSave);
CPPUNIT_TEST_SUITE_END();
public:
@ -70,6 +71,32 @@ public:
}
}
void testRepeatedSave()
{
ScopedFileCopy copy("empty", ".tta");
{
TrueAudio::File f(copy.fileName().c_str());
CPPUNIT_ASSERT(!f.hasID3v2Tag());
CPPUNIT_ASSERT(!f.hasID3v1Tag());
f.ID3v2Tag(true)->setTitle("01234 56789 ABCDE FGHIJ");
f.save();
f.ID3v2Tag()->setTitle("0");
f.save();
f.ID3v1Tag(true)->setTitle("01234 56789 ABCDE FGHIJ");
f.ID3v2Tag()->setTitle("01234 56789 ABCDE FGHIJ 01234 56789 ABCDE FGHIJ 01234 56789");
f.save();
}
{
TrueAudio::File f(copy.fileName().c_str());
CPPUNIT_ASSERT(f.hasID3v2Tag());
CPPUNIT_ASSERT(f.hasID3v1Tag());
}
}
};
CPPUNIT_TEST_SUITE_REGISTRATION(TestTrueAudio);

View File

@ -19,6 +19,7 @@ class TestWavPack : public CppUnit::TestFixture
CPPUNIT_TEST(testTaggedProperties);
CPPUNIT_TEST(testFuzzedFile);
CPPUNIT_TEST(testStripAndProperties);
CPPUNIT_TEST(testRepeatedSave);
CPPUNIT_TEST_SUITE_END();
public:
@ -97,6 +98,32 @@ public:
}
}
void testRepeatedSave()
{
ScopedFileCopy copy("click", ".wv");
{
WavPack::File f(copy.fileName().c_str());
CPPUNIT_ASSERT(!f.hasAPETag());
CPPUNIT_ASSERT(!f.hasID3v1Tag());
f.APETag(true)->setTitle("01234 56789 ABCDE FGHIJ");
f.save();
f.APETag()->setTitle("0");
f.save();
f.ID3v1Tag(true)->setTitle("01234 56789 ABCDE FGHIJ");
f.APETag()->setTitle("01234 56789 ABCDE FGHIJ 01234 56789 ABCDE FGHIJ 01234 56789");
f.save();
}
{
WavPack::File f(copy.fileName().c_str());
CPPUNIT_ASSERT(f.hasAPETag());
CPPUNIT_ASSERT(f.hasID3v1Tag());
}
}
};
CPPUNIT_TEST_SUITE_REGISTRATION(TestWavPack);