]> granicus.if.org Git - taglib/commitdiff
Support for MP4 cover art
authorLukáš Lalinský <lalinsky@gmail.com>
Sat, 24 Oct 2009 16:55:54 +0000 (16:55 +0000)
committerLukáš Lalinský <lalinsky@gmail.com>
Sat, 24 Oct 2009 16:55:54 +0000 (16:55 +0000)
CCMAIL:martin.trashbox@gmail.com

git-svn-id: svn://anonsvn.kde.org/home/kde/trunk/kdesupport/taglib@1039809 283d02a7-25f6-0310-bc7c-ecb5cbfe19da

15 files changed:
NEWS
include/mp4coverart.h [new file with mode: 0644]
taglib/CMakeLists.txt
taglib/mp4/CMakeLists.txt
taglib/mp4/Makefile.am
taglib/mp4/mp4coverart.cpp [new file with mode: 0644]
taglib/mp4/mp4coverart.h [new file with mode: 0644]
taglib/mp4/mp4item.cpp
taglib/mp4/mp4item.h
taglib/mp4/mp4tag.cpp
taglib/mp4/mp4tag.h
tests/CMakeLists.txt
tests/test_mp4.cpp
tests/test_mp4coverart.cpp [new file with mode: 0644]
tests/test_mp4item.cpp [new file with mode: 0644]

diff --git a/NEWS b/NEWS
index 0d4bd52737955c953b419751badc5a171d878c14..437af6e13746560df4e58d54a1f626963658d702 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -1,14 +1,15 @@
 TagLib 1.6.1
 ============
 
- * Better detection of .oga files in FileRef.
+ * Better detection of the audio codec of .oga files in FileRef.
  * Fixed saving of Vorbis comments to Ogg FLAC files. TagLib tried to
    include the Vorbis framing bit, which is only correct for Ogg Vorbis.
  * Public symbols now have explicitly set visibility to "default" on GCC.
  * Added missing exports for static ID3v1 functions.
  * Fixed a typo in taglib_c.pc
  * Fixed a failing test on ppc64.
- * Support for binary 'covr' atom in MP4 files.
+ * Support for binary 'covr' atom in MP4 files. TagLib 1.6 treated them
+   as text atoms, which corrupted them in some cases.
 
 TagLib 1.6
 ==========
diff --git a/include/mp4coverart.h b/include/mp4coverart.h
new file mode 100644 (file)
index 0000000..f5ca629
--- /dev/null
@@ -0,0 +1 @@
+#include "../taglib/mp4/mp4coverart.h"
index 852776fd3b5594ecb9dfe1cda341e5b6107eb702..3314d6cebda4461134c8936df7fd446df0018436 100644 (file)
@@ -111,6 +111,7 @@ mp4/mp4atom.cpp
 mp4/mp4tag.cpp
 mp4/mp4item.cpp
 mp4/mp4properties.cpp
+mp4/mp4coverart.cpp
 )
 ELSE(WITH_MP4)
 SET(mp4_SRCS)
