From e8d0551c9a4188ea4f93c54fed2913fd5d6b18b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Lalinsk=C3=BD?= Date: Wed, 16 Mar 2011 17:14:36 +0100 Subject: [PATCH] Simple ID3v2.4 => ID3v2.3 frame migration --- taglib/mpeg/id3v2/id3v2tag.cpp | 68 +++++++++++++++++++++++++++++++++- taglib/mpeg/id3v2/id3v2tag.h | 2 + tests/test_id3v2.cpp | 43 +++++++++++++++++++++ 3 files changed, 112 insertions(+), 1 deletion(-) diff --git a/taglib/mpeg/id3v2/id3v2tag.cpp b/taglib/mpeg/id3v2/id3v2tag.cpp index c85ef59d..6908c1b0 100644 --- a/taglib/mpeg/id3v2/id3v2tag.cpp +++ b/taglib/mpeg/id3v2/id3v2tag.cpp @@ -334,6 +334,61 @@ ByteVector ID3v2::Tag::render() const return render(4); } +void ID3v2::Tag::downgradeFrames(FrameList *frames, FrameList *newFrames) const +{ + const char *unsupportedFrames[] = { + "ASPI", "EQU2", "RVA2", "SEEK", "SIGN", "TDRL", "TDTG", + "TMOO", "TPRO", "TSOA", "TSOT", "TSST", "TSOP", "TIPL", + "TMCL", 0 + }; + ID3v2::TextIdentificationFrame *frameTDOR = 0; + ID3v2::TextIdentificationFrame *frameTDRC = 0; + for(FrameList::Iterator it = d->frameList.begin(); it != d->frameList.end(); it++) { + ID3v2::Frame *frame = *it; + ByteVector frameID = frame->header()->frameID(); + for(int i = 0; unsupportedFrames[i]; i++) { + if(frameID == unsupportedFrames[i]) { + debug("A frame that is not supported in ID3v2.3 \'" + + String(frameID) + "\' has been discarded"); + frame = 0; + break; + } + } + if(frame && frameID == "TDOR") { + frameTDOR = dynamic_cast(frame); + frame = 0; + } + if(frame && frameID == "TDRC") { + frameTDRC = dynamic_cast(frame); + frame = 0; + } + if(frame) { + frames->append(frame); + } + } + if(frameTDOR) { + String content = frameTDOR->toString(); + if(content.size() >= 4) { + ID3v2::TextIdentificationFrame *frameTORY = new ID3v2::TextIdentificationFrame("TORY", String::Latin1); + frameTORY->setText(content.substr(0, 4)); + frames->append(frameTORY); + newFrames->append(frameTORY); + } + } + if(frameTDRC) { + // FIXME we can do better here, define also TDAT and TIME + String content = frameTDRC->toString(); + if(content.size() >= 4) { + ID3v2::TextIdentificationFrame *frameTYER = new ID3v2::TextIdentificationFrame("TYER", String::Latin1); + frameTYER->setText(content.substr(0, 4)); + frames->append(frameTYER); + newFrames->append(frameTYER); + } + } + // FIXME migrate TIPL and TMCL to IPLS +} + + ByteVector ID3v2::Tag::render(int version) const { // We need to render the "tag data" first so that we have to correct size to @@ -347,7 +402,18 @@ ByteVector ID3v2::Tag::render(int version) const // Loop through the frames rendering them and adding them to the tagData. - for(FrameList::Iterator it = d->frameList.begin(); it != d->frameList.end(); it++) { + FrameList newFrames; + newFrames.setAutoDelete(true); + + FrameList frameList; + if(version == 4) { + frameList = d->frameList; + } + else { + downgradeFrames(&frameList, &newFrames); + } + + for(FrameList::Iterator it = frameList.begin(); it != frameList.end(); it++) { (*it)->header()->setVersion(version); if((*it)->header()->frameID().size() != 4) { debug("A frame of unsupported or unknown type \'" diff --git a/taglib/mpeg/id3v2/id3v2tag.h b/taglib/mpeg/id3v2/id3v2tag.h index 137139bd..4a52854a 100644 --- a/taglib/mpeg/id3v2/id3v2tag.h +++ b/taglib/mpeg/id3v2/id3v2tag.h @@ -295,6 +295,8 @@ namespace TagLib { */ void setTextFrame(const ByteVector &id, const String &value); + void downgradeFrames(FrameList *existingFrames, FrameList *newFrames) const; + private: Tag(const Tag &); Tag &operator=(const Tag &); diff --git a/tests/test_id3v2.cpp b/tests/test_id3v2.cpp index 3460168c..84074585 100644 --- a/tests/test_id3v2.cpp +++ b/tests/test_id3v2.cpp @@ -64,6 +64,7 @@ class TestID3v2 : public CppUnit::TestFixture CPPUNIT_TEST(testUpdateGenre23_2); CPPUNIT_TEST(testUpdateGenre24); CPPUNIT_TEST(testUpdateDate22); + CPPUNIT_TEST(testDowngradeTo23); // CPPUNIT_TEST(testUpdateFullDate22); TODO TYE+TDA should be upgraded to TDRC together CPPUNIT_TEST(testCompressedFrameWithBrokenLength); CPPUNIT_TEST_SUITE_END(); @@ -474,6 +475,48 @@ public: CPPUNIT_ASSERT_EQUAL(String("2010-04-03"), f.ID3v2Tag()->frameListMap()["TDRC"].front()->toString()); } + void testDowngradeTo23() + { + ScopedFileCopy copy("xing", ".mp3"); + string newname = copy.fileName(); + + ID3v2::TextIdentificationFrame *tf; + MPEG::File foo(newname.c_str()); + tf = new ID3v2::TextIdentificationFrame("TDOR", String::Latin1); + tf->setText("2011-03-16"); + foo.ID3v2Tag()->addFrame(tf); + tf = new ID3v2::TextIdentificationFrame("TDRC", String::Latin1); + tf->setText("2012-04-17"); + foo.ID3v2Tag()->addFrame(tf); + foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TDRL", String::Latin1)); + foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TDTG", String::Latin1)); + foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TMOO", String::Latin1)); + foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TPRO", String::Latin1)); + foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TSOA", String::Latin1)); + foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TSOT", String::Latin1)); + foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TSST", String::Latin1)); + foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TSOP", String::Latin1)); + foo.save(MPEG::File::AllTags, true, 3); + + MPEG::File bar(newname.c_str()); + tf = static_cast(bar.ID3v2Tag()->frameList("TDOR").front()); + CPPUNIT_ASSERT(tf); + CPPUNIT_ASSERT_EQUAL(TagLib::uint(1), tf->fieldList().size()); + CPPUNIT_ASSERT_EQUAL(String("2011"), tf->fieldList().front()); + tf = static_cast(bar.ID3v2Tag()->frameList("TDRC").front()); + CPPUNIT_ASSERT(tf); + CPPUNIT_ASSERT_EQUAL(TagLib::uint(1), tf->fieldList().size()); + CPPUNIT_ASSERT_EQUAL(String("2012"), tf->fieldList().front()); + CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TDRL")); + CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TDTG")); + CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TMOO")); + CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TPRO")); + CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TSOA")); + CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TSOT")); + CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TSST")); + CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TSOP")); + } + void testCompressedFrameWithBrokenLength() { MPEG::File f("data/compressed_id3_frame.mp3", false);