This enables all the file formats to handle duplicate ID3v2 tags properly and erase them automatically.
***************************************************************************/
#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"
delete d;
}
-
String ID3v2::Tag::title() const
{
if(!d->frameListMap["TIT2"].isEmpty())
}
}
-
ByteVector ID3v2::Tag::render(int version) const
{
// We need to render the "tag data" first so that we have to correct size to
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);
}
}
{
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);
}
CPPUNIT_TEST(testRenderTableOfContentsFrame);
CPPUNIT_TEST(testShrinkPadding);
CPPUNIT_TEST(testEmptyFrame);
+ CPPUNIT_TEST(testDuplicateTags);
CPPUNIT_TEST_SUITE_END();
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);