From e8281e1b9f6b28eb47ff92c3eb7f7ddb0186cc60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Lalinsk=C3=BD?= Date: Sat, 24 Oct 2009 16:55:54 +0000 Subject: [PATCH] Support for MP4 cover art CCMAIL:martin.trashbox@gmail.com git-svn-id: svn://anonsvn.kde.org/home/kde/trunk/kdesupport/taglib@1039809 283d02a7-25f6-0310-bc7c-ecb5cbfe19da --- NEWS | 5 ++- include/mp4coverart.h | 1 + taglib/CMakeLists.txt | 2 + taglib/mp4/CMakeLists.txt | 2 +- taglib/mp4/Makefile.am | 4 +- taglib/mp4/mp4coverart.cpp | 89 ++++++++++++++++++++++++++++++++++++++ taglib/mp4/mp4coverart.h | 71 ++++++++++++++++++++++++++++++ taglib/mp4/mp4item.cpp | 13 ++++++ taglib/mp4/mp4item.h | 3 ++ taglib/mp4/mp4tag.cpp | 42 ++++++++++++++++++ taglib/mp4/mp4tag.h | 2 + tests/CMakeLists.txt | 6 ++- tests/test_mp4.cpp | 41 ++++++++++++++++++ tests/test_mp4coverart.cpp | 49 +++++++++++++++++++++ tests/test_mp4item.cpp | 37 ++++++++++++++++ 15 files changed, 361 insertions(+), 6 deletions(-) create mode 100644 include/mp4coverart.h create mode 100644 taglib/mp4/mp4coverart.cpp create mode 100644 taglib/mp4/mp4coverart.h create mode 100644 tests/test_mp4coverart.cpp create mode 100644 tests/test_mp4item.cpp diff --git a/NEWS b/NEWS index 0d4bd527..437af6e1 100644 --- 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 index 00000000..f5ca629e --- /dev/null +++ b/include/mp4coverart.h @@ -0,0 +1 @@ +#include "../taglib/mp4/mp4coverart.h" diff --git a/taglib/CMakeLists.txt b/taglib/CMakeLists.txt index 852776fd..3314d6ce 100644 --- a/taglib/CMakeLists.txt +++ b/taglib/CMakeLists.txt @@ -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} diff --git a/taglib/mp4/CMakeLists.txt b/taglib/mp4/CMakeLists.txt index 803a9d52..993a5c0b 100644 --- a/taglib/mp4/CMakeLists.txt +++ b/taglib/mp4/CMakeLists.txt @@ -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) diff --git a/taglib/mp4/Makefile.am b/taglib/mp4/Makefile.am index bc2faa4e..68f08124 100644 --- a/taglib/mp4/Makefile.am +++ b/taglib/mp4/Makefile.am @@ -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 index 00000000..983df02c --- /dev/null +++ b/taglib/mp4/mp4coverart.cpp @@ -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 +#endif + +#ifdef WITH_MP4 + +#include +#include +#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 index 00000000..00a7aff9 --- /dev/null +++ b/taglib/mp4/mp4coverart.h @@ -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 CoverArtList; + + } + +} + +#endif diff --git a/taglib/mp4/mp4item.cpp b/taglib/mp4/mp4item.cpp index 2b5613ad..0af331f5 100644 --- a/taglib/mp4/mp4item.cpp +++ b/taglib/mp4/mp4item.cpp @@ -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 { diff --git a/taglib/mp4/mp4item.h b/taglib/mp4/mp4item.h index 1405dc59..50a025f8 100644 --- a/taglib/mp4/mp4item.h +++ b/taglib/mp4/mp4item.h @@ -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; diff --git a/taglib/mp4/mp4tag.cpp b/taglib/mp4/mp4tag.cpp index a774e391..df74811d 100644 --- a/taglib/mp4/mp4tag.cpp +++ b/taglib/mp4/mp4tag.cpp @@ -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)); } diff --git a/taglib/mp4/mp4tag.h b/taglib/mp4/mp4tag.h index a2e0968a..6d7f140c 100644 --- a/taglib/mp4/mp4tag.h +++ b/taglib/mp4/mp4tag.h @@ -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); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 403e6060..0e42ffce 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -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) diff --git a/tests/test_mp4.cpp b/tests/test_mp4.cpp index 7f9c9fe7..8bb62825 100644 --- a/tests/test_mp4.cpp +++ b/tests/test_mp4.cpp @@ -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 index 00000000..75c6ad03 --- /dev/null +++ b/tests/test_mp4coverart.cpp @@ -0,0 +1,49 @@ +#include +#include +#include +#include +#include +#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 index 00000000..68ba0c8d --- /dev/null +++ b/tests/test_mp4item.cpp @@ -0,0 +1,37 @@ +#include +#include +#include +#include +#include +#include +#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);