From 0c14bd0ed075d140ef55fc62efc81b8c3ad841c8 Mon Sep 17 00:00:00 2001 From: Sander Jansen Date: Sat, 16 May 2015 14:10:24 -0500 Subject: [PATCH] Add FLAC::Picture support to Xiph Comment --- taglib/ogg/xiphcomment.cpp | 87 +++++++++++++++++++++++++++++++++----- taglib/ogg/xiphcomment.h | 26 ++++++++++++ tests/test_ogg.cpp | 28 ++++++++++++ 3 files changed, 130 insertions(+), 11 deletions(-) diff --git a/taglib/ogg/xiphcomment.cpp b/taglib/ogg/xiphcomment.cpp index 9462607f..c307e618 100644 --- a/taglib/ogg/xiphcomment.cpp +++ b/taglib/ogg/xiphcomment.cpp @@ -26,17 +26,22 @@ #include #include +#include #include #include using namespace TagLib; + +typedef List PictureList; + class Ogg::XiphComment::XiphCommentPrivate { public: FieldListMap fieldListMap; String vendorID; String commentField; + PictureList pictureList; }; //////////////////////////////////////////////////////////////////////////////// @@ -181,6 +186,8 @@ TagLib::uint Ogg::XiphComment::fieldCount() const for(; it != d->fieldListMap.end(); ++it) count += (*it).second.size(); + count += d->pictureList.size(); + return count; } @@ -275,6 +282,36 @@ bool Ogg::XiphComment::contains(const String &key) const return d->fieldListMap.contains(key) && !d->fieldListMap[key].isEmpty(); } +void Ogg::XiphComment::removePicture(FLAC::Picture *picture, bool del) +{ + List::Iterator it = d->pictureList.find(picture); + if(it != d->pictureList.end()) + d->pictureList.erase(it); + + if(del) + delete picture; +} + +void Ogg::XiphComment::removePictures() +{ + PictureList newList; + for(uint i = 0; i < d->pictureList.size(); i++) { + delete d->pictureList[i]; + } + d->pictureList = newList; +} + +void Ogg::XiphComment::addPicture(FLAC::Picture * picture) +{ + d->pictureList.append(picture); +} + + +List Ogg::XiphComment::pictureList() +{ + return d->pictureList; +} + ByteVector Ogg::XiphComment::render() const { return render(true); @@ -321,6 +358,13 @@ ByteVector Ogg::XiphComment::render(bool addFramingBit) const } } + for(PictureList::ConstIterator it = d->pictureList.begin(); it != d->pictureList.end(); ++it) { + ByteVector picture = (*it)->render().toBase64(); + data.append(ByteVector::fromUInt(picture.size()+23,false)); + data.append("METADATA_BLOCK_PICTURE="); + data.append(picture); + } + // Append the "framing bit". if(addFramingBit) @@ -363,20 +407,41 @@ void Ogg::XiphComment::parse(const ByteVector &data) const uint commentLength = data.toUInt(pos, false); pos += 4; - String comment = String(data.mid(pos, commentLength), String::UTF8); - pos += commentLength; - if(pos > data.size()) { + ByteVector entry = data.mid(pos, commentLength); + + // Don't go past data end + pos+=commentLength; + if (pos>data.size()) break; + + // Handle Pictures separately + if(entry.startsWith("METADATA_BLOCK_PICTURE=")) { + + // Decode base64 picture data + ByteVector picturedata = entry.mid(23, entry.size()-23).fromBase64(); + + if(picturedata.size()==0) { + debug("Empty picture data. Discarding content"); + continue; + } + + FLAC::Picture * picture = new FLAC::Picture(); + if(picture->parse(picturedata)) + d->pictureList.append(picture); + else + debug("Unable to parse METADATA_BLOCK_PICTURE. Discarding content."); } + else { - int commentSeparatorPosition = comment.find("="); - if(commentSeparatorPosition == -1) { - break; + // Check for field separator + int sep = entry.find('='); + if (sep == -1) + break; + + // Parse key and value + String key = String(entry.mid(0,sep), String::UTF8); + String value = String(entry.mid(sep+1, commentLength-sep), String::UTF8); + addField(key, value, false); } - - String key = comment.substr(0, commentSeparatorPosition); - String value = comment.substr(commentSeparatorPosition + 1); - - addField(key, value, false); } } diff --git a/taglib/ogg/xiphcomment.h b/taglib/ogg/xiphcomment.h index 54f3070b..8fcc6873 100644 --- a/taglib/ogg/xiphcomment.h +++ b/taglib/ogg/xiphcomment.h @@ -32,6 +32,7 @@ #include "tstring.h" #include "tstringlist.h" #include "tbytevector.h" +#include "flacpicture.h" #include "taglib_export.h" namespace TagLib { @@ -205,6 +206,31 @@ namespace TagLib { */ ByteVector render(bool addFramingBit) const; + + /*! + * Returns a list of pictures attached to the xiph comment. + */ + List pictureList(); + + /*! + * Removes an picture. If \a del is true the picture's memory + * will be freed; if it is false, it must be deleted by the user. + */ + void removePicture(FLAC::Picture *picture, bool del = true); + + /*! + * Remove all pictures. + */ + void removePictures(); + + /*! + * Add a new picture to the comment block. The comment block takes ownership of the + * picture and will handle freeing its memory. + * + * \note The file will be saved only after calling save(). + */ + void addPicture(FLAC::Picture *picture); + protected: /*! * Reads the tag from the file specified in the constructor and fills the diff --git a/tests/test_ogg.cpp b/tests/test_ogg.cpp index 210fca2f..05b23f64 100644 --- a/tests/test_ogg.cpp +++ b/tests/test_ogg.cpp @@ -21,6 +21,7 @@ class TestOGG : public CppUnit::TestFixture CPPUNIT_TEST(testDictInterface1); CPPUNIT_TEST(testDictInterface2); CPPUNIT_TEST(testAudioProperties); + CPPUNIT_TEST(testPicture); CPPUNIT_TEST_SUITE_END(); public: @@ -134,6 +135,33 @@ public: CPPUNIT_ASSERT_EQUAL(112000, f.audioProperties()->bitrateNominal()); CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->bitrateMinimum()); } + + void testPicture() + { + ScopedFileCopy copy("empty", ".ogg"); + string newname = copy.fileName(); + + Vorbis::File *f = new Vorbis::File(newname.c_str()); + FLAC::Picture *newpic = new FLAC::Picture(); + newpic->setType(FLAC::Picture::BackCover); + newpic->setWidth(5); + newpic->setHeight(6); + newpic->setColorDepth(16); + newpic->setNumColors(7); + newpic->setMimeType("image/jpeg"); + newpic->setDescription("new image"); + newpic->setData("JPEG data"); + f->tag()->addPicture(newpic); + f->save(); + delete f; + + f = new Vorbis::File(newname.c_str()); + List lst = f->tag()->pictureList(); + CPPUNIT_ASSERT_EQUAL(TagLib::uint(1), lst.size()); + delete f; + } + + }; CPPUNIT_TEST_SUITE_REGISTRATION(TestOGG);