diff --git a/taglib/mpeg/id3v2/id3v2frame.cpp b/taglib/mpeg/id3v2/id3v2frame.cpp index 6c37a6a6e..742368d75 100644 --- a/taglib/mpeg/id3v2/id3v2frame.cpp +++ b/taglib/mpeg/id3v2/id3v2frame.cpp @@ -181,6 +181,11 @@ ByteVector Frame::render() const return headerData + fieldData; } +Frame::Header *Frame::header() const +{ + return d->header; +} + //////////////////////////////////////////////////////////////////////////////// // protected members //////////////////////////////////////////////////////////////////////////////// @@ -197,11 +202,6 @@ Frame::Frame(Header *h) : d->header = h; } -Frame::Header *Frame::header() const -{ - return d->header; -} - void Frame::setHeader(Header *h, bool deleteCurrent) { if(deleteCurrent) diff --git a/taglib/mpeg/id3v2/id3v2frame.h b/taglib/mpeg/id3v2/id3v2frame.h index 0f090453c..c7cc268e7 100644 --- a/taglib/mpeg/id3v2/id3v2frame.h +++ b/taglib/mpeg/id3v2/id3v2frame.h @@ -54,11 +54,9 @@ namespace TagLib { class TAGLIB_EXPORT Frame { friend class Tag; - friend class FrameFactory; - friend class TableOfContentsFrame; - friend class ChapterFrame; public: + class Header; /*! * Creates a textual frame which corresponds to a single key in the PropertyMap @@ -122,6 +120,11 @@ namespace TagLib { */ ByteVector render() const; + /*! + * Returns a pointer to the frame header. + */ + Header *header() const; + /*! * Returns the text delimiter that is used between fields for the string * type \a t. @@ -151,8 +154,6 @@ namespace TagLib { static const String urlPrefix; protected: - class Header; - /*! * Constructs an ID3v2 frame using \a data to read the header information. * All other processing of \a data should be handled in a subclass. @@ -170,11 +171,6 @@ namespace TagLib { */ Frame(Header *h); - /*! - * Returns a pointer to the frame header. - */ - Header *header() const; - /*! * Sets the header to \a h. If \a deleteCurrent is true, this will free * the memory of the current header. diff --git a/taglib/mpeg/id3v2/id3v2framefactory.cpp b/taglib/mpeg/id3v2/id3v2framefactory.cpp index 5f6928045..6c027ca63 100644 --- a/taglib/mpeg/id3v2/id3v2framefactory.cpp +++ b/taglib/mpeg/id3v2/id3v2framefactory.cpp @@ -370,6 +370,11 @@ void FrameFactory::setDefaultTextEncoding(String::Type encoding) d->defaultEncoding = encoding; } +bool FrameFactory::isUsingDefaultTextEncoding() const +{ + return d->useDefaultEncoding; +} + //////////////////////////////////////////////////////////////////////////////// // protected members //////////////////////////////////////////////////////////////////////////////// diff --git a/taglib/mpeg/id3v2/id3v2framefactory.h b/taglib/mpeg/id3v2/id3v2framefactory.h index 17bdc2a16..826e0194e 100644 --- a/taglib/mpeg/id3v2/id3v2framefactory.h +++ b/taglib/mpeg/id3v2/id3v2framefactory.h @@ -50,10 +50,11 @@ namespace TagLib { * factory to be the default factory in ID3v2::Tag constructor you can * implement behavior that will allow for new ID3v2::Frame subclasses (also * provided by you) to be used. + * See \c testFrameFactory() in \e tests/test_mpeg.cpp for an example. * * This implements both abstract factory and singleton patterns * of which more information is available on the web and in software design - * textbooks (Notably Design Patters). + * textbooks (Notably Design Patterns). * * \note You do not need to use this factory to create new frames to add to * an ID3v2::Tag. You can instantiate frame subclasses directly (with new) @@ -105,6 +106,18 @@ namespace TagLib { */ void setDefaultTextEncoding(String::Type encoding); + /*! + * Returns true if defaultTextEncoding() is used. + * The default text encoding is used when setDefaultTextEncoding() has + * been called. In this case, reimplementations of FrameFactory should + * use defaultTextEncoding() on the frames (having a text encoding field) + * they create. + * + * \see defaultTextEncoding() + * \see setDefaultTextEncoding() + */ + bool isUsingDefaultTextEncoding() const; + protected: /*! * Constructs a frame factory. Because this is a singleton this method is diff --git a/tests/test_mpeg.cpp b/tests/test_mpeg.cpp index 54d833f8a..57d40478c 100644 --- a/tests/test_mpeg.cpp +++ b/tests/test_mpeg.cpp @@ -27,11 +27,14 @@ #include #include +#include "tbytevector.h" #include "tstring.h" #include "tpropertymap.h" #include "mpegfile.h" #include "id3v2tag.h" #include "id3v1tag.h" +#include "id3v2frame.h" +#include "id3v2framefactory.h" #include "apetag.h" #include "mpegproperties.h" #include "xingheader.h" @@ -71,6 +74,7 @@ class TestMPEG : public CppUnit::TestFixture CPPUNIT_TEST(testIgnoreGarbage); CPPUNIT_TEST(testExtendedHeader); CPPUNIT_TEST(testReadStyleFast); + CPPUNIT_TEST(testFrameFactory); CPPUNIT_TEST_SUITE_END(); public: @@ -620,6 +624,95 @@ class TestMPEG : public CppUnit::TestFixture } } + void testFrameFactory() + { + class CustomFrameFactory; + + // Just a silly example of a custom frame holding a number. + class CustomFrame : public ID3v2::Frame + { + friend class CustomFrameFactory; + public: + explicit CustomFrame(unsigned int value = 0) + : Frame("CUST"), m_value(value) {} + CustomFrame(const CustomFrame &) = delete; + CustomFrame &operator=(const CustomFrame &) = delete; + ~CustomFrame() override = default; + String toString() const override { return String::number(m_value); } + PropertyMap asProperties() const override { + return SimplePropertyMap{{"CUSTOM", StringList(String::number(m_value))}}; + } + unsigned int value() const { return m_value; } + + protected: + void parseFields(const ByteVector &data) override { + m_value = data.toUInt(); + } + ByteVector renderFields() const override { + return ByteVector::fromUInt(m_value); + } + + private: + CustomFrame(const ByteVector &data, Header *h) : Frame(h) { + parseFields(fieldData(data)); + } + unsigned int m_value; + }; + + // Example for frame factory with support for CustomFrame. + class CustomFrameFactory : public ID3v2::FrameFactory { + protected: + ID3v2::Frame *createFrame(const ByteVector &data, ID3v2::Frame::Header *header, + const ID3v2::Header *tagHeader) const override { + if(header->frameID() == "CUST") { + return new CustomFrame(data, header); + } + return ID3v2::FrameFactory::createFrame(data, header, tagHeader); + } + }; + + CustomFrameFactory factory; + + ScopedFileCopy copy("lame_cbr", ".mp3"); + { + MPEG::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT(f.hasID3v2Tag()); + ID3v2::Tag *tag = f.ID3v2Tag(); + tag->addFrame(new CustomFrame(1234567890)); + f.save(); + } + { + MPEG::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT(f.hasID3v2Tag()); + ID3v2::Tag *tag = f.ID3v2Tag(); + const auto &frames = tag->frameList("CUST"); + CPPUNIT_ASSERT(!frames.isEmpty()); + // Without a specialized FrameFactory, you can add custom frames, + // but your cannot parse them. + CPPUNIT_ASSERT(!dynamic_cast(frames.front())); + } + { + MPEG::File f(copy.fileName().c_str(), &factory); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT(f.hasID3v2Tag()); + ID3v2::Tag *tag = f.ID3v2Tag(); + const auto &frames = tag->frameList("CUST"); + CPPUNIT_ASSERT(!frames.isEmpty()); + auto frame = dynamic_cast(frames.front()); + CPPUNIT_ASSERT(frame); + CPPUNIT_ASSERT_EQUAL(1234567890U, frame->value()); + PropertyMap properties = tag->properties(); + CPPUNIT_ASSERT_EQUAL(StringList("1234567890"), + properties.value("CUSTOM")); + CPPUNIT_ASSERT_EQUAL(StringList("-1.020000 dB"), + properties.value("REPLAYGAIN_TRACK_GAIN")); + CPPUNIT_ASSERT_EQUAL(StringList("0.920032"), + properties.value("REPLAYGAIN_TRACK_PEAK")); + } + } + }; CPPUNIT_TEST_SUITE_REGISTRATION(TestMPEG);