mirror of
https://github.com/taglib/taglib.git
synced 2025-07-18 04:54:19 -04:00
Fix extensibility of ID3v2 FrameFactory
Because the main extension point of FrameFactory was using a protected Frame subclass, it was not really possible to implement a custom frame factory. Existing Frame subclasses also show that access to the frame header might be needed when implementing a Frame subclass.
This commit is contained in:
@ -47,6 +47,7 @@ SET(test_runner_SRCS
|
||||
test_fileref.cpp
|
||||
test_id3v1.cpp
|
||||
test_id3v2.cpp
|
||||
test_id3v2framefactory.cpp
|
||||
test_xiphcomment.cpp
|
||||
test_aiff.cpp
|
||||
test_riff.cpp
|
||||
|
379
tests/test_id3v2framefactory.cpp
Normal file
379
tests/test_id3v2framefactory.cpp
Normal file
@ -0,0 +1,379 @@
|
||||
/***************************************************************************
|
||||
copyright : (C) 2023 by Urs Fleisch
|
||||
email : ufleisch@users.sourceforge.net
|
||||
***************************************************************************/
|
||||
|
||||
/***************************************************************************
|
||||
* 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 <functional>
|
||||
#include <memory>
|
||||
|
||||
#include "tbytevector.h"
|
||||
#include "tpropertymap.h"
|
||||
#include "mpegfile.h"
|
||||
#include "flacfile.h"
|
||||
#include "trueaudiofile.h"
|
||||
#include "wavfile.h"
|
||||
#include "aifffile.h"
|
||||
#include "dsffile.h"
|
||||
#include "dsdifffile.h"
|
||||
#include "id3v2tag.h"
|
||||
#include "id3v2frame.h"
|
||||
#include "id3v2framefactory.h"
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
#include "utils.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace TagLib;
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
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 {
|
||||
public:
|
||||
ID3v2::Frame *createFrameForProperty(
|
||||
const String &key, const StringList &values) const override {
|
||||
if(key == "CUSTOM") {
|
||||
return new CustomFrame(!values.isEmpty() ? values.front().toInt() : 0);
|
||||
}
|
||||
return ID3v2::FrameFactory::createFrameForProperty(key, values);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
class TestId3v2FrameFactory : public CppUnit::TestFixture
|
||||
{
|
||||
CPPUNIT_TEST_SUITE(TestId3v2FrameFactory);
|
||||
CPPUNIT_TEST(testMPEG);
|
||||
CPPUNIT_TEST(testFLAC);
|
||||
CPPUNIT_TEST(testTrueAudio);
|
||||
CPPUNIT_TEST(testWAV);
|
||||
CPPUNIT_TEST(testAIFF);
|
||||
CPPUNIT_TEST(testDSF);
|
||||
CPPUNIT_TEST(testDSDIFF);
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
|
||||
public:
|
||||
|
||||
void testGenericFrameFactory(
|
||||
const char *fileName,
|
||||
function<File *(const char *)> createFileWithDefaultFactory,
|
||||
function<File *(const char *, ID3v2::FrameFactory *factory)> createFileWithFactory,
|
||||
function<bool(const File &)> hasID3v2Tag,
|
||||
function<ID3v2::Tag *(File &)> getID3v2Tag,
|
||||
function<bool(File &)> stripAllTags)
|
||||
{
|
||||
CustomFrameFactory factory;
|
||||
|
||||
{
|
||||
auto f = std::unique_ptr<File>(createFileWithDefaultFactory(fileName));
|
||||
CPPUNIT_ASSERT(f->isValid());
|
||||
ID3v2::Tag *tag = getID3v2Tag(*f);
|
||||
const ID3v2::FrameList frames = tag->frameList();
|
||||
for(const auto &frame : frames) {
|
||||
tag->removeFrame(frame, false);
|
||||
}
|
||||
tag->setArtist("An artist");
|
||||
tag->setTitle("A title");
|
||||
f->save();
|
||||
}
|
||||
{
|
||||
auto f = std::unique_ptr<File>(createFileWithDefaultFactory(fileName));
|
||||
CPPUNIT_ASSERT(f->isValid());
|
||||
CPPUNIT_ASSERT(hasID3v2Tag(*f));
|
||||
ID3v2::Tag *tag = getID3v2Tag(*f);
|
||||
tag->addFrame(new CustomFrame(1234567890));
|
||||
f->save();
|
||||
}
|
||||
{
|
||||
auto f = std::unique_ptr<File>(createFileWithDefaultFactory(fileName));
|
||||
CPPUNIT_ASSERT(f->isValid());
|
||||
CPPUNIT_ASSERT(hasID3v2Tag(*f));
|
||||
ID3v2::Tag *tag = getID3v2Tag(*f);
|
||||
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<CustomFrame *>(frames.front()));
|
||||
}
|
||||
{
|
||||
auto f = std::unique_ptr<File>(createFileWithFactory(fileName, &factory));
|
||||
CPPUNIT_ASSERT(f->isValid());
|
||||
CPPUNIT_ASSERT(hasID3v2Tag(*f));
|
||||
ID3v2::Tag *tag = getID3v2Tag(*f);
|
||||
const auto &frames = tag->frameList("CUST");
|
||||
CPPUNIT_ASSERT(!frames.isEmpty());
|
||||
auto frame = dynamic_cast<CustomFrame *>(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("An artist"),
|
||||
properties.value("ARTIST"));
|
||||
CPPUNIT_ASSERT_EQUAL(StringList("A title"),
|
||||
properties.value("TITLE"));
|
||||
stripAllTags(*f);
|
||||
}
|
||||
{
|
||||
auto f = std::unique_ptr<File>(createFileWithFactory(fileName, &factory));
|
||||
CPPUNIT_ASSERT(f->isValid());
|
||||
CPPUNIT_ASSERT(!hasID3v2Tag(*f));
|
||||
ID3v2::Tag *tag = getID3v2Tag(*f);
|
||||
PropertyMap properties = tag->properties();
|
||||
CPPUNIT_ASSERT(properties.isEmpty());
|
||||
properties.insert("CUSTOM", StringList("305419896"));
|
||||
tag->setProperties(properties);
|
||||
f->save();
|
||||
}
|
||||
{
|
||||
auto f = std::unique_ptr<File>(createFileWithFactory(fileName, &factory));
|
||||
CPPUNIT_ASSERT(f->isValid());
|
||||
CPPUNIT_ASSERT(hasID3v2Tag(*f));
|
||||
ID3v2::Tag *tag = getID3v2Tag(*f);
|
||||
PropertyMap properties = tag->properties();
|
||||
CPPUNIT_ASSERT_EQUAL(StringList("305419896"), properties.value("CUSTOM"));
|
||||
const auto &frames = tag->frameList("CUST");
|
||||
CPPUNIT_ASSERT(!frames.isEmpty());
|
||||
auto frame = dynamic_cast<CustomFrame *>(frames.front());
|
||||
CPPUNIT_ASSERT(frame);
|
||||
CPPUNIT_ASSERT_EQUAL(0x12345678U, frame->value());
|
||||
}
|
||||
}
|
||||
|
||||
void testMPEG()
|
||||
{
|
||||
ScopedFileCopy copy("lame_cbr", ".mp3");
|
||||
testGenericFrameFactory(
|
||||
copy.fileName().c_str(),
|
||||
[](const char *fileName) {
|
||||
return new MPEG::File(fileName);
|
||||
},
|
||||
[](const char *fileName, ID3v2::FrameFactory *factory) {
|
||||
return new MPEG::File(fileName, factory);
|
||||
},
|
||||
[](const File &f) {
|
||||
return static_cast<const MPEG::File &>(f).hasID3v2Tag();
|
||||
},
|
||||
[](File &f) {
|
||||
return static_cast<MPEG::File &>(f).ID3v2Tag(true);
|
||||
},
|
||||
[](File &f) {
|
||||
return static_cast<MPEG::File &>(f).strip();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void testFLAC()
|
||||
{
|
||||
ScopedFileCopy copy("no-tags", ".flac");
|
||||
testGenericFrameFactory(
|
||||
copy.fileName().c_str(),
|
||||
[](const char *fileName) {
|
||||
return new FLAC::File(fileName);
|
||||
},
|
||||
[](const char *fileName, ID3v2::FrameFactory *factory) {
|
||||
return new FLAC::File(fileName, factory);
|
||||
},
|
||||
[](const File &f) {
|
||||
return static_cast<const FLAC::File &>(f).hasID3v2Tag();
|
||||
},
|
||||
[](File &f) {
|
||||
return static_cast<FLAC::File &>(f).ID3v2Tag(true);
|
||||
},
|
||||
[](File &f) {
|
||||
static_cast<FLAC::File &>(f).strip();
|
||||
return f.save();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void testTrueAudio()
|
||||
{
|
||||
ScopedFileCopy copy("empty", ".tta");
|
||||
testGenericFrameFactory(
|
||||
copy.fileName().c_str(),
|
||||
[](const char *fileName) {
|
||||
return new TrueAudio::File(fileName);
|
||||
},
|
||||
[](const char *fileName, ID3v2::FrameFactory *factory) {
|
||||
return new TrueAudio::File(fileName, factory);
|
||||
},
|
||||
[](const File &f) {
|
||||
return static_cast<const TrueAudio::File &>(f).hasID3v2Tag();
|
||||
},
|
||||
[](File &f) {
|
||||
return static_cast<TrueAudio::File &>(f).ID3v2Tag(true);
|
||||
},
|
||||
[](File &f) {
|
||||
static_cast<TrueAudio::File &>(f).strip();
|
||||
return f.save();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void testWAV()
|
||||
{
|
||||
ScopedFileCopy copy("empty", ".wav");
|
||||
testGenericFrameFactory(
|
||||
copy.fileName().c_str(),
|
||||
[](const char *fileName) {
|
||||
return new RIFF::WAV::File(fileName);
|
||||
},
|
||||
[](const char *fileName, ID3v2::FrameFactory *factory) {
|
||||
return new RIFF::WAV::File(
|
||||
fileName, true, RIFF::WAV::Properties::Average, factory);
|
||||
},
|
||||
[](const File &f) {
|
||||
return static_cast<const RIFF::WAV::File &>(f).hasID3v2Tag();
|
||||
},
|
||||
[](File &f) {
|
||||
return static_cast<RIFF::WAV::File &>(f).tag();
|
||||
},
|
||||
[](File &f) {
|
||||
static_cast<RIFF::WAV::File &>(f).strip();
|
||||
return true;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void testAIFF()
|
||||
{
|
||||
ScopedFileCopy copy("empty", ".aiff");
|
||||
testGenericFrameFactory(
|
||||
copy.fileName().c_str(),
|
||||
[](const char *fileName) {
|
||||
return new RIFF::AIFF::File(fileName);
|
||||
},
|
||||
[](const char *fileName, ID3v2::FrameFactory *factory) {
|
||||
return new RIFF::AIFF::File(
|
||||
fileName, true, RIFF::AIFF::Properties::Average, factory);
|
||||
},
|
||||
[](const File &f) {
|
||||
return static_cast<const RIFF::AIFF::File &>(f).hasID3v2Tag();
|
||||
},
|
||||
[](File &f) {
|
||||
return static_cast<RIFF::AIFF::File &>(f).tag();
|
||||
},
|
||||
[](File &f) {
|
||||
f.setProperties({});
|
||||
return f.save();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void testDSF()
|
||||
{
|
||||
ScopedFileCopy copy("empty10ms", ".dsf");
|
||||
testGenericFrameFactory(
|
||||
copy.fileName().c_str(),
|
||||
[](const char *fileName) {
|
||||
return new DSF::File(fileName);
|
||||
},
|
||||
[](const char *fileName, ID3v2::FrameFactory *factory) {
|
||||
return new DSF::File(
|
||||
fileName, true, DSF::Properties::Average, factory);
|
||||
},
|
||||
[](const File &f) {
|
||||
return !f.tag()->isEmpty();
|
||||
},
|
||||
[](File &f) {
|
||||
return static_cast<DSF::File &>(f).tag();
|
||||
},
|
||||
[](File &f) {
|
||||
f.setProperties({});
|
||||
return f.save();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void testDSDIFF()
|
||||
{
|
||||
ScopedFileCopy copy("empty10ms", ".dff");
|
||||
testGenericFrameFactory(
|
||||
copy.fileName().c_str(),
|
||||
[](const char *fileName) {
|
||||
return new DSDIFF::File(fileName);
|
||||
},
|
||||
[](const char *fileName, ID3v2::FrameFactory *factory) {
|
||||
return new DSDIFF::File(
|
||||
fileName, true, DSDIFF::Properties::Average, factory);
|
||||
},
|
||||
[](const File &f) {
|
||||
return static_cast<const DSDIFF::File &>(f).hasID3v2Tag();
|
||||
},
|
||||
[](File &f) {
|
||||
return static_cast<DSDIFF::File &>(f).ID3v2Tag(true);
|
||||
},
|
||||
[](File &f) {
|
||||
static_cast<DSDIFF::File &>(f).strip();
|
||||
return true;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
CPPUNIT_TEST_SUITE_REGISTRATION(TestId3v2FrameFactory);
|
Reference in New Issue
Block a user