Skip duplicate ID3v2 tags and treat them as an extra blank of the first one.

This enables all the file formats to handle duplicate ID3v2 tags properly and erase them automatically.
This commit is contained in:
Tsuda Kageyu 2015-11-13 11:55:56 +09:00
parent a25e1e9f90
commit 11fbf394a3
3 changed files with 74 additions and 30 deletions

View File

@ -24,22 +24,22 @@
***************************************************************************/
#ifdef HAVE_CONFIG_H
#include "config.h"
# include "config.h"
#endif
#include <algorithm>
#include "tfile.h"
#include <tfile.h>
#include <tbytevector.h>
#include <tpropertymap.h>
#include <tdebug.h>
#include "id3v2tag.h"
#include "id3v2header.h"
#include "id3v2extendedheader.h"
#include "id3v2footer.h"
#include "id3v2synchdata.h"
#include "tbytevector.h"
#include "id3v1genres.h"
#include "tpropertymap.h"
#include "tdebug.h"
#include "frames/textidentificationframe.h"
#include "frames/commentsframe.h"
@ -136,7 +136,6 @@ ID3v2::Tag::~Tag()
delete d;
}
String ID3v2::Tag::title() const
{
if(!d->frameListMap["TIT2"].isEmpty())
@ -564,7 +563,6 @@ void ID3v2::Tag::downgradeFrames(FrameList *frames, FrameList *newFrames) const
}
}
ByteVector ID3v2::Tag::render(int version) const
{
// We need to render the "tag data" first so that we have to correct size to
@ -667,18 +665,43 @@ void ID3v2::Tag::setLatin1StringHandler(const Latin1StringHandler *handler)
void ID3v2::Tag::read()
{
if(d->file && d->file->isOpen()) {
if(!d->file)
return;
d->file->seek(d->tagOffset);
d->header.setData(d->file->readBlock(Header::size()));
if(!d->file->isOpen())
return;
// if the tag size is 0, then this is an invalid tag (tags must contain at
// least one frame)
d->file->seek(d->tagOffset);
d->header.setData(d->file->readBlock(Header::size()));
if(d->header.tagSize() == 0)
return;
// If the tag size is 0, then this is an invalid tag (tags must contain at
// least one frame)
if(d->header.tagSize() != 0)
parse(d->file->readBlock(d->header.tagSize()));
// Look for duplicate ID3v2 tags and treat them as an extra blank of this one.
// It leads to overwriting them with zero when saving the tag.
// This is a workaround for some faulty files that have duplicate ID3v2 tags.
// Unfortunately, TagLib itself may write such duplicate tags until v1.10.
uint extraSize = 0;
while(true) {
d->file->seek(d->tagOffset + d->header.completeTagSize() + extraSize);
const ByteVector data = d->file->readBlock(Header::size());
if(data.size() < Header::size() || !data.startsWith(Header::fileIdentifier()))
break;
extraSize += Header(data).completeTagSize();
}
if(extraSize != 0) {
debug("ID3v2::Tag::read() - Duplicate ID3v2 tags found.");
d->header.setTagSize(d->header.tagSize() + extraSize);
}
}

View File

@ -446,24 +446,9 @@ long MPEG::File::firstFrameOffset()
{
long position = 0;
if(hasID3v2Tag()) {
if(hasID3v2Tag())
position = d->ID3v2Location + ID3v2Tag()->header()->completeTagSize();
// Skip duplicate ID3v2 tags.
// Workaround for some faulty files that have duplicate ID3v2 tags.
// Combination of EAC and LAME creates such files when configured incorrectly.
long location;
while((location = findID3v2(position)) >= 0) {
seek(location);
const ID3v2::Header header(readBlock(ID3v2::Header::size()));
position = location + header.completeTagSize();
debug("MPEG::File::firstFrameOffset() - Duplicate ID3v2 tag found.");
}
}
return nextFrameOffset(position);
}

View File

@ -93,6 +93,7 @@ class TestID3v2 : public CppUnit::TestFixture
CPPUNIT_TEST(testRenderTableOfContentsFrame);
CPPUNIT_TEST(testShrinkPadding);
CPPUNIT_TEST(testEmptyFrame);
CPPUNIT_TEST(testDuplicateTags);
CPPUNIT_TEST_SUITE_END();
public:
@ -1117,6 +1118,41 @@ public:
}
}
void testDuplicateTags()
{
ScopedFileCopy copy("duplicate_id3v2", ".mp3");
ByteVector audioStream;
{
MPEG::File f(copy.fileName().c_str());
f.seek(f.ID3v2Tag()->header()->completeTagSize());
audioStream = f.readBlock(2089);
// duplicate_id3v2.mp3 has duplicate ID3v2 tags.
// Sample rate will be 32000 if we can't skip the second tag.
CPPUNIT_ASSERT(f.hasID3v2Tag());
CPPUNIT_ASSERT_EQUAL((TagLib::uint)8049, f.ID3v2Tag()->header()->completeTagSize());
CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate());
f.ID3v2Tag()->setArtist("Artist A");
f.save(MPEG::File::ID3v2, true);
}
{
MPEG::File f(copy.fileName().c_str());
CPPUNIT_ASSERT(f.hasID3v2Tag());
CPPUNIT_ASSERT_EQUAL((long)3594, f.length());
CPPUNIT_ASSERT_EQUAL((TagLib::uint)1505, f.ID3v2Tag()->header()->completeTagSize());
CPPUNIT_ASSERT_EQUAL(String("Artist A"), f.ID3v2Tag()->artist());
CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate());
f.seek(f.ID3v2Tag()->header()->completeTagSize());
CPPUNIT_ASSERT_EQUAL(f.readBlock(2089), audioStream);
}
}
};
CPPUNIT_TEST_SUITE_REGISTRATION(TestID3v2);