]> granicus.if.org Git - taglib/commitdiff
Implement the PropertyMap interface for MP4
authorLukáš Lalinský <lalinsky@gmail.com>
Thu, 22 Nov 2012 09:40:22 +0000 (10:40 +0100)
committerLukáš Lalinský <lalinsky@gmail.com>
Thu, 22 Nov 2012 09:40:22 +0000 (10:40 +0100)
taglib/ape/apefile.h
taglib/mp4/mp4file.cpp
taglib/mp4/mp4file.h
taglib/mp4/mp4tag.cpp
taglib/mp4/mp4tag.h
taglib/mpeg/id3v2/id3v2frame.cpp
taglib/toolkit/tfile.cpp
taglib/toolkit/tpropertymap.h
tests/test_mp4.cpp

index 8b187f6a63c483c10a3bc3e6edc30a510eecfb78..7be0f21da50d80b8f868349a63178bf4f2fd7483 100644 (file)
@@ -132,6 +132,7 @@ namespace TagLib {
        * has no tag at all, APE will be created.
        */
       PropertyMap setProperties(const PropertyMap &);
+
       /*!
        * Returns the APE::Properties for this file.  If no audio properties
        * were read then this will return a null pointer.
index a95432117dd84280186a33c5ec42ed1bcbd6b94b..f41ee88860ebba36d5aa595f1bbf2e95a1a7ee06 100644 (file)
@@ -29,6 +29,7 @@
 
 #include <tdebug.h>
 #include <tstring.h>
+#include <tpropertymap.h>
 #include "mp4atom.h"
 #include "mp4tag.h"
 #include "mp4file.h"
@@ -90,6 +91,21 @@ MP4::File::tag() const
   return d->tag;
 }
 
+PropertyMap MP4::File::properties() const
+{
+  return d->tag->properties();
+}
+
+void MP4::File::removeUnsupportedProperties(const StringList &properties)
+{
+  d->tag->removeUnsupportedProperties(properties);
+}
+
+PropertyMap MP4::File::setProperties(const PropertyMap &properties)
+{
+  return d->tag->setProperties(properties);
+}
+
 MP4::Properties *
 MP4::File::audioProperties() const
 {
index 2ed3bea581e087b0bb089a930aaf37ca793201d2..17fd5a95a67b262a8bfb6999adc9936e8483a304 100644 (file)
@@ -88,6 +88,22 @@ namespace TagLib {
        */
       Tag *tag() const;
 
+      /*!
+       * Implements the unified property interface -- export function.
+       */
+      PropertyMap properties() const;
+
+      /*!
+       * Removes unsupported properties. Forwards to the actual Tag's
+       * removeUnsupportedProperties() function.
+       */
+      void removeUnsupportedProperties(const StringList &properties);
+
+      /*!
+       * Implements the unified property interface -- import function.
+       */
+      PropertyMap setProperties(const PropertyMap &);
+
       /*!
        * Returns the MP4 audio properties for this file.
        */
index afecf98ad5f99c793cea813392f36ca4816d6666..3c22f0622f4623cbdc6a3ae4170c557aa9ce0e91 100644 (file)
@@ -29,6 +29,7 @@
 
 #include <tdebug.h>
 #include <tstring.h>
+#include <tpropertymap.h>
 #include "mp4atom.h"
 #include "mp4tag.h"
 #include "id3v1genres.h"
@@ -759,3 +760,153 @@ MP4::Tag::itemListMap()
   return d->items;
 }
 
+static const char *keyTranslation[][2] = {
+  { "\251nam", "TITLE" },
+  { "\251ART", "ARTIST" },
+  { "\251alb", "ALBUM" },
+  { "\251cmt", "COMMENT" },
+  { "\251gen", "GENRE" },
+  { "\251day", "DATE" },
+  { "\251wrt", "COMPOSER" },
+  { "\251grp", "GROUPING" },
+  { "trkn", "TRACKNUMBER" },
+  { "disk", "DISCNUMBER" },
+  { "cpil", "COMPILATION" },
+  { "tmpo", "BPM" },
+  { "cprt", "COPYRIGHT" },
+  { "\251lyr", "LYRICS" },
+  { "\251too", "ENCODEDBY" },
+  { "soal", "ALBUMSORT" },
+  { "soaa", "ALBUMARTISTSORT" },
+  { "soar", "ARTISTSORT" },
+  { "sonm", "TITLESORT" },
+  { "soco", "COMPOSERSORT" },
+  { "sosn", "SHOWSORT" },
+  { "----:com.apple.iTunes:MusicBrainz Track Id", "MUSICBRAINZ_TRACKID" },
+  { "----:com.apple.iTunes:MusicBrainz Artist Id", "MUSICBRAINZ_ARTISTID" },
+  { "----:com.apple.iTunes:MusicBrainz Album Id", "MUSICBRAINZ_ALBUMID" },
+  { "----:com.apple.iTunes:MusicBrainz Album Artist Id", "MUSICBRAINZ_ALBUMARTISTID" },
+  { "----:com.apple.iTunes:MusicBrainz Release Group Id", "MUSICBRAINZ_RELEASEGROUPID" },
+  { "----:com.apple.iTunes:MusicBrainz Work Id", "MUSICBRAINZ_WORKID" },
+  { "----:com.apple.iTunes:ASIN", "ASIN" },
+  { "----:com.apple.iTunes:LABEL", "LABEL" },
+  { "----:com.apple.iTunes:LYRICIST", "LYRICIST" },
+  { "----:com.apple.iTunes:CONDUCTOR", "CONDUCTOR" },
+  { "----:com.apple.iTunes:REMIXER", "REMIXER" },
+  { "----:com.apple.iTunes:ENGINEER", "ENGINEER" },
+  { "----:com.apple.iTunes:PRODUCER", "PRODUCER" },
+  { "----:com.apple.iTunes:DJMIXER", "DJMIXER" },
+  { "----:com.apple.iTunes:MIXER", "MIXER" },
+  { "----:com.apple.iTunes:SUBTITLE", "SUBTITLE" },
+  { "----:com.apple.iTunes:DISCSUBTITLE", "DISCSUBTITLE" },
+  { "----:com.apple.iTunes:MOOD", "MOOD" },
+  { "----:com.apple.iTunes:ISRC", "ISRC" },
+  { "----:com.apple.iTunes:CATALOGNUMBER", "CATALOGNUMBER" },
+  { "----:com.apple.iTunes:BARCODE", "BARCODE" },
+  { "----:com.apple.iTunes:SCRIPT", "SCRIPT" },
+  { "----:com.apple.iTunes:LANGUAGE", "LANGUAGE" },
+  { "----:com.apple.iTunes:LICENSE", "LICENSE" },
+  { "----:com.apple.iTunes:MEDIA", "MEDIA" },
+};
+
+PropertyMap MP4::Tag::properties() const
+{
+  static Map<String, String> keyMap;
+  if(keyMap.isEmpty()) {
+    int numKeys = sizeof(keyTranslation) / sizeof(keyTranslation[0]);
+    for(int i = 0; i < numKeys; i++) {
+      keyMap[keyTranslation[i][0]] = keyTranslation[i][1];
+    }
+  }
+
+  PropertyMap props;
+  MP4::ItemListMap::ConstIterator it = d->items.begin();
+  for(; it != d->items.end(); ++it) {
+    if(keyMap.contains(it->first)) {
+      String key = keyMap[it->first];
+      if(key == "TRACKNUMBER" || key == "DISCNUMBER") {
+        MP4::Item::IntPair ip = it->second.toIntPair();
+        String value = String::number(ip.first);
+        if(ip.second) {
+          value += "/" + String::number(ip.second);
+        }
+        props[key] = value;
+      }
+      else if(key == "BPM") {
+        props[key] = String::number(it->second.toInt());
+      }
+      else if(key == "COMPILATION") {
+        props[key] = String::number(it->second.toBool());
+      }
+      else {
+        props[key] = it->second.toStringList();
+      }
+    }
+    else {
+      props.unsupportedData().append(it->first);
+    }
+  }
+  return props;
+}
+
+void MP4::Tag::removeUnsupportedProperties(const StringList &props)
+{
+  StringList::ConstIterator it = props.begin();
+  for(; it != props.end(); ++it)
+    d->items.erase(*it);
+}
+
+PropertyMap MP4::Tag::setProperties(const PropertyMap &props)
+{
+  static Map<String, String> reverseKeyMap;
+  if(reverseKeyMap.isEmpty()) {
+    int numKeys = sizeof(keyTranslation) / sizeof(keyTranslation[0]);
+    for(int i = 0; i < numKeys; i++) {
+      reverseKeyMap[keyTranslation[i][1]] = keyTranslation[i][0];
+    }
+  }
+
+  PropertyMap origProps = properties();
+  PropertyMap::ConstIterator it = origProps.begin();
+  for(; it != origProps.end(); ++it) {
+    if(!props.contains(it->first) || props[it->first].isEmpty()) {
+      d->items.erase(reverseKeyMap[it->first]);
+    }
+  }
+
+  PropertyMap ignoredProps;
+  it = props.begin();
+  for(; it != props.end(); ++it) {
+    if(reverseKeyMap.contains(it->first)) {
+      String name = reverseKeyMap[it->first];
+      if(it->first == "TRACKNUMBER" || it->first == "DISCNUMBER") {
+        int first = 0, second = 0;
+        StringList parts = StringList::split(it->second.front(), "/");
+        if(parts.size() > 0) {
+          first = parts[0].toInt();
+          if(parts.size() > 1) {
+            second = parts[1].toInt();
+          }
+          d->items[name] = MP4::Item(first, second);
+        }
+      }
+      else if(it->first == "BPM") {
+        int value = it->second.front().toInt();
+        d->items[name] = MP4::Item(value);
+      }
+      else if(it->first == "COMPILATION") {
+        bool value = it->second.front().toInt();
+        d->items[name] = MP4::Item(value > 0);
+      }
+      else {
+        d->items[name] = it->second;
+      }
+    }
+    else {
+      ignoredProps.insert(it->first, it->second);
+    }
+  }
+
+  return ignoredProps;
+}
+
index b5ea6ebbc5ef9e7f824fd2fea8474ca6745ce08e..0e1d0676ab26472bb54bce23c19f8318dc503306 100644 (file)
@@ -67,6 +67,10 @@ namespace TagLib {
 
         ItemListMap &itemListMap();
 
+        PropertyMap properties() const;
+        void removeUnsupportedProperties(const StringList& properties);
+        PropertyMap setProperties(const PropertyMap &properties);
+
     private:
         AtomDataList parseData2(Atom *atom, TagLib::File *file, int expectedFlags = -1, bool freeForm = false);
         TagLib::ByteVectorList parseData(Atom *atom, TagLib::File *file, int expectedFlags = -1, bool freeForm = false);
index 37d96846968794b3ddc8fd101322dac6ac04ab9a..f94a074ef7ce60a6d3efab1373f75d21c28299d3 100644 (file)
@@ -355,7 +355,7 @@ static const char *frameTranslation[][2] = {
   { "TLAN", "LANGUAGE" },
   { "TLEN", "LENGTH" },
   //{ "TMCL", "MUSICIANCREDITS" }, handled separately
-  { "TMED", "MEDIATYPE" },
+  { "TMED", "MEDIA" },
   { "TMOO", "MOOD" },
   { "TOAL", "ORIGINALALBUM" },
   { "TOFN", "ORIGINALFILENAME" },
index 8d7ccdc95279e17ce775f05bb11e6c22fa681f39..2457949686449fe961bed3823ce801f6d1619a71 100644 (file)
@@ -69,6 +69,7 @@
 #include "s3mfile.h"
 #include "itfile.h"
 #include "xmfile.h"
+#include "mp4file.h"
 
 using namespace TagLib;
 
@@ -152,12 +153,11 @@ PropertyMap File::properties() const
     return dynamic_cast<const WavPack::File* >(this)->properties();
   if(dynamic_cast<const XM::File* >(this))
     return dynamic_cast<const XM::File* >(this)->properties();
+  if(dynamic_cast<const MP4::File* >(this))
+    return dynamic_cast<const MP4::File* >(this)->properties();
   // no specialized implementation available -> use generic one
   // - ASF: ugly format, largely undocumented, not worth implementing
   //   dict interface ...
-  // - MP4: taglib's MP4::Tag does not really support anything beyond
-  //   the basic implementation, therefor we use just the default Tag
-  //   interface
   return tag()->properties();
 }
 
@@ -193,6 +193,8 @@ void File::removeUnsupportedProperties(const StringList &properties)
     dynamic_cast<WavPack::File* >(this)->removeUnsupportedProperties(properties);
   else if(dynamic_cast<XM::File* >(this))
     dynamic_cast<XM::File* >(this)->removeUnsupportedProperties(properties);
+  else if(dynamic_cast<MP4::File* >(this))
+    dynamic_cast<MP4::File* >(this)->removeUnsupportedProperties(properties);
   else
     tag()->removeUnsupportedProperties(properties);
 }
@@ -231,6 +233,8 @@ PropertyMap File::setProperties(const PropertyMap &properties)
     return dynamic_cast<WavPack::File* >(this)->setProperties(properties);
   else if(dynamic_cast<XM::File* >(this))
     return dynamic_cast<XM::File* >(this)->setProperties(properties);
+  else if(dynamic_cast<MP4::File* >(this))
+    return dynamic_cast<MP4::File* >(this)->setProperties(properties);
   else
     return tag()->setProperties(properties);
 }
index d67eea8884169c9c0588e5df9877ba49e2d4429f..51d5df6fe31892a5462b8d2249c71393a9633db8 100644 (file)
@@ -82,6 +82,9 @@ namespace TagLib {
    *  - ENCODEDBY
    *  - MOOD
    *  - COMMENT 
+   *  - MEDIA
+   *  - CATALOGNUMBER
+   *  - BARCODE
    *
    * MusicBrainz identifiers:
    * 
index d6830a61252cedf5798ba15f989136a05ab1cf27..be7ad2c3c6cf68b54770ce9c11ec5135b1f3e08e 100644 (file)
@@ -3,6 +3,7 @@
 #include <tag.h>
 #include <mp4tag.h>
 #include <tbytevectorlist.h>
+#include <tpropertymap.h>
 #include <mp4atom.h>
 #include <mp4file.h>
 #include <cppunit/extensions/HelperMacros.h>
@@ -25,6 +26,7 @@ class TestMP4 : public CppUnit::TestFixture
   CPPUNIT_TEST(testCovrRead);
   CPPUNIT_TEST(testCovrWrite);
   CPPUNIT_TEST(testCovrRead2);
+  CPPUNIT_TEST(testProperties);
   CPPUNIT_TEST_SUITE_END();
 
 public:
@@ -224,6 +226,55 @@ public:
     delete f;
   }
 
+  void testProperties()
+  {
+    MP4::File f(TEST_FILE_PATH_C("has-tags.m4a"));
+    
+    PropertyMap tags = f.properties();
+
+    CPPUNIT_ASSERT_EQUAL(StringList("Test Artist"), tags["ARTIST"]);
+
+    tags["TRACKNUMBER"] = StringList("2/4");
+    tags["DISCNUMBER"] = StringList("3/5");
+    tags["BPM"] = StringList("123");
+    tags["ARTIST"] = StringList("Foo Bar");
+    tags["COMPILATION"] = StringList("1");
+    f.setProperties(tags);
+
+    tags = f.properties();
+
+    CPPUNIT_ASSERT(f.tag()->itemListMap().contains("trkn"));
+    CPPUNIT_ASSERT_EQUAL(2, f.tag()->itemListMap()["trkn"].toIntPair().first);
+    CPPUNIT_ASSERT_EQUAL(4, f.tag()->itemListMap()["trkn"].toIntPair().second);
+    CPPUNIT_ASSERT_EQUAL(StringList("2/4"), tags["TRACKNUMBER"]);
+
+    CPPUNIT_ASSERT(f.tag()->itemListMap().contains("disk"));
+    CPPUNIT_ASSERT_EQUAL(3, f.tag()->itemListMap()["disk"].toIntPair().first);
+    CPPUNIT_ASSERT_EQUAL(5, f.tag()->itemListMap()["disk"].toIntPair().second);
+    CPPUNIT_ASSERT_EQUAL(StringList("3/5"), tags["DISCNUMBER"]);
+
+    CPPUNIT_ASSERT(f.tag()->itemListMap().contains("tmpo"));
+    CPPUNIT_ASSERT_EQUAL(123, f.tag()->itemListMap()["tmpo"].toInt());
+    CPPUNIT_ASSERT_EQUAL(StringList("123"), tags["BPM"]);
+
+    CPPUNIT_ASSERT(f.tag()->itemListMap().contains("\251ART"));
+    CPPUNIT_ASSERT_EQUAL(StringList("Foo Bar"), f.tag()->itemListMap()["\251ART"].toStringList());
+    CPPUNIT_ASSERT_EQUAL(StringList("Foo Bar"), tags["ARTIST"]);
+
+    CPPUNIT_ASSERT(f.tag()->itemListMap().contains("cpil"));
+    CPPUNIT_ASSERT_EQUAL(true, f.tag()->itemListMap()["cpil"].toBool());
+    CPPUNIT_ASSERT_EQUAL(StringList("1"), tags["COMPILATION"]);
+
+    tags["COMPILATION"] = StringList("0");
+    f.setProperties(tags);
+
+    tags = f.properties();
+
+    CPPUNIT_ASSERT(f.tag()->itemListMap().contains("cpil"));
+    CPPUNIT_ASSERT_EQUAL(false, f.tag()->itemListMap()["cpil"].toBool());
+    CPPUNIT_ASSERT_EQUAL(StringList("0"), tags["COMPILATION"]);
+  }
+
 };
 
 CPPUNIT_TEST_SUITE_REGISTRATION(TestMP4);