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);