From e75d6f616c82ebb00b405ce7dd03d77a7f589ff7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Lalinsk=C3=BD?= Date: Wed, 21 Nov 2012 17:21:30 +0100 Subject: [PATCH] Add support for reading MusicBrainz IDs from ID3v2 tags to PropertyMap --- .../id3v2/frames/textidentificationframe.cpp | 16 ++---- .../frames/uniquefileidentifierframe.cpp | 30 +++++++++++ .../id3v2/frames/uniquefileidentifierframe.h | 10 ++++ taglib/mpeg/id3v2/id3v2frame.cpp | 53 ++++++++++++++++++- taglib/mpeg/id3v2/id3v2frame.h | 9 ++++ taglib/mpeg/id3v2/id3v2tag.cpp | 8 ++- taglib/toolkit/tpropertymap.h | 48 +++++++++++++++++ tests/test_id3v2.cpp | 24 ++++++++- 8 files changed, 182 insertions(+), 16 deletions(-) diff --git a/taglib/mpeg/id3v2/frames/textidentificationframe.cpp b/taglib/mpeg/id3v2/frames/textidentificationframe.cpp index 3287063c..70ea50f8 100644 --- a/taglib/mpeg/id3v2/frames/textidentificationframe.cpp +++ b/taglib/mpeg/id3v2/frames/textidentificationframe.cpp @@ -381,18 +381,12 @@ void UserTextIdentificationFrame::setDescription(const String &s) PropertyMap UserTextIdentificationFrame::asProperties() const { - String tagName = description(); - PropertyMap map; - String key = tagName.upper(); - if(key.isNull()) // this frame's description is not a valid PropertyMap key -> add to unsupported list - map.unsupportedData().append(L"TXXX/" + description()); - else { - StringList v = fieldList(); - for(StringList::ConstIterator it = v.begin(); it != v.end(); ++it) - if(*it != description()) - map.insert(key, *it); - } + String tagName = txxxToKey(description()); + StringList v = fieldList(); + for(StringList::ConstIterator it = v.begin(); it != v.end(); ++it) + if(it != v.begin()) + map.insert(tagName, *it); return map; } diff --git a/taglib/mpeg/id3v2/frames/uniquefileidentifierframe.cpp b/taglib/mpeg/id3v2/frames/uniquefileidentifierframe.cpp index e12583ad..0725c729 100644 --- a/taglib/mpeg/id3v2/frames/uniquefileidentifierframe.cpp +++ b/taglib/mpeg/id3v2/frames/uniquefileidentifierframe.cpp @@ -24,8 +24,10 @@ ***************************************************************************/ #include +#include #include +#include "id3v2tag.h" #include "uniquefileidentifierframe.h" using namespace TagLib; @@ -87,6 +89,34 @@ String UniqueFileIdentifierFrame::toString() const return String::null; } +PropertyMap UniqueFileIdentifierFrame::asProperties() const +{ + PropertyMap map; + if(d->owner == "http://musicbrainz.org") { + map.insert("MUSICBRAINZ_RECORDINGID", String(d->identifier)); + } + else { + map.unsupportedData().append(frameID() + String("/") + d->owner); + } + return map; +} + +UniqueFileIdentifierFrame *UniqueFileIdentifierFrame::findByOwner(const ID3v2::Tag *tag, const String &o) // static +{ + ID3v2::FrameList comments = tag->frameList("UFID"); + + for(ID3v2::FrameList::ConstIterator it = comments.begin(); + it != comments.end(); + ++it) + { + UniqueFileIdentifierFrame *frame = dynamic_cast(*it); + if(frame && frame->owner() == o) + return frame; + } + + return 0; +} + void UniqueFileIdentifierFrame::parseFields(const ByteVector &data) { if(data.size() < 1) { diff --git a/taglib/mpeg/id3v2/frames/uniquefileidentifierframe.h b/taglib/mpeg/id3v2/frames/uniquefileidentifierframe.h index 0cf4b8f6..add5a2e0 100644 --- a/taglib/mpeg/id3v2/frames/uniquefileidentifierframe.h +++ b/taglib/mpeg/id3v2/frames/uniquefileidentifierframe.h @@ -94,6 +94,16 @@ namespace TagLib { virtual String toString() const; + PropertyMap asProperties() const; + + /*! + * UFID frames each have a unique owner. This searches for a UFID + * frame with the owner \a o and returns a pointer to it. + * + * \see owner() + */ + static UniqueFileIdentifierFrame *findByOwner(const Tag *tag, const String &o); + protected: virtual void parseFields(const ByteVector &data); virtual ByteVector renderFields() const; diff --git a/taglib/mpeg/id3v2/id3v2frame.cpp b/taglib/mpeg/id3v2/id3v2frame.cpp index 1079785d..372778b1 100644 --- a/taglib/mpeg/id3v2/id3v2frame.cpp +++ b/taglib/mpeg/id3v2/id3v2frame.cpp @@ -44,6 +44,7 @@ #include "frames/urllinkframe.h" #include "frames/unsynchronizedlyricsframe.h" #include "frames/commentsframe.h" +#include "frames/uniquefileidentifierframe.h" #include "frames/unknownframe.h" using namespace TagLib; @@ -126,6 +127,10 @@ Frame *Frame::createTextualFrame(const String &key, const StringList &values) // return frame; } } + if(key == "MUSICBRAINZ_RECORDINGID" && values.size() == 1) { + UniqueFileIdentifierFrame *frame = new UniqueFileIdentifierFrame("http://musicbrainz.org", values.front().data(String::UTF8)); + return frame; + } // now we check if it's one of the "special" cases: // -LYRICS: depending on the number of values, use USLT or TXXX (with description=LYRICS) if((key == "LYRICS" || key.startsWith(lyricsPrefix)) && values.size() == 1){ @@ -151,7 +156,7 @@ Frame *Frame::createTextualFrame(const String &key, const StringList &values) // return frame; } // if non of the above cases apply, we use a TXXX frame with the key as description - return new UserTextIdentificationFrame(key, values, String::UTF8); + return new UserTextIdentificationFrame(keyToTXXX(key), values, String::UTF8); } Frame::~Frame() @@ -387,6 +392,18 @@ static const char *frameTranslation[][2] = { //{ "USLT", "LYRICS" }, handled specially }; +static const TagLib::uint txxxFrameTranslationSize = 7; +static const char *txxxFrameTranslation[][2] = { + { "MusicBrainz Album Id", "MUSICBRAINZ_RELEASEID" }, + { "MusicBrainz Artist Id", "MUSICBRAINZ_ARTISTID" }, + { "MusicBrainz Album Artist Id", "MUSICBRAINZ_RELEASEARTISTID" }, + { "MusicBrainz Release Group Id", "MUSICBRAINZ_RELEASEGROUPID" }, + { "MusicBrainz Work Id", "MUSICBRAINZ_WORKID" }, + { "Acoustid Id", "ACOUSTID_ID" }, + { "Acoustid Fingerprint", "ACOUSTID_FINGERPRINT" }, + { "MusicIP PUID", "MUSICIP_PUID" }, +}; + Map &idMap() { static Map m; @@ -396,6 +413,18 @@ Map &idMap() return m; } +Map &txxxMap() +{ + static Map m; + if(m.isEmpty()) { + for(size_t i = 0; i < txxxFrameTranslationSize; ++i) { + String key = String(txxxFrameTranslation[i][0]).upper(); + m[key] = txxxFrameTranslation[i][1]; + } + } + return m; +} + // list of deprecated frames and their successors static const TagLib::uint deprecatedFramesSize = 4; static const char *deprecatedFrames[][2] = { @@ -435,6 +464,26 @@ ByteVector Frame::keyToFrameID(const String &s) return ByteVector::null; } +String Frame::txxxToKey(const String &description) +{ + Map &m = txxxMap(); + String d = description.upper(); + if(m.contains(d)) + return m[d]; + return d; +} + +String Frame::keyToTXXX(const String &s) +{ + static Map m; + if(m.isEmpty()) + for(size_t i = 0; i < txxxFrameTranslationSize; ++i) + m[txxxFrameTranslation[i][1]] = txxxFrameTranslation[i][0]; + if(m.contains(s.upper())) + return m[s]; + return s; +} + PropertyMap Frame::asProperties() const { if(dynamic_cast< const UnknownFrame *>(this)) { @@ -456,6 +505,8 @@ PropertyMap Frame::asProperties() const return dynamic_cast< const CommentsFrame* >(this)->asProperties(); else if(id == "USLT") return dynamic_cast< const UnsynchronizedLyricsFrame* >(this)->asProperties(); + else if(id == "UFID") + return dynamic_cast< const UniqueFileIdentifierFrame* >(this)->asProperties(); PropertyMap m; m.unsupportedData().append(id); return m; diff --git a/taglib/mpeg/id3v2/id3v2frame.h b/taglib/mpeg/id3v2/id3v2frame.h index 95c4070b..3e257d4f 100644 --- a/taglib/mpeg/id3v2/id3v2frame.h +++ b/taglib/mpeg/id3v2/id3v2frame.h @@ -274,6 +274,15 @@ namespace TagLib { */ static String frameIDToKey(const ByteVector &); + /*! + * Returns an appropriate TXXX frame description for the given free-form tag key. + */ + static String keyToTXXX(const String &); + + /*! + * Returns a free-form tag name for the given ID3 frame description. + */ + static String txxxToKey(const String &); /*! * This helper function splits the PropertyMap \a original into three ProperytMaps diff --git a/taglib/mpeg/id3v2/id3v2tag.cpp b/taglib/mpeg/id3v2/id3v2tag.cpp index 54e63920..a1b169e1 100644 --- a/taglib/mpeg/id3v2/id3v2tag.cpp +++ b/taglib/mpeg/id3v2/id3v2tag.cpp @@ -379,10 +379,12 @@ void ID3v2::Tag::removeUnsupportedProperties(const StringList &properties) for(FrameList::ConstIterator fit = l.begin(); fit != l.end(); fit++) if (dynamic_cast(*fit) != 0) removeFrame(*fit); - } else if(it->size() == 4){ + } + else if(it->size() == 4){ ByteVector id = it->data(String::Latin1); removeFrames(id); - } else { + } + else { ByteVector id = it->substr(0,4).data(String::Latin1); if(it->size() <= 5) continue; // invalid specification @@ -396,6 +398,8 @@ void ID3v2::Tag::removeUnsupportedProperties(const StringList &properties) frame = CommentsFrame::findByDescription(this, description); else if(id == "USLT") frame = UnsynchronizedLyricsFrame::findByDescription(this, description); + else if(id == "UFID") + frame = UniqueFileIdentifierFrame::findByOwner(this, description); if(frame) removeFrame(frame); } diff --git a/taglib/toolkit/tpropertymap.h b/taglib/toolkit/tpropertymap.h index 7f59b21e..8073003e 100644 --- a/taglib/toolkit/tpropertymap.h +++ b/taglib/toolkit/tpropertymap.h @@ -40,6 +40,54 @@ namespace TagLib { * Note that most metadata formats pose additional conditions on the tag keys. The * most popular ones (Vorbis, APE, ID3v2) should support all ASCII only words of * length between 2 and 16. + * + * This class can contain any tags, but here is a list of "well-known" tags that + * you might want to use: + * + * Basic tags: + * + * - TITLE + * - ALBUM + * - ARTIST + * - ALBUMARTIST + * - SUBTITLE + * - TRACKNUMBER + * - DISCNUMBER + * - DATE + * - ORIGINALDATE + * - GENRE + * - COMMENT + * + * Credits: + * + * - COMPOSER + * - LYRICIST + * - CONDUCTOR + * - REMIXER + * - PERFORMER: + * + * Other tags: + * + * - ISRC + * - ASIN + * - BPM + * - COPYRIGHT + * - ENCODEDBY + * - MOOD + * - COMMENT + * + * MusicBrainz identifiers: + * + * - MUSICBRAINZ_RECORDINGID + * - MUSICBRAINZ_RELEASEID + * - MUSICBRAINZ_RELEASEGROUPID + * - MUSICBRAINZ_WORKID + * - MUSICBRAINZ_ARTISTID + * - MUSICBRAINZ_RELEASEARTISTID + * - ACOUSTID_ID + * - ACOUSTID_FINGERPRINT + * - MUSICIP_PUID + * */ class TAGLIB_EXPORT PropertyMap: public SimplePropertyMap diff --git a/tests/test_id3v2.cpp b/tests/test_id3v2.cpp index 99f77101..b48bd68f 100644 --- a/tests/test_id3v2.cpp +++ b/tests/test_id3v2.cpp @@ -624,7 +624,7 @@ public: CPPUNIT_ASSERT_EQUAL(String("A COMMENT"), dict["COMMENT"].front()); CPPUNIT_ASSERT_EQUAL(1u, dict.unsupportedData().size()); - CPPUNIT_ASSERT_EQUAL(String("UFID"), dict.unsupportedData().front()); + CPPUNIT_ASSERT_EQUAL(String("UFID/supermihi@web.de"), dict.unsupportedData().front()); } void testPropertyInterface2() @@ -657,11 +657,23 @@ public: frame5->setText(tmclData); tag.addFrame(frame5); + ID3v2::UniqueFileIdentifierFrame *frame6 = new ID3v2::UniqueFileIdentifierFrame("http://musicbrainz.org", "152454b9-19ba-49f3-9fc9-8fc26545cf41"); + tag.addFrame(frame6); + + ID3v2::UniqueFileIdentifierFrame *frame7 = new ID3v2::UniqueFileIdentifierFrame("http://example.com", "123"); + tag.addFrame(frame7); + + ID3v2::UserTextIdentificationFrame *frame8 = new ID3v2::UserTextIdentificationFrame(); + frame8->setDescription("MusicBrainz Album Id"); + frame8->setText("95c454a5-d7e0-4d8f-9900-db04aca98ab3"); + tag.addFrame(frame8); + PropertyMap properties = tag.properties(); - CPPUNIT_ASSERT_EQUAL(2u, properties.unsupportedData().size()); + CPPUNIT_ASSERT_EQUAL(3u, properties.unsupportedData().size()); CPPUNIT_ASSERT(properties.unsupportedData().contains("TIPL")); CPPUNIT_ASSERT(properties.unsupportedData().contains("APIC")); + CPPUNIT_ASSERT(properties.unsupportedData().contains("UFID/http://example.com")); CPPUNIT_ASSERT(properties.contains("PERFORMER:VIOLIN")); CPPUNIT_ASSERT(properties.contains("PERFORMER:PIANO")); @@ -671,9 +683,17 @@ public: CPPUNIT_ASSERT(properties.contains("LYRICS")); CPPUNIT_ASSERT(properties.contains("LYRICS:TEST")); + CPPUNIT_ASSERT(properties.contains("MUSICBRAINZ_RECORDINGID")); + CPPUNIT_ASSERT_EQUAL(String("152454b9-19ba-49f3-9fc9-8fc26545cf41"), properties["MUSICBRAINZ_RECORDINGID"].front()); + + CPPUNIT_ASSERT(properties.contains("MUSICBRAINZ_RELEASEID")); + CPPUNIT_ASSERT_EQUAL(String("95c454a5-d7e0-4d8f-9900-db04aca98ab3"), properties["MUSICBRAINZ_RELEASEID"].front()); + tag.removeUnsupportedProperties(properties.unsupportedData()); CPPUNIT_ASSERT(tag.frameList("APIC").isEmpty()); CPPUNIT_ASSERT(tag.frameList("TIPL").isEmpty()); + CPPUNIT_ASSERT_EQUAL((ID3v2::UniqueFileIdentifierFrame *)0, ID3v2::UniqueFileIdentifierFrame::findByOwner(&tag, "http://example.com")); + CPPUNIT_ASSERT_EQUAL(frame6, ID3v2::UniqueFileIdentifierFrame::findByOwner(&tag, "http://musicbrainz.org")); } void testDeleteFrame()