]> granicus.if.org Git - taglib/commitdiff
Add support for reading MusicBrainz IDs from ID3v2 tags to PropertyMap
authorLukáš Lalinský <lalinsky@gmail.com>
Wed, 21 Nov 2012 16:21:30 +0000 (17:21 +0100)
committerLukáš Lalinský <lalinsky@gmail.com>
Wed, 21 Nov 2012 16:21:30 +0000 (17:21 +0100)
taglib/mpeg/id3v2/frames/textidentificationframe.cpp
taglib/mpeg/id3v2/frames/uniquefileidentifierframe.cpp
taglib/mpeg/id3v2/frames/uniquefileidentifierframe.h
taglib/mpeg/id3v2/id3v2frame.cpp
taglib/mpeg/id3v2/id3v2frame.h
taglib/mpeg/id3v2/id3v2tag.cpp
taglib/toolkit/tpropertymap.h
tests/test_id3v2.cpp

index 3287063ca63d5c8826df7255f081881e56511db4..70ea50f8fb34b7f1ff6c89a41f749c466f8c7c2e 100644 (file)
@@ -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;
 }
 
index e12583ade51136a9e312c33375f0abc02440d297..0725c729a7227fba17f9d3132afaad0fbf8a2a76 100644 (file)
  ***************************************************************************/
 
 #include <tbytevectorlist.h>
+#include <tpropertymap.h>
 #include <tdebug.h>
 
+#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<UniqueFileIdentifierFrame *>(*it);
+    if(frame && frame->owner() == o)
+      return frame;
+  }
+
+  return 0;
+}
+
 void UniqueFileIdentifierFrame::parseFields(const ByteVector &data)
 {
   if(data.size() < 1) {
index 0cf4b8f6e01b2d348033f7ea43ea2d8a0917114a..add5a2e0c23633b726dee794f161f14ada82b655 100644 (file)
@@ -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;
index 1079785d25913131d53fe2a4e16b540641c84e29..372778b1e1b09bc8f429e6974ffea0e78a31047f 100644 (file)
@@ -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<ByteVector, String> &idMap()
 {
   static Map<ByteVector, String> m;
@@ -396,6 +413,18 @@ Map<ByteVector, String> &idMap()
   return m;
 }
 
+Map<String, String> &txxxMap()
+{
+  static Map<String, String> 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<String, String> &m = txxxMap();
+  String d = description.upper();
+  if(m.contains(d))
+    return m[d];
+  return d;
+}
+
+String Frame::keyToTXXX(const String &s)
+{
+  static Map<String, String> 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;
index 95c4070b4fd4d9ba5c895265a9ac9455636ad6e8..3e257d4f545429926ce85fd64cfbcd48b14166eb 100644 (file)
@@ -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
index 54e63920364b26dbf3fd66184f345d2729b83d87..a1b169e1d18bb3b7703ce24066564dd0c137dcde 100644 (file)
@@ -379,10 +379,12 @@ void ID3v2::Tag::removeUnsupportedProperties(const StringList &properties)
       for(FrameList::ConstIterator fit = l.begin(); fit != l.end(); fit++)
         if (dynamic_cast<const UnknownFrame *>(*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);
     }
index 7f59b21ea0375711d94426915b07a33fb425f5bc..8073003e9d6f2880a8fe5c9962eb7da8ec557d5b 100644 (file)
@@ -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:<XXXX>
+   *
+   * 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
index 99f77101cf3354dd1b0c5b7369ea61462b1d78e2..b48bd68f135b848316c86729832ec51501958b83 100644 (file)
@@ -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()