@@ -200,6 +201,7 @@ SET_TARGET_PROPERTIES(tag PROPERTIES
         SOVERSION ${TAGLIB_LIB_MAJOR_VERSION}
         INSTALL_NAME_DIR ${LIB_INSTALL_DIR}
         DEFINE_SYMBOL MAKE_TAGLIB_LIB
+        LINK_INTERFACE_LIBRARIES ""
 )
 INSTALL(TARGETS tag
        LIBRARY DESTINATION ${LIB_INSTALL_DIR}
index 803a9d526adebf5e8fdd246c17d9ae392568d8f2..993a5c0bc41fcebceb9d3bb5bd1661a643660583 100644 (file)
@@ -1 +1 @@
-INSTALL( FILES  mp4file.h mp4atom.h mp4tag.h mp4item.h mp4properties.h DESTINATION ${INCLUDE_INSTALL_DIR}/taglib)
+INSTALL( FILES  mp4file.h mp4atom.h mp4tag.h mp4item.h mp4properties.h mp4coverart.h DESTINATION ${INCLUDE_INSTALL_DIR}/taglib)
index bc2faa4ec4b13ae0448998854ad3f9cb6c37313e..68f081241461c83010b3d717433d764fbfbe3d06 100644 (file)
@@ -7,7 +7,7 @@ INCLUDES = \
 
 noinst_LTLIBRARIES = libmp4.la
 
-libmp4_la_SOURCES = mp4atom.cpp mp4file.cpp mp4item.cpp mp4properties.cpp mp4tag.cpp
+libmp4_la_SOURCES = mp4atom.cpp mp4file.cpp mp4item.cpp mp4properties.cpp mp4tag.cpp mp4coverart.cpp
 
-taglib_include_HEADERS = mp4atom.h mp4file.h mp4item.h mp4properties.h mp4tag.h
+taglib_include_HEADERS = mp4atom.h mp4file.h mp4item.h mp4properties.h mp4tag.h mp4coverart.h
 taglib_includedir = $(includedir)/taglib
diff --git a/taglib/mp4/mp4coverart.cpp b/taglib/mp4/mp4coverart.cpp
new file mode 100644 (file)
index 0000000..983df02
--- /dev/null
@@ -0,0 +1,89 @@
+/**************************************************************************
+    copyright            : (C) 2009 by Lukáš Lalinský
+    email                : lalinsky@gmail.com
+ **************************************************************************/
+
+/***************************************************************************
+ *   This library is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU Lesser General Public License version   *
+ *   2.1 as published by the Free Software Foundation.                     *
+ *                                                                         *
+ *   This library is distributed in the hope that it will be useful, but   *
+ *   WITHOUT ANY WARRANTY; without even the implied warranty of            *
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU     *
+ *   Lesser General Public License for more details.                       *
+ *                                                                         *
+ *   You should have received a copy of the GNU Lesser General Public      *
+ *   License along with this library; if not, write to the Free Software   *
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  *
+ *   USA                                                                   *
+ *                                                                         *
+ *   Alternatively, this file is available under the Mozilla Public        *
+ *   License Version 1.1.  You may obtain a copy of the License at         *
+ *   http://www.mozilla.org/MPL/                                           *
+ ***************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#ifdef WITH_MP4
+
+#include <taglib.h>
+#include <tdebug.h>
+#include "mp4coverart.h"
+
+using namespace TagLib;
+
+class MP4::CoverArt::CoverArtPrivate : public RefCounter
+{
+public:
+  CoverArtPrivate() : RefCounter(), format(MP4::CoverArt::JPEG) {}
+
+  Format format;
+  ByteVector data;
+};
+
+MP4::CoverArt::CoverArt(Format format, const ByteVector &data)
+{
+  d = new CoverArtPrivate;
+  d->format = format;
+  d->data = data;
+}
+
+MP4::CoverArt::CoverArt(const CoverArt &item) : d(item.d)
+{
+  d->ref();
+}
+
+MP4::CoverArt &
+MP4::CoverArt::operator=(const CoverArt &item)
+{
+  if(d->deref()) {
+    delete d;
+  }
+  d = item.d;
+  d->ref();
+  return *this;
+}
+
+MP4::CoverArt::~CoverArt()
+{
+  if(d->deref()) {
+    delete d;
+  }
+}
+
+MP4::CoverArt::Format
+MP4::CoverArt::format() const
+{
+  return d->format;
+}
+
+ByteVector
+MP4::CoverArt::data() const
+{
+  return d->data;
+}
+
+#endif
diff --git a/taglib/mp4/mp4coverart.h b/taglib/mp4/mp4coverart.h
new file mode 100644 (file)
index 0000000..00a7aff
--- /dev/null
@@ -0,0 +1,71 @@
+/**************************************************************************
+    copyright            : (C) 2009 by Lukáš Lalinský
+    email                : lalinsky@gmail.com
+ **************************************************************************/
+
+/***************************************************************************
+ *   This library is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU Lesser General Public License version   *
+ *   2.1 as published by the Free Software Foundation.                     *
+ *                                                                         *
+ *   This library is distributed in the hope that it will be useful, but   *
+ *   WITHOUT ANY WARRANTY; without even the implied warranty of            *
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU     *
+ *   Lesser General Public License for more details.                       *
+ *                                                                         *
+ *   You should have received a copy of the GNU Lesser General Public      *
+ *   License along with this library; if not, write to the Free Software   *
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  *
+ *   USA                                                                   *
+ *                                                                         *
+ *   Alternatively, this file is available under the Mozilla Public        *
+ *   License Version 1.1.  You may obtain a copy of the License at         *
+ *   http://www.mozilla.org/MPL/                                           *
+ ***************************************************************************/
+
+#ifndef TAGLIB_MP4COVERART_H
+#define TAGLIB_MP4COVERART_H
+
+#include "tlist.h"
+#include "tbytevector.h"
+#include "taglib_export.h"
+
+namespace TagLib {
+
+  namespace MP4 {
+
+    class TAGLIB_EXPORT CoverArt
+    {
+    public:
+      /*!
+       * This describes the image type.
+       */
+      enum Format {
+        JPEG = 0x0D,
+        PNG  = 0x0E
+      };
+
+      CoverArt(Format format, const ByteVector &data);
+      ~CoverArt();
+
+      CoverArt(const CoverArt &item);
+      CoverArt &operator=(const CoverArt &item);
+
+      //! Format of the image
+      Format format() const;
+
+      //! The image data
+      ByteVector data() const;
+
+    private:
+      class CoverArtPrivate;
+      CoverArtPrivate *d;
+    };
+
+    typedef List<CoverArt> CoverArtList;
+
+  }
+
+}
+
+#endif
index 2b5613ad61c9e9c251d6488dfcc040943cb0aed8..0af331f5d960b5253a64a3573de47245050e48ea 100644 (file)
@@ -47,6 +47,7 @@ public:
     IntPair m_intPair;
   };
   StringList m_stringList;
+  MP4::CoverArtList m_coverArtList;
 };
 
 MP4::Item::Item()
@@ -103,6 +104,12 @@ MP4::Item::Item(const StringList &value)
   d->m_stringList = value;
 }
 
+MP4::Item::Item(const MP4::CoverArtList &value)
+{
+  d = new ItemPrivate;
+  d->m_coverArtList = value;
+}
+
 bool
 MP4::Item::toBool() const
 {
@@ -127,6 +134,12 @@ MP4::Item::toStringList() const
   return d->m_stringList;
 }
 
+MP4::CoverArtList
+MP4::Item::toCoverArtList() const
+{
+  return d->m_coverArtList;
+}
+
 bool
 MP4::Item::isValid() const
 {
index 1405dc594806297cb587ad13faf8f84f57e25614..50a025f84b76a0e97af57ef5d1394ae81050684c 100644 (file)
@@ -27,6 +27,7 @@
 #define TAGLIB_MP4ITEM_H
 
 #include "tstringlist.h"
+#include "mp4coverart.h"
 #include "taglib_export.h"
 
 namespace TagLib {
@@ -49,11 +50,13 @@ namespace TagLib {
       Item(bool value);
       Item(int first, int second);
       Item(const StringList &value);
+      Item(const CoverArtList &value);
 
       int toInt() const;
       bool toBool() const;
       IntPair toIntPair() const;
       StringList toStringList() const;
+      CoverArtList toCoverArtList() const;
 
       bool isValid() const;
 
index a774e3910a8edcd0511ce8d69129382718258424..df74811d019d8ae407f11cdd188a19cc08ce2884 100644 (file)
@@ -77,6 +77,9 @@ MP4::Tag::Tag(File *file, MP4::Atoms *atoms)
     else if(atom->name == "gnre") {
       parseGnre(atom, file);
     }
+    else if(atom->name == "covr") {
+      parseCovr(atom, file);
+    }
     else {
       parseText(atom, file);
     }
@@ -194,6 +197,30 @@ MP4::Tag::parseFreeForm(MP4::Atom *atom, TagLib::File *file)
   }
 }
 
+void
+MP4::Tag::parseCovr(MP4::Atom *atom, TagLib::File *file)
+{
+  MP4::CoverArtList value;
+  ByteVector data = file->readBlock(atom->length - 8);
+  unsigned int pos = 0;
+  while(pos < data.size()) {
+    int length = data.mid(pos, 4).toUInt();
+    ByteVector name = data.mid(pos + 4, 4);
+    int flags = data.mid(pos + 8, 4).toUInt();
+    if(name != "data") {
+      debug("MP4: Unexpected atom \"" + name + "\", expecting \"data\"");
+      return;
+    }
+    if(flags == MP4::CoverArt::PNG || flags == MP4::CoverArt::JPEG) {
+      value.append(MP4::CoverArt(MP4::CoverArt::Format(flags),
+                                 data.mid(pos + 16, length - 16)));
+    }
+    pos += length;
+  }
+  if(value.size() > 0)
+    d->items.insert(atom->name, value);
+}
+
 ByteVector
 MP4::Tag::padIlst(const ByteVector &data, int length)
 {
@@ -267,6 +294,18 @@ MP4::Tag::renderText(const ByteVector &name, MP4::Item &item, int flags)
   return renderData(name, flags, data);
 }
 
+ByteVector
+MP4::Tag::renderCovr(const ByteVector &name, MP4::Item &item)
+{
+  ByteVector data;
+  MP4::CoverArtList value = item.toCoverArtList();
+  for(unsigned int i = 0; i < value.size(); i++) {
+    data.append(renderAtom("data", ByteVector::fromUInt(value[i].format()) +
+                                   ByteVector(4, '\0') + value[i].data()));
+  }
+  return renderAtom(name, data);
+}
+
 ByteVector
 MP4::Tag::renderFreeForm(const String &name, MP4::Item &item)
 {
@@ -306,6 +345,9 @@ MP4::Tag::save()
     else if(name == "tmpo") {
       data.append(renderInt(name.data(String::Latin1), i->second));
     }
+    else if(name == "covr") {
+      data.append(renderCovr(name.data(String::Latin1), i->second));
+    }
     else if(name.size() == 4){
       data.append(renderText(name.data(String::Latin1), i->second));
     }
index a2e0968a8c82717e1661a59b4857acce4f752036..6d7f140c9b1ef3de9899b5c8982d252198c5ab1a 100644 (file)
@@ -74,6 +74,7 @@ namespace TagLib {
         void parseGnre(Atom *atom, TagLib::File *file);
         void parseIntPair(Atom *atom, TagLib::File *file);
         void parseBool(Atom *atom, TagLib::File *file);
+        void parseCovr(Atom *atom, TagLib::File *file);
 
         TagLib::ByteVector padIlst(const ByteVector &data, int length = -1);
         TagLib::ByteVector renderAtom(const ByteVector &name, const TagLib::ByteVector &data);
@@ -84,6 +85,7 @@ namespace TagLib {
         TagLib::ByteVector renderInt(const ByteVector &name, Item &item);
         TagLib::ByteVector renderIntPair(const ByteVector &name, Item &item);
         TagLib::ByteVector renderIntPairNoTrailing(const ByteVector &name, Item &item);
+        TagLib::ByteVector renderCovr(const ByteVector &name, Item &item);
 
         void updateParents(AtomList &path, long delta, int ignore = 0);
         void updateOffsets(long delta, long offset);
index 403e6060eed4338372b588184727c91a0f1efcbb..0e42ffce10a0a8b61e3156616fdb9e39456b24c8 100644 (file)
@@ -38,7 +38,11 @@ SET(test_runner_SRCS
   test_oggflac.cpp
 )
 IF(WITH_MP4)
-   SET(test_runner_SRCS ${test_runner_SRCS} test_mp4.cpp)
+   SET(test_runner_SRCS ${test_runner_SRCS}
+     test_mp4.cpp
+     test_mp4item.cpp
+     test_mp4coverart.cpp
+   )
 ENDIF(WITH_MP4)
 
 IF(WITH_ASF)
index 7f9c9fe7189b2c9a2112aca554175529a6853b46..8bb6282508360a8b026c596d1a5617d039bc7d71 100644 (file)
@@ -19,6 +19,8 @@ class TestMP4 : public CppUnit::TestFixture
   CPPUNIT_TEST(testUpdateStco);
   CPPUNIT_TEST(testSaveExisingWhenIlstIsLast);
   CPPUNIT_TEST(test64BitAtom);
+  CPPUNIT_TEST(testCovrRead);
+  CPPUNIT_TEST(testCovrWrite);
   CPPUNIT_TEST_SUITE_END();
 
 public:
@@ -145,6 +147,45 @@ public:
     deleteFile(filename);
   }
 
+  void testCovrRead()
+  {
+    MP4::File *f = new MP4::File("data/has-tags.m4a");
+    CPPUNIT_ASSERT(f->tag()->itemListMap().contains("covr"));
+    MP4::CoverArtList l = f->tag()->itemListMap()["covr"].toCoverArtList();
+    CPPUNIT_ASSERT_EQUAL(TagLib::uint(2), l.size());
+    CPPUNIT_ASSERT_EQUAL(MP4::CoverArt::PNG, l[0].format());
+    CPPUNIT_ASSERT_EQUAL(TagLib::uint(79), l[0].data().size());
+    CPPUNIT_ASSERT_EQUAL(MP4::CoverArt::JPEG, l[1].format());
+    CPPUNIT_ASSERT_EQUAL(TagLib::uint(287), l[1].data().size());
+  }
+
+  void testCovrWrite()
+  {
+    string filename = copyFile("has-tags", ".m4a");
+
+    MP4::File *f = new MP4::File(filename.c_str());
+    CPPUNIT_ASSERT(f->tag()->itemListMap().contains("covr"));
+    MP4::CoverArtList l = f->tag()->itemListMap()["covr"].toCoverArtList();
+    l.append(MP4::CoverArt(MP4::CoverArt::PNG, "foo"));
+    f->tag()->itemListMap()["covr"] = l;
+    f->save();
+    delete f;
+
+    f = new MP4::File(filename.c_str());
+    CPPUNIT_ASSERT(f->tag()->itemListMap().contains("covr"));
+    l = f->tag()->itemListMap()["covr"].toCoverArtList();
+    CPPUNIT_ASSERT_EQUAL(TagLib::uint(3), l.size());
+    CPPUNIT_ASSERT_EQUAL(MP4::CoverArt::PNG, l[0].format());
+    CPPUNIT_ASSERT_EQUAL(TagLib::uint(79), l[0].data().size());
+    CPPUNIT_ASSERT_EQUAL(MP4::CoverArt::JPEG, l[1].format());
+    CPPUNIT_ASSERT_EQUAL(TagLib::uint(287), l[1].data().size());
+    CPPUNIT_ASSERT_EQUAL(MP4::CoverArt::PNG, l[2].format());
+    CPPUNIT_ASSERT_EQUAL(TagLib::uint(3), l[2].data().size());
+    delete f;
+
+    deleteFile(filename);
+  }
+
 };
 
 CPPUNIT_TEST_SUITE_REGISTRATION(TestMP4);
diff --git a/tests/test_mp4coverart.cpp b/tests/test_mp4coverart.cpp
new file mode 100644 (file)
index 0000000..75c6ad0
--- /dev/null
@@ -0,0 +1,49 @@
+#include <cppunit/extensions/HelperMacros.h>
+#include <string>
+#include <stdio.h>
+#include <tag.h>
+#include <mp4coverart.h>
+#include "utils.h"
+
+using namespace std;
+using namespace TagLib;
+
+class TestMP4CoverArt : public CppUnit::TestFixture
+{
+  CPPUNIT_TEST_SUITE(TestMP4CoverArt);
+  CPPUNIT_TEST(testSimple);
+  CPPUNIT_TEST(testList);
+  CPPUNIT_TEST_SUITE_END();
+
+public:
+
+  void testSimple()
+  {
+    MP4::CoverArt c(MP4::CoverArt::PNG, "foo");
+    CPPUNIT_ASSERT_EQUAL(MP4::CoverArt::PNG, c.format());
+    CPPUNIT_ASSERT_EQUAL(ByteVector("foo"), c.data());
+
+    MP4::CoverArt c2(c);
+    CPPUNIT_ASSERT_EQUAL(MP4::CoverArt::PNG, c2.format());
+    CPPUNIT_ASSERT_EQUAL(ByteVector("foo"), c2.data());
+
+    MP4::CoverArt c3 = c;
+    CPPUNIT_ASSERT_EQUAL(MP4::CoverArt::PNG, c3.format());
+    CPPUNIT_ASSERT_EQUAL(ByteVector("foo"), c3.data());
+  }
+
+  void testList()
+  {
+    MP4::CoverArtList l;
+    l.append(MP4::CoverArt(MP4::CoverArt::PNG, "foo"));
+    l.append(MP4::CoverArt(MP4::CoverArt::JPEG, "bar"));
+
+    CPPUNIT_ASSERT_EQUAL(MP4::CoverArt::PNG, l[0].format());
+    CPPUNIT_ASSERT_EQUAL(ByteVector("foo"), l[0].data());
+    CPPUNIT_ASSERT_EQUAL(MP4::CoverArt::JPEG, l[1].format());
+    CPPUNIT_ASSERT_EQUAL(ByteVector("bar"), l[1].data());
+  }
+
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(TestMP4CoverArt);
diff --git a/tests/test_mp4item.cpp b/tests/test_mp4item.cpp
new file mode 100644 (file)
index 0000000..68ba0c8
--- /dev/null
@@ -0,0 +1,37 @@
+#include <cppunit/extensions/HelperMacros.h>
+#include <string>
+#include <stdio.h>
+#include <tag.h>
+#include <mp4coverart.h>
+#include <mp4item.h>
+#include "utils.h"
+
+using namespace std;
+using namespace TagLib;
+
+class TestMP4Item : public CppUnit::TestFixture
+{
+  CPPUNIT_TEST_SUITE(TestMP4Item);
+  CPPUNIT_TEST(testCoverArtList);
+  CPPUNIT_TEST_SUITE_END();
+
+public:
+
+  void testCoverArtList()
+  {
+    MP4::CoverArtList l;
+    l.append(MP4::CoverArt(MP4::CoverArt::PNG, "foo"));
+    l.append(MP4::CoverArt(MP4::CoverArt::JPEG, "bar"));
+
+    MP4::Item i(l);
+    MP4::CoverArtList l2 = i.toCoverArtList();
+
+    CPPUNIT_ASSERT_EQUAL(MP4::CoverArt::PNG, l[0].format());
+    CPPUNIT_ASSERT_EQUAL(ByteVector("foo"), l[0].data());
+    CPPUNIT_ASSERT_EQUAL(MP4::CoverArt::JPEG, l[1].format());
+    CPPUNIT_ASSERT_EQUAL(ByteVector("bar"), l[1].data());
+  }
+
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(TestMP4Item);