mirror of
https://github.com/taglib/taglib.git
synced 2026-04-12 17:09:50 -04:00
MP4: Add support for NI STEM (#1299)
This commit is contained in:
committed by
GitHub
parent
2c01b63433
commit
11e3eb05bd
@ -194,6 +194,7 @@ if(WITH_MP4)
|
||||
mp4/mp4item.h
|
||||
mp4/mp4properties.h
|
||||
mp4/mp4coverart.h
|
||||
mp4/mp4stem.h
|
||||
mp4/mp4itemfactory.h
|
||||
)
|
||||
endif()
|
||||
@ -369,6 +370,7 @@ if(WITH_MP4)
|
||||
mp4/mp4item.cpp
|
||||
mp4/mp4properties.cpp
|
||||
mp4/mp4coverart.cpp
|
||||
mp4/mp4stem.cpp
|
||||
mp4/mp4itemfactory.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
@ -37,7 +37,7 @@ namespace {
|
||||
constexpr std::array containers {
|
||||
"moov", "udta", "mdia", "meta", "ilst",
|
||||
"stbl", "minf", "moof", "traf", "trak",
|
||||
"stsd"
|
||||
"stsd", "stem"
|
||||
};
|
||||
} // namespace
|
||||
|
||||
@ -86,6 +86,11 @@ MP4::Atom::Atom(File *file)
|
||||
|
||||
d->name = header.mid(4, 4);
|
||||
|
||||
if(d->name == "stem") {
|
||||
file->seek(d->length - 8, File::Current);
|
||||
return;
|
||||
}
|
||||
|
||||
for(auto c : containers) {
|
||||
if(d->name == c) {
|
||||
if(d->name == "meta") {
|
||||
|
||||
@ -44,6 +44,7 @@ public:
|
||||
StringList m_stringList;
|
||||
ByteVectorList m_byteVectorList;
|
||||
MP4::CoverArtList m_coverArtList;
|
||||
MP4::Stem m_stem;
|
||||
};
|
||||
|
||||
MP4::Item::Item() :
|
||||
@ -130,6 +131,13 @@ MP4::Item::Item(const MP4::CoverArtList &value) :
|
||||
d->m_coverArtList = value;
|
||||
}
|
||||
|
||||
MP4::Item::Item(const MP4::Stem &value) :
|
||||
d(std::make_shared<ItemPrivate>())
|
||||
{
|
||||
d->type = Type::Stem;
|
||||
d->m_stem = value;
|
||||
}
|
||||
|
||||
void MP4::Item::setAtomDataType(MP4::AtomDataType type)
|
||||
{
|
||||
d->atomDataType = type;
|
||||
@ -194,6 +202,12 @@ MP4::Item::toCoverArtList() const
|
||||
return d->m_coverArtList;
|
||||
}
|
||||
|
||||
MP4::Stem
|
||||
MP4::Item::toStem() const
|
||||
{
|
||||
return d->m_stem;
|
||||
}
|
||||
|
||||
bool
|
||||
MP4::Item::isValid() const
|
||||
{
|
||||
@ -234,6 +248,8 @@ bool MP4::Item::operator==(const Item &other) const
|
||||
return toByteVectorList() == other.toByteVectorList();
|
||||
case Type::CoverArtList:
|
||||
return toCoverArtList() == other.toCoverArtList();
|
||||
case Type::Stem:
|
||||
return toStem() == other.toStem();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
@ -29,6 +29,7 @@
|
||||
#include "tstringlist.h"
|
||||
#include "taglib_export.h"
|
||||
#include "mp4coverart.h"
|
||||
#include "mp4stem.h"
|
||||
|
||||
namespace TagLib {
|
||||
namespace MP4 {
|
||||
@ -49,7 +50,8 @@ namespace TagLib {
|
||||
LongLong,
|
||||
StringList,
|
||||
ByteVectorList,
|
||||
CoverArtList
|
||||
CoverArtList,
|
||||
Stem,
|
||||
};
|
||||
|
||||
struct IntPair {
|
||||
@ -80,6 +82,7 @@ namespace TagLib {
|
||||
Item(const StringList &value);
|
||||
Item(const ByteVectorList &value);
|
||||
Item(const CoverArtList &value);
|
||||
Item(const Stem &value);
|
||||
|
||||
void setAtomDataType(AtomDataType type);
|
||||
AtomDataType atomDataType() const;
|
||||
@ -93,6 +96,7 @@ namespace TagLib {
|
||||
StringList toStringList() const;
|
||||
ByteVectorList toByteVectorList() const;
|
||||
CoverArtList toCoverArtList() const;
|
||||
Stem toStem() const;
|
||||
|
||||
bool isValid() const;
|
||||
|
||||
|
||||
@ -87,6 +87,8 @@ std::pair<String, Item> ItemFactory::parseItem(
|
||||
return parseGnre(atom, data);
|
||||
case ItemHandlerType::Covr:
|
||||
return parseCovr(atom, data);
|
||||
case ItemHandlerType::Stem:
|
||||
return parseStem(atom, data);
|
||||
case ItemHandlerType::TextImplicit:
|
||||
return parseText(atom, data, -1);
|
||||
case ItemHandlerType::Text:
|
||||
@ -128,6 +130,8 @@ ByteVector ItemFactory::renderItem(
|
||||
return renderInt(name, item);
|
||||
case ItemHandlerType::Covr:
|
||||
return renderCovr(name, item);
|
||||
case ItemHandlerType::Stem:
|
||||
return renderStem(name, item);
|
||||
case ItemHandlerType::TextImplicit:
|
||||
return renderText(name, item, TypeImplicit);
|
||||
case ItemHandlerType::Text:
|
||||
@ -175,8 +179,8 @@ std::pair<ByteVector, Item> ItemFactory::itemFromProperty(
|
||||
case ItemHandlerType::TextImplicit:
|
||||
case ItemHandlerType::Text:
|
||||
return {name, values};
|
||||
|
||||
case ItemHandlerType::Covr:
|
||||
case ItemHandlerType::Stem:
|
||||
debug("MP4: Invalid item \"" + name + "\" for property");
|
||||
break;
|
||||
case ItemHandlerType::Unknown:
|
||||
@ -222,6 +226,7 @@ std::pair<String, StringList> ItemFactory::itemToProperty(
|
||||
return {key, item.toStringList()};
|
||||
|
||||
case ItemHandlerType::Covr:
|
||||
case ItemHandlerType::Stem:
|
||||
debug("MP4: Invalid item \"" + itemName + "\" for property");
|
||||
break;
|
||||
case ItemHandlerType::Unknown:
|
||||
@ -303,6 +308,7 @@ ItemFactory::NameHandlerMap ItemFactory::nameHandlerMap() const
|
||||
{"akID", ItemHandlerType::Byte},
|
||||
{"gnre", ItemHandlerType::Gnre},
|
||||
{"covr", ItemHandlerType::Covr},
|
||||
{"stem", ItemHandlerType::Stem},
|
||||
{"purl", ItemHandlerType::TextImplicit},
|
||||
{"egid", ItemHandlerType::TextImplicit},
|
||||
};
|
||||
@ -633,6 +639,12 @@ std::pair<String, Item> ItemFactory::parseCovr(
|
||||
};
|
||||
}
|
||||
|
||||
std::pair<String, Item> ItemFactory::parseStem(
|
||||
const MP4::Atom *atom, const ByteVector &data)
|
||||
{
|
||||
return {atom->name(), Item(Stem(data))};
|
||||
}
|
||||
|
||||
|
||||
ByteVector ItemFactory::renderAtom(
|
||||
const ByteVector &name, const ByteVector &data)
|
||||
@ -742,6 +754,13 @@ ByteVector ItemFactory::renderCovr(
|
||||
return renderAtom(name, data);
|
||||
}
|
||||
|
||||
ByteVector ItemFactory::renderStem(
|
||||
const ByteVector &name, const MP4::Item &item)
|
||||
{
|
||||
auto data = item.toStem().data();
|
||||
return renderAtom(name, data);
|
||||
}
|
||||
|
||||
ByteVector ItemFactory::renderFreeForm(
|
||||
const String &name, const MP4::Item &item)
|
||||
{
|
||||
|
||||
@ -142,6 +142,7 @@ namespace TagLib {
|
||||
Byte,
|
||||
Gnre,
|
||||
Covr,
|
||||
Stem,
|
||||
TextImplicit,
|
||||
Text
|
||||
};
|
||||
@ -214,6 +215,8 @@ namespace TagLib {
|
||||
const MP4::Atom *atom, const ByteVector &bytes);
|
||||
static std::pair<String, Item> parseCovr(
|
||||
const MP4::Atom *atom, const ByteVector &data);
|
||||
static std::pair<String, Item> parseStem(
|
||||
const MP4::Atom *atom, const ByteVector &data);
|
||||
|
||||
// Functions used by renderItem() to render atom data for items.
|
||||
static ByteVector renderAtom(
|
||||
@ -242,6 +245,8 @@ namespace TagLib {
|
||||
const ByteVector &name, const MP4::Item &item);
|
||||
static ByteVector renderCovr(
|
||||
const ByteVector &name, const MP4::Item &item);
|
||||
static ByteVector renderStem(
|
||||
const ByteVector &name, const MP4::Item &item);
|
||||
|
||||
private:
|
||||
static ItemFactory factory;
|
||||
|
||||
64
taglib/mp4/mp4stem.cpp
Normal file
64
taglib/mp4/mp4stem.cpp
Normal file
@ -0,0 +1,64 @@
|
||||
/**************************************************************************
|
||||
copyright : (C) 2026 by Antoine Colombier
|
||||
email : antoine@mixxx.org
|
||||
**************************************************************************/
|
||||
|
||||
/***************************************************************************
|
||||
* 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 "mp4stem.h"
|
||||
|
||||
using namespace TagLib;
|
||||
|
||||
MP4::Stem::Stem(const ByteVector &data) :
|
||||
d(std::make_shared<StemPrivate>())
|
||||
{
|
||||
d->data = data;
|
||||
}
|
||||
|
||||
MP4::Stem::Stem() = default;
|
||||
MP4::Stem::Stem(const Stem &) = default;
|
||||
MP4::Stem &MP4::Stem::operator=(const Stem &) = default;
|
||||
|
||||
void
|
||||
MP4::Stem::swap(Stem &item) noexcept
|
||||
{
|
||||
using std::swap;
|
||||
|
||||
swap(d, item.d);
|
||||
}
|
||||
|
||||
MP4::Stem::~Stem() = default;
|
||||
|
||||
ByteVector
|
||||
MP4::Stem::data() const
|
||||
{
|
||||
return d->data;
|
||||
}
|
||||
|
||||
bool MP4::Stem::operator==(const Stem &other) const
|
||||
{
|
||||
return data() == other.data();
|
||||
}
|
||||
|
||||
bool MP4::Stem::operator!=(const Stem &other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
78
taglib/mp4/mp4stem.h
Normal file
78
taglib/mp4/mp4stem.h
Normal file
@ -0,0 +1,78 @@
|
||||
/**************************************************************************
|
||||
copyright : (C) 2026 by Antoine Colombier
|
||||
email : antoine@mixxx.org
|
||||
**************************************************************************/
|
||||
|
||||
/***************************************************************************
|
||||
* 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_MP4STEM_H
|
||||
#define TAGLIB_MP4STEM_H
|
||||
|
||||
#include "tbytevector.h"
|
||||
#include "taglib_export.h"
|
||||
|
||||
namespace TagLib::MP4 {
|
||||
//! STEM
|
||||
class StemPrivate
|
||||
{
|
||||
public:
|
||||
ByteVector data;
|
||||
};
|
||||
|
||||
class TAGLIB_EXPORT Stem
|
||||
{
|
||||
public:
|
||||
Stem();
|
||||
explicit Stem(const ByteVector &data);
|
||||
~Stem();
|
||||
|
||||
Stem(const Stem &item);
|
||||
|
||||
/*!
|
||||
* Copies the contents of \a item into this Stem.
|
||||
*/
|
||||
Stem &operator=(const Stem &item);
|
||||
|
||||
/*!
|
||||
* Exchanges the content of the Stem with the content of \a item.
|
||||
*/
|
||||
void swap(Stem &item) noexcept;
|
||||
|
||||
//! The Stem data
|
||||
ByteVector data() const;
|
||||
|
||||
/*!
|
||||
* Returns \c true if the Stem and \a other contain the same data.
|
||||
*/
|
||||
bool operator==(const Stem &other) const;
|
||||
|
||||
/*!
|
||||
* Returns \c true if the Stem and \a other have different data.
|
||||
*/
|
||||
bool operator!=(const Stem &other) const;
|
||||
|
||||
private:
|
||||
TAGLIB_MSVC_SUPPRESS_WARNING_NEEDS_TO_HAVE_DLL_INTERFACE
|
||||
std::shared_ptr<StemPrivate> d;
|
||||
};
|
||||
} // namespace TagLib::MP4
|
||||
|
||||
#endif
|
||||
@ -32,6 +32,7 @@
|
||||
#include "mp4itemfactory.h"
|
||||
#include "mp4atom.h"
|
||||
#include "mp4coverart.h"
|
||||
#include "mp4stem.h"
|
||||
|
||||
using namespace TagLib;
|
||||
|
||||
@ -65,15 +66,22 @@ MP4::Tag::Tag(TagLib::File *file, MP4::Atoms *atoms,
|
||||
d->atoms = atoms;
|
||||
|
||||
const MP4::Atom *ilst = atoms->find("moov", "udta", "meta", "ilst");
|
||||
if(!ilst) {
|
||||
//debug("Atom moov.udta.meta.ilst not found.");
|
||||
return;
|
||||
if(ilst) {
|
||||
for(const auto &atom : ilst->children()) {
|
||||
file->seek(atom->offset() + 8);
|
||||
ByteVector data = d->file->readBlock(atom->length() - 8);
|
||||
if(const auto &[name, itm] = d->factory->parseItem(atom, data);
|
||||
itm.isValid()) {
|
||||
addItem(name, itm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(const auto &atom : ilst->children()) {
|
||||
file->seek(atom->offset() + 8);
|
||||
ByteVector data = d->file->readBlock(atom->length() - 8);
|
||||
if(const auto &[name, itm] = d->factory->parseItem(atom, data);
|
||||
const MP4::Atom *stem = atoms->find("moov", "udta", "stem");
|
||||
if(stem) {
|
||||
file->seek(stem->offset() + 8);
|
||||
ByteVector data = d->file->readBlock(stem->length() - 8);
|
||||
if(const auto &[name, itm] = d->factory->parseItem(stem, data);
|
||||
itm.isValid()) {
|
||||
addItem(name, itm);
|
||||
}
|
||||
@ -100,18 +108,33 @@ MP4::Tag::renderAtom(const ByteVector &name, const ByteVector &data) const
|
||||
bool
|
||||
MP4::Tag::save()
|
||||
{
|
||||
ByteVector data;
|
||||
ByteVector ilstData, stemData;
|
||||
for(const auto &[name, itm] : std::as_const(d->items)) {
|
||||
data.append(d->factory->renderItem(name, itm));
|
||||
if(name == "stem"){
|
||||
stemData.append(d->factory->renderItem(name, itm));
|
||||
} else {
|
||||
ilstData.append(d->factory->renderItem(name, itm));
|
||||
}
|
||||
}
|
||||
data = renderAtom("ilst", data);
|
||||
ilstData = renderAtom("ilst", ilstData);
|
||||
|
||||
AtomList path = d->atoms->path("moov", "udta", "meta", "ilst");
|
||||
if(path.size() == 4) {
|
||||
saveExisting(data, path);
|
||||
saveExisting(ilstData, path);
|
||||
}
|
||||
else {
|
||||
saveNew(data);
|
||||
ByteVector metaData = renderAtom("meta", ByteVector(4, '\0') +
|
||||
renderAtom("hdlr", ByteVector(8, '\0') + ByteVector("mdirappl") +
|
||||
ByteVector(9, '\0')) +
|
||||
ilstData + padIlst(ilstData));
|
||||
saveNew(metaData);
|
||||
}
|
||||
|
||||
path = d->atoms->path("moov", "udta", "stem");
|
||||
if(path.size() == 3) {
|
||||
saveExisting(stemData, path);
|
||||
} else if (!stemData.isEmpty()) {
|
||||
saveNew(stemData);
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -127,6 +150,11 @@ MP4::Tag::strip()
|
||||
saveExisting(ByteVector(), path);
|
||||
}
|
||||
|
||||
path = d->atoms->path("moov", "udta", "stem");
|
||||
if(path.size() == 3) {
|
||||
saveExisting(ByteVector(), path);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -227,11 +255,6 @@ MP4::Tag::updateOffsets(offset_t delta, offset_t offset)
|
||||
void
|
||||
MP4::Tag::saveNew(ByteVector data)
|
||||
{
|
||||
data = renderAtom("meta", ByteVector(4, '\0') +
|
||||
renderAtom("hdlr", ByteVector(8, '\0') + ByteVector("mdirappl") +
|
||||
ByteVector(9, '\0')) +
|
||||
data + padIlst(data));
|
||||
|
||||
AtomList path = d->atoms->path("moov", "udta");
|
||||
if(path.size() != 2) {
|
||||
path = d->atoms->path("moov");
|
||||
@ -510,13 +533,17 @@ StringList MP4::Tag::complexPropertyKeys() const
|
||||
if(d->items.contains("covr")) {
|
||||
keys.append("PICTURE");
|
||||
}
|
||||
if(d->items.contains("stem")) {
|
||||
keys.append("STEM");
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
List<VariantMap> MP4::Tag::complexProperties(const String &key) const
|
||||
{
|
||||
List<VariantMap> props;
|
||||
if(const String uppercaseKey = key.upper(); uppercaseKey == "PICTURE") {
|
||||
const String uppercaseKey = key.upper();
|
||||
if(uppercaseKey == "PICTURE") {
|
||||
const CoverArtList pictures = d->items.value("covr").toCoverArtList();
|
||||
for(const CoverArt &picture : pictures) {
|
||||
String mimeType = "image/";
|
||||
@ -543,12 +570,20 @@ List<VariantMap> MP4::Tag::complexProperties(const String &key) const
|
||||
props.append(property);
|
||||
}
|
||||
}
|
||||
else if(uppercaseKey == "STEM" && d->items.contains("stem")) {
|
||||
const Stem stem = d->items.value("stem").toStem();
|
||||
|
||||
VariantMap property;
|
||||
property.insert("manifest", stem.data());
|
||||
props.append(property);
|
||||
}
|
||||
return props;
|
||||
}
|
||||
|
||||
bool MP4::Tag::setComplexProperties(const String &key, const List<VariantMap> &value)
|
||||
{
|
||||
if(const String uppercaseKey = key.upper(); uppercaseKey == "PICTURE") {
|
||||
const String uppercaseKey = key.upper();
|
||||
if(uppercaseKey == "PICTURE") {
|
||||
CoverArtList pictures;
|
||||
for(const auto &property : value) {
|
||||
auto mimeType = property.value("mimeType").value<String>();
|
||||
@ -568,6 +603,13 @@ bool MP4::Tag::setComplexProperties(const String &key, const List<VariantMap> &v
|
||||
}
|
||||
d->items["covr"] = pictures;
|
||||
}
|
||||
else if(uppercaseKey == "STEM") {
|
||||
if (!value.isEmpty()) {
|
||||
d->items["stem"] = Stem(value.front().value("manifest").value<ByteVector>());
|
||||
} else {
|
||||
d->items.erase("stem");
|
||||
}
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -131,6 +131,7 @@ IF(WITH_MP4)
|
||||
test_mp4.cpp
|
||||
test_mp4item.cpp
|
||||
test_mp4coverart.cpp
|
||||
test_mp4stem.cpp
|
||||
)
|
||||
ENDIF()
|
||||
IF(WITH_ASF)
|
||||
|
||||
153
tests/test_mp4stem.cpp
Normal file
153
tests/test_mp4stem.cpp
Normal file
@ -0,0 +1,153 @@
|
||||
/***************************************************************************
|
||||
copyright : (C) 2026 by Antoine Colombier
|
||||
email : antoine@mixxx.org
|
||||
***************************************************************************/
|
||||
|
||||
/***************************************************************************
|
||||
* 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 <string>
|
||||
|
||||
#include "tag.h"
|
||||
#include "mp4file.h"
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
#include "utils.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace TagLib;
|
||||
|
||||
namespace {
|
||||
const String STEM_KEY("STEM");
|
||||
}
|
||||
|
||||
class TestMP4Stem : public CppUnit::TestFixture
|
||||
{
|
||||
CPPUNIT_TEST_SUITE(TestMP4Stem);
|
||||
CPPUNIT_TEST(testCreate);
|
||||
CPPUNIT_TEST(testUpdate);
|
||||
CPPUNIT_TEST(testRemove);
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
|
||||
protected:
|
||||
static void createTestFile(ScopedFileCopy ©){
|
||||
MP4::File f(copy.fileName().c_str());
|
||||
CPPUNIT_ASSERT(!f.hasMP4Tag());
|
||||
auto &tag = *f.tag();
|
||||
CPPUNIT_ASSERT_EQUAL(0U, tag.complexProperties(STEM_KEY).size());
|
||||
CPPUNIT_ASSERT(tag.setComplexProperties(STEM_KEY, List<VariantMap>({{{"manifest", ByteVector("{some text data}")}}})));
|
||||
f.save();
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
void testCreate()
|
||||
{
|
||||
ScopedFileCopy copy("no-tags", ".m4a");
|
||||
createTestFile(copy);
|
||||
|
||||
// Assert whether the newly created stem content is as expected
|
||||
{
|
||||
MP4::File f(copy.fileName().c_str());
|
||||
auto &tag = *f.tag();
|
||||
CPPUNIT_ASSERT(f.hasMP4Tag());
|
||||
auto stems = tag.complexProperties(STEM_KEY);
|
||||
CPPUNIT_ASSERT_EQUAL(1U, stems.size());
|
||||
CPPUNIT_ASSERT(stems.front().contains("manifest"));
|
||||
CPPUNIT_ASSERT_EQUAL(Variant(ByteVector("{some text data}")), stems.front().find("manifest")->second);
|
||||
}
|
||||
}
|
||||
|
||||
void testUpdate()
|
||||
{
|
||||
ScopedFileCopy copy("no-tags", ".m4a");
|
||||
createTestFile(copy);
|
||||
|
||||
// Prepare so large test data, to ensure that free padding is correctly used
|
||||
char *buffer = new char[1025]{'X'};
|
||||
buffer[1024] = '\0';
|
||||
String artist(buffer);
|
||||
std::memset(buffer, 'Y', 1024);
|
||||
String title(buffer);
|
||||
std::memset(buffer, 'Z', 1024);
|
||||
ByteVector newStem(buffer);
|
||||
delete [] buffer;
|
||||
|
||||
// Update tags and stems to force atom offset recalculation
|
||||
{
|
||||
MP4::File f(copy.fileName().c_str());
|
||||
CPPUNIT_ASSERT(f.setComplexProperties("PICTURE", List<VariantMap>({
|
||||
{
|
||||
{"data", ByteVector("DummyData")},
|
||||
{"pictureType", "Front Cover"},
|
||||
{"mimeType", String("image/png")},
|
||||
{"description", String("Test")}
|
||||
}
|
||||
})));
|
||||
CPPUNIT_ASSERT(f.tag()->setComplexProperties(STEM_KEY, List<VariantMap>({{{"manifest", newStem}}})));
|
||||
f.tag()->setArtist(artist);
|
||||
f.tag()->setTitle(title);
|
||||
f.save();
|
||||
}
|
||||
// Reload the file to assert its tags
|
||||
{
|
||||
MP4::File f(copy.fileName().c_str());
|
||||
CPPUNIT_ASSERT(f.hasMP4Tag());
|
||||
|
||||
auto stems = f.tag()->complexProperties(STEM_KEY);
|
||||
CPPUNIT_ASSERT_EQUAL(1U, stems.size());
|
||||
CPPUNIT_ASSERT(stems.front().contains("manifest"));
|
||||
CPPUNIT_ASSERT_EQUAL(Variant(newStem), stems.front().find("manifest")->second);
|
||||
|
||||
auto pictures = f.tag()->complexProperties("PICTURE");
|
||||
CPPUNIT_ASSERT_EQUAL(1U, pictures.size());
|
||||
CPPUNIT_ASSERT_EQUAL(VariantMap({
|
||||
{
|
||||
{"data", ByteVector("DummyData")},
|
||||
{"mimeType", String("image/png")},
|
||||
}
|
||||
}), pictures.front());
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL(title, f.tag()->title());
|
||||
CPPUNIT_ASSERT_EQUAL(artist, f.tag()->artist());
|
||||
}
|
||||
}
|
||||
|
||||
void testRemove()
|
||||
{
|
||||
ScopedFileCopy copy("no-tags", ".m4a");
|
||||
createTestFile(copy);
|
||||
|
||||
// Remove the stem
|
||||
{
|
||||
MP4::File f(copy.fileName().c_str());
|
||||
CPPUNIT_ASSERT(f.hasMP4Tag());
|
||||
CPPUNIT_ASSERT(f.tag()->setComplexProperties(STEM_KEY, List<VariantMap>()));
|
||||
f.save();
|
||||
}
|
||||
{
|
||||
MP4::File f(copy.fileName().c_str());
|
||||
CPPUNIT_ASSERT(!f.hasMP4Tag());
|
||||
CPPUNIT_ASSERT(!f.tag()->complexPropertyKeys().contains(STEM_KEY));
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
CPPUNIT_TEST_SUITE_REGISTRATION(TestMP4Stem);
|
||||
Reference in New Issue
Block a user