From 719187794e830d8d24acab4622212dc27f5c0afb Mon Sep 17 00:00:00 2001 From: Rupert Daniel Date: Wed, 5 Sep 2012 16:37:46 +0100 Subject: [PATCH 1/2] Implementation of the ID3v2.4 OWNE frame. --- taglib/mpeg/id3v2/frames/ownershipframe.cpp | 159 ++++++++++++++++++++ taglib/mpeg/id3v2/frames/ownershipframe.h | 152 +++++++++++++++++++ taglib/mpeg/id3v2/id3v2framefactory.cpp | 9 ++ taglib/mpeg/id3v2/id3v2tag.cpp | 4 + taglib/mpeg/id3v2/id3v2tag.h | 1 + tests/test_id3v2.cpp | 85 +++++------ 6 files changed, 360 insertions(+), 50 deletions(-) create mode 100644 taglib/mpeg/id3v2/frames/ownershipframe.cpp create mode 100644 taglib/mpeg/id3v2/frames/ownershipframe.h diff --git a/taglib/mpeg/id3v2/frames/ownershipframe.cpp b/taglib/mpeg/id3v2/frames/ownershipframe.cpp new file mode 100644 index 00000000..da8410b5 --- /dev/null +++ b/taglib/mpeg/id3v2/frames/ownershipframe.cpp @@ -0,0 +1,159 @@ +/*************************************************************************** + copyright : (C) 2012 by Rupert Daniel + email : rupert@cancelmonday.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., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 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/ * + ***************************************************************************/ + +#include + +#include "ownershipframe.h" + +using namespace TagLib; +using namespace ID3v2; + +class OwnershipFrame::OwnershipFramePrivate +{ +public: + String currencyCode; + String pricePaid; + String datePurchased; + String seller; + String::Type textEncoding; +}; + +//////////////////////////////////////////////////////////////////////////////// +// public members +//////////////////////////////////////////////////////////////////////////////// + +OwnershipFrame::OwnershipFrame(String::Type encoding) : Frame("OWNE") +{ + d = new OwnershipFramePrivate; + d->textEncoding = encoding; +} + +OwnershipFrame::OwnershipFrame(const ByteVector &data) : Frame(data) +{ + d = new OwnershipFramePrivate; + setData(data); +} + +OwnershipFrame::~OwnershipFrame() +{ + delete d; +} + +String OwnershipFrame::toString() const +{ + return "pricePaid=" + d->pricePaid + " datePurchased=" + d->datePurchased + " seller=" + d->seller; +} + +String OwnershipFrame::pricePaid() const +{ + return d->pricePaid; +} + +void OwnershipFrame::setPricePaid(const String &s) +{ + d->pricePaid = s; +} + +String OwnershipFrame::datePurchased() const +{ + return d->datePurchased; +} + +void OwnershipFrame::setDatePurchased(const String &s) +{ + d->datePurchased = s; +} + +String OwnershipFrame::seller() const +{ + return d->seller; +} + +void OwnershipFrame::setSeller(const String &s) +{ + d->seller = s; +} + +String::Type OwnershipFrame::textEncoding() const +{ + return d->textEncoding; +} + +void OwnershipFrame::setTextEncoding(String::Type encoding) +{ + d->textEncoding = encoding; +} + +//////////////////////////////////////////////////////////////////////////////// +// protected members +//////////////////////////////////////////////////////////////////////////////// + +void OwnershipFrame::parseFields(const ByteVector &data) +{ + int pos = 0; + + // Get the text encoding + d->textEncoding = String::Type(data[0]); + pos += 1; + + // Read the price paid this is a null terminate string + d->pricePaid = readStringField(data, String::Latin1, &pos); + + // If we don't have at least 8 bytes left then don't parse the rest of the + // data + if (data.size() - pos < 8) { + return; + } + + // Read the date purchased YYYYMMDD + d->datePurchased = String(data.mid(pos, 8)); + pos += 8; + + // Read the seller + d->seller = String(data.mid(pos)); +} + +ByteVector OwnershipFrame::renderFields() const +{ + ByteVector v; + + v.append(char(d->textEncoding)); + v.append(d->pricePaid.data(String::Latin1)); + v.append(textDelimiter(String::Latin1)); + v.append(d->datePurchased.data(String::Latin1)); + v.append(d->seller.data(d->textEncoding)); + + return v; +} + +//////////////////////////////////////////////////////////////////////////////// +// private members +//////////////////////////////////////////////////////////////////////////////// + +OwnershipFrame::OwnershipFrame(const ByteVector &data, Header *h) : Frame(h) +{ + d = new OwnershipFramePrivate; + parseFields(fieldData(data)); +} diff --git a/taglib/mpeg/id3v2/frames/ownershipframe.h b/taglib/mpeg/id3v2/frames/ownershipframe.h new file mode 100644 index 00000000..b73832ba --- /dev/null +++ b/taglib/mpeg/id3v2/frames/ownershipframe.h @@ -0,0 +1,152 @@ +/*************************************************************************** + copyright : (C) 2012 by Rupert Daniel + email : rupert@cancelmonday.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., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 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_OWNERSHIPFRAME_H +#define TAGLIB_OWNERSHIPFRAME_H + +#include "id3v2frame.h" +#include "taglib_export.h" + +namespace TagLib { + + namespace ID3v2 { + + //! An implementation of ID3v2 "ownership" + + /*! + * This implements the ID3v2 ownership (OWNE frame). It concists of + * an price paid, a date purchased (YYYYMMDD) and the name of the seller. + */ + + class TAGLIB_EXPORT OwnershipFrame : public Frame + { + friend class FrameFactory; + + public: + /*! + * Construct an empty ownership frame. + */ + explicit OwnershipFrame(String::Type encoding = String::Latin1); + + /*! + * Construct a ownership based on the data in \a data. + */ + explicit OwnershipFrame(const ByteVector &data); + + /*! + * Destroys this OwnershipFrame instance. + */ + virtual ~OwnershipFrame(); + + /*! + * Returns the text of this popularimeter. + * + * \see text() + */ + virtual String toString() const; + + /*! + * Returns the date purchased. + * + * \see setDatePurchased() + */ + String datePurchased() const; + + /*! + * Set the date purchased. + * + * \see datePurchased() + */ + void setDatePurchased(const String &datePurchased); + + + /*! + * Returns the price paid. + * + * \see setPricePaid() + */ + String pricePaid() const; + + /*! + * Set the price paid. + * + * \see pricePaid() + */ + void setPricePaid(const String &pricePaid); + + /*! + * Returns the seller. + * + * \see setSeller() + */ + String seller() const; + + /*! + * Set the seller. + * + * \see seller() + */ + void setSeller(const String &seller); + + /*! + * Returns the text encoding that will be used in rendering this frame. + * This defaults to the type that was either specified in the constructor + * or read from the frame when parsed. + * + * \see setTextEncoding() + * \see render() + */ + String::Type textEncoding() const; + + /*! + * Sets the text encoding to be used when rendering this frame to + * \a encoding. + * + * \see textEncoding() + * \see render() + */ + void setTextEncoding(String::Type encoding); + + protected: + // Reimplementations. + + virtual void parseFields(const ByteVector &data); + virtual ByteVector renderFields() const; + + private: + /*! + * The constructor used by the FrameFactory. + */ + OwnershipFrame(const ByteVector &data, Header *h); + OwnershipFrame(const OwnershipFrame &); + OwnershipFrame &operator=(const OwnershipFrame &); + + class OwnershipFramePrivate; + OwnershipFramePrivate *d; + }; + + } +} +#endif diff --git a/taglib/mpeg/id3v2/id3v2framefactory.cpp b/taglib/mpeg/id3v2/id3v2framefactory.cpp index bcf69e1a..7e2e98a8 100644 --- a/taglib/mpeg/id3v2/id3v2framefactory.cpp +++ b/taglib/mpeg/id3v2/id3v2framefactory.cpp @@ -44,6 +44,7 @@ #include "frames/unsynchronizedlyricsframe.h" #include "frames/popularimeterframe.h" #include "frames/privateframe.h" +#include "frames/ownershipframe.h" using namespace TagLib; using namespace ID3v2; @@ -238,6 +239,14 @@ Frame *FrameFactory::createFrame(const ByteVector &origData, Header *tagHeader) if(frameID == "PRIV") return new PrivateFrame(data, header); + + // Ownership (frames 4.22) + + if(frameID == "OWNE") { + OwnershipFrame *f = new OwnershipFrame(data, header); + d->setTextEncoding(f); + return f; + } return new UnknownFrame(data, header); } diff --git a/taglib/mpeg/id3v2/id3v2tag.cpp b/taglib/mpeg/id3v2/id3v2tag.cpp index ffb0189c..54e63920 100644 --- a/taglib/mpeg/id3v2/id3v2tag.cpp +++ b/taglib/mpeg/id3v2/id3v2tag.cpp @@ -81,6 +81,10 @@ const ID3v2::Latin1StringHandler *ID3v2::Tag::TagPrivate::stringHandler = &defau // StringHandler implementation //////////////////////////////////////////////////////////////////////////////// +Latin1StringHandler::Latin1StringHandler() +{ +} + Latin1StringHandler::~Latin1StringHandler() { } diff --git a/taglib/mpeg/id3v2/id3v2tag.h b/taglib/mpeg/id3v2/id3v2tag.h index e6d6d4fd..5fd5c1f1 100644 --- a/taglib/mpeg/id3v2/id3v2tag.h +++ b/taglib/mpeg/id3v2/id3v2tag.h @@ -77,6 +77,7 @@ namespace TagLib { class TAGLIB_EXPORT Latin1StringHandler { public: + Latin1StringHandler(); virtual ~Latin1StringHandler(); /*! diff --git a/tests/test_id3v2.cpp b/tests/test_id3v2.cpp index 29758aa4..243f9753 100644 --- a/tests/test_id3v2.cpp +++ b/tests/test_id3v2.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include "utils.h" @@ -61,6 +62,8 @@ class TestID3v2 : public CppUnit::TestFixture CPPUNIT_TEST(testRenderUrlLinkFrame); CPPUNIT_TEST(testParseUserUrlLinkFrame); CPPUNIT_TEST(testRenderUserUrlLinkFrame); + CPPUNIT_TEST(testParseOwnershipFrame); + CPPUNIT_TEST(testRenderOwnershipFrame); CPPUNIT_TEST(testSaveUTF16Comment); CPPUNIT_TEST(testUpdateGenre23_1); CPPUNIT_TEST(testUpdateGenre23_2); @@ -69,11 +72,8 @@ class TestID3v2 : public CppUnit::TestFixture CPPUNIT_TEST(testDowngradeTo23); // CPPUNIT_TEST(testUpdateFullDate22); TODO TYE+TDA should be upgraded to TDRC together CPPUNIT_TEST(testCompressedFrameWithBrokenLength); - CPPUNIT_TEST(testW000); CPPUNIT_TEST(testPropertyInterface); CPPUNIT_TEST(testPropertyInterface2); - CPPUNIT_TEST(testDeleteFrame); - CPPUNIT_TEST(testSaveAndStripID3v1ShouldNotAddFrameFromID3v1ToId3v2); CPPUNIT_TEST_SUITE_END(); public: @@ -386,6 +386,38 @@ public: "http://example.com", 33), // URL f.render()); } + + void testParseOwnershipFrame() + { + ID3v2::OwnershipFrame f( + ByteVector("OWNE" // Frame ID + "\x00\x00\x00\x19" // Frame size + "\x00\x00" // Frame flags + "\x00" // Text encoding + "GBP1.99\x00" // Price paid + "20120905" // Date of purchase + "Beatport", 35)); // Seller + CPPUNIT_ASSERT_EQUAL(String("GBP1.99"), f.pricePaid()); + CPPUNIT_ASSERT_EQUAL(String("20120905"), f.datePurchased()); + CPPUNIT_ASSERT_EQUAL(String("Beatport"), f.seller()); + } + + void testRenderOwnershipFrame() + { + ID3v2::OwnershipFrame f; + f.setPricePaid("GBP1.99"); + f.setDatePurchased("20120905"); + f.setSeller("Beatport"); + CPPUNIT_ASSERT_EQUAL( + ByteVector("OWNE" // Frame ID + "\x00\x00\x00\x19" // Frame size + "\x00\x00" // Frame flags + "\x00" // Text encoding + "GBP1.99\x00" // Price paid + "20120905" // Date of purchase + "Beatport", 35), // URL + f.render()); + } void testItunes24FrameSize() { @@ -554,16 +586,6 @@ public: CPPUNIT_ASSERT_EQUAL(TagLib::uint(86414), frame->picture().size()); } - void testW000() - { - MPEG::File f(TEST_FILE_PATH_C("w000.mp3"), false); - CPPUNIT_ASSERT(f.ID3v2Tag()->frameListMap().contains("W000")); - ID3v2::UrlLinkFrame *frame = - dynamic_cast(f.ID3v2Tag()->frameListMap()["W000"].front()); - CPPUNIT_ASSERT(frame); - CPPUNIT_ASSERT_EQUAL(String("lukas.lalinsky@example.com____"), frame->url()); - } - void testPropertyInterface() { ScopedFileCopy copy("rare_frames", ".mp3"); @@ -641,43 +663,6 @@ public: CPPUNIT_ASSERT(tag.frameList("TIPL").isEmpty()); } - void testDeleteFrame() - { - ScopedFileCopy copy("rare_frames", ".mp3"); - string newname = copy.fileName(); - MPEG::File f(newname.c_str()); - ID3v2::Tag *t = f.ID3v2Tag(); - ID3v2::Frame *frame = t->frameList("TCON")[0]; - CPPUNIT_ASSERT_EQUAL(1u, t->frameList("TCON").size()); - t->removeFrame(frame, true); - f.save(MPEG::File::ID3v2); - - MPEG::File f2(newname.c_str()); - t = f2.ID3v2Tag(); - CPPUNIT_ASSERT(t->frameList("TCON").isEmpty()); - } - void testSaveAndStripID3v1ShouldNotAddFrameFromID3v1ToId3v2() - { - ScopedFileCopy copy("xing", ".mp3"); - string newname = copy.fileName(); - - { - MPEG::File foo(newname.c_str()); - foo.tag()->setArtist("Artist"); - foo.save(MPEG::File::ID3v1 | MPEG::File::ID3v2); - } - - { - MPEG::File bar(newname.c_str()); - bar.ID3v2Tag()->removeFrames("TPE1"); - // Should strip ID3v1 here and not add old values to ID3v2 again - bar.save(MPEG::File::ID3v2, true); - } - - MPEG::File f(newname.c_str()); - CPPUNIT_ASSERT(!f.ID3v2Tag()->frameListMap().contains("TPE1")); - } - }; CPPUNIT_TEST_SUITE_REGISTRATION(TestID3v2); From f194a55c0ffb8aa6d2240f1fe52f34bea165552f Mon Sep 17 00:00:00 2001 From: Rupert Daniel Date: Thu, 6 Sep 2012 12:11:20 +0100 Subject: [PATCH 2/2] Updated OWNE implementaion with minor changes after pull review --- taglib/mpeg/id3v2/frames/ownershipframe.cpp | 9 ++-- taglib/mpeg/id3v2/frames/ownershipframe.h | 5 +- tests/test_id3v2.cpp | 51 +++++++++++++++++++++ 3 files changed, 59 insertions(+), 6 deletions(-) diff --git a/taglib/mpeg/id3v2/frames/ownershipframe.cpp b/taglib/mpeg/id3v2/frames/ownershipframe.cpp index da8410b5..9451c4c4 100644 --- a/taglib/mpeg/id3v2/frames/ownershipframe.cpp +++ b/taglib/mpeg/id3v2/frames/ownershipframe.cpp @@ -26,6 +26,7 @@ #include #include "ownershipframe.h" +#include using namespace TagLib; using namespace ID3v2; @@ -33,7 +34,6 @@ using namespace ID3v2; class OwnershipFrame::OwnershipFramePrivate { public: - String currencyCode; String pricePaid; String datePurchased; String seller; @@ -123,7 +123,7 @@ void OwnershipFrame::parseFields(const ByteVector &data) // If we don't have at least 8 bytes left then don't parse the rest of the // data - if (data.size() - pos < 8) { + if(data.size() - pos < 8) { return; } @@ -132,7 +132,10 @@ void OwnershipFrame::parseFields(const ByteVector &data) pos += 8; // Read the seller - d->seller = String(data.mid(pos)); + if(d->textEncoding == String::Latin1) + d->seller = Tag::latin1StringHandler()->parse(data.mid(pos)); + else + d->seller = String(data.mid(pos), d->textEncoding); } ByteVector OwnershipFrame::renderFields() const diff --git a/taglib/mpeg/id3v2/frames/ownershipframe.h b/taglib/mpeg/id3v2/frames/ownershipframe.h index b73832ba..34fc9129 100644 --- a/taglib/mpeg/id3v2/frames/ownershipframe.h +++ b/taglib/mpeg/id3v2/frames/ownershipframe.h @@ -36,8 +36,8 @@ namespace TagLib { //! An implementation of ID3v2 "ownership" /*! - * This implements the ID3v2 ownership (OWNE frame). It concists of - * an price paid, a date purchased (YYYYMMDD) and the name of the seller. + * This implements the ID3v2 ownership (OWNE frame). It consists of + * a price paid, a date purchased (YYYYMMDD) and the name of the seller. */ class TAGLIB_EXPORT OwnershipFrame : public Frame @@ -81,7 +81,6 @@ namespace TagLib { */ void setDatePurchased(const String &datePurchased); - /*! * Returns the price paid. * diff --git a/tests/test_id3v2.cpp b/tests/test_id3v2.cpp index 243f9753..d3fbfee9 100644 --- a/tests/test_id3v2.cpp +++ b/tests/test_id3v2.cpp @@ -72,8 +72,11 @@ class TestID3v2 : public CppUnit::TestFixture CPPUNIT_TEST(testDowngradeTo23); // CPPUNIT_TEST(testUpdateFullDate22); TODO TYE+TDA should be upgraded to TDRC together CPPUNIT_TEST(testCompressedFrameWithBrokenLength); + CPPUNIT_TEST(testW000); CPPUNIT_TEST(testPropertyInterface); CPPUNIT_TEST(testPropertyInterface2); + CPPUNIT_TEST(testDeleteFrame); + CPPUNIT_TEST(testSaveAndStripID3v1ShouldNotAddFrameFromID3v1ToId3v2); CPPUNIT_TEST_SUITE_END(); public: @@ -585,6 +588,16 @@ public: CPPUNIT_ASSERT_EQUAL(String(""), frame->description()); CPPUNIT_ASSERT_EQUAL(TagLib::uint(86414), frame->picture().size()); } + + void testW000() + { + MPEG::File f(TEST_FILE_PATH_C("w000.mp3"), false); + CPPUNIT_ASSERT(f.ID3v2Tag()->frameListMap().contains("W000")); + ID3v2::UrlLinkFrame *frame = + dynamic_cast(f.ID3v2Tag()->frameListMap()["W000"].front()); + CPPUNIT_ASSERT(frame); + CPPUNIT_ASSERT_EQUAL(String("lukas.lalinsky@example.com____"), frame->url()); + } void testPropertyInterface() { @@ -663,6 +676,44 @@ public: CPPUNIT_ASSERT(tag.frameList("TIPL").isEmpty()); } + void testDeleteFrame() + { + ScopedFileCopy copy("rare_frames", ".mp3"); + string newname = copy.fileName(); + MPEG::File f(newname.c_str()); + ID3v2::Tag *t = f.ID3v2Tag(); + ID3v2::Frame *frame = t->frameList("TCON")[0]; + CPPUNIT_ASSERT_EQUAL(1u, t->frameList("TCON").size()); + t->removeFrame(frame, true); + f.save(MPEG::File::ID3v2); + + MPEG::File f2(newname.c_str()); + t = f2.ID3v2Tag(); + CPPUNIT_ASSERT(t->frameList("TCON").isEmpty()); + } + + void testSaveAndStripID3v1ShouldNotAddFrameFromID3v1ToId3v2() + { + ScopedFileCopy copy("xing", ".mp3"); + string newname = copy.fileName(); + + { + MPEG::File foo(newname.c_str()); + foo.tag()->setArtist("Artist"); + foo.save(MPEG::File::ID3v1 | MPEG::File::ID3v2); + } + + { + MPEG::File bar(newname.c_str()); + bar.ID3v2Tag()->removeFrames("TPE1"); + // Should strip ID3v1 here and not add old values to ID3v2 again + bar.save(MPEG::File::ID3v2, true); + } + + MPEG::File f(newname.c_str()); + CPPUNIT_ASSERT(!f.ID3v2Tag()->frameListMap().contains("TPE1")); + } + }; CPPUNIT_TEST_SUITE_REGISTRATION(TestID3v2);