Support for Matroska chapters

This commit is contained in:
Urs Fleisch
2025-11-12 16:55:21 +01:00
parent d2bf7e72d2
commit b625be5690
18 changed files with 1364 additions and 10 deletions

View File

@ -130,6 +130,9 @@
#include "matroskatag.h"
#include "matroskaattachments.h"
#include "matroskaattachedfile.h"
#include "matroskachapter.h"
#include "matroskachapteredition.h"
#include "matroskachapters.h"
#include "matroskasimpletag.h"
#include "plainfile.h"
#include <cppunit/extensions/HelperMacros.h>
@ -152,6 +155,7 @@ class TestMatroska : public CppUnit::TestFixture
CPPUNIT_TEST(testComplexProperties);
CPPUNIT_TEST(testOpenInvalid);
CPPUNIT_TEST(testSegmentSizeChange);
CPPUNIT_TEST(testChapters);
CPPUNIT_TEST_SUITE_END();
public:
@ -995,6 +999,208 @@ public:
}
}
void testChapters()
{
const Matroska::ChapterEdition edition1(
List{
Matroska::Chapter(
0, 40000,
List{
Matroska::Chapter::Display("Chapter 1", "eng")},
1, false),
Matroska::Chapter(
40000, 80000,
List{
Matroska::Chapter::Display("Chapter 2", "eng"),
Matroska::Chapter::Display("Kapitel 2", "deu"),
},
2),
Matroska::Chapter(
80000, 120000,
List{
Matroska::Chapter::Display("Chapter 3", "und")},
3, true)
},
true, false);
const VariantMap chapterEdition1 {
{"chapters",
VariantList{
VariantMap{
{"displays", VariantList{
VariantMap{{"language", "eng"}, {"string", "Chapter 1"}}}},
{"timeEnd", 40000ULL},
{"timeStart", 0ULL},
{"uid", 1ULL}
},
VariantMap{
{"displays", VariantList{
VariantMap{{"language", "eng"}, {"string", "Chapter 2"}},
VariantMap{{"language", "deu"}, {"string", "Kapitel 2"}}}},
{"timeEnd", 80000ULL},
{"timeStart", 40000ULL},
{"uid", 2ULL}
},
VariantMap{
{
"displays", VariantList{
VariantMap{{"language", "und"}, {"string", "Chapter 3"}}}
},
{"isHidden", true},
{"timeEnd", 120000ULL},
{"timeStart", 80000ULL},
{"uid", 3ULL}
}
}
},
{"isDefault", true}
};
const VariantMap chapterEdition2 {
{"chapters",
VariantList{
VariantMap{
{"displays", VariantList{
VariantMap{{"string", "Chapter A"}}}},
{"timeStart", 10000ULL},
{"uid", 1234567890ULL}
},
}
},
{"isOrdered", true},
{"uid", 321ULL}
};
ScopedFileCopy copy("tags-before-cues", ".mkv");
string newname = copy.fileName();
{
Matroska::File f(newname.c_str());
CPPUNIT_ASSERT(f.isValid());
CPPUNIT_ASSERT(f.tag(false));
CPPUNIT_ASSERT(!f.attachments(false));
CPPUNIT_ASSERT(!f.chapters(false));
CPPUNIT_ASSERT_EQUAL(StringList({"DURATION"}), f.complexPropertyKeys());
CPPUNIT_ASSERT(f.complexProperties("CHAPTERS").isEmpty());
f.chapters(true)->addChapterEdition(edition1);
CPPUNIT_ASSERT(f.save());
}
{
Matroska::File f(newname.c_str());
CPPUNIT_ASSERT(f.isValid());
CPPUNIT_ASSERT(f.tag(false));
CPPUNIT_ASSERT(!f.attachments(false));
auto chapters = f.chapters(false);
CPPUNIT_ASSERT(chapters);
CPPUNIT_ASSERT_EQUAL(StringList({"DURATION", "CHAPTERS"}), f.complexPropertyKeys());
auto chaptersProperties = f.complexProperties("CHAPTERS");
CPPUNIT_ASSERT_EQUAL(1U, chaptersProperties.size());
CPPUNIT_ASSERT_EQUAL(chapterEdition1, chaptersProperties.front());
CPPUNIT_ASSERT_EQUAL(1U, chapters->chapterEditionList().size());
const auto &edition = chapters->chapterEditionList().front();
CPPUNIT_ASSERT_EQUAL(true, edition.isDefault());
CPPUNIT_ASSERT_EQUAL(false, edition.isOrdered());
CPPUNIT_ASSERT_EQUAL(0ULL, edition.uid());
const auto &chapterAtoms = edition.chapterList();
CPPUNIT_ASSERT_EQUAL(3U, chapterAtoms.size());
CPPUNIT_ASSERT_EQUAL(1ULL, chapterAtoms[0].uid());
CPPUNIT_ASSERT_EQUAL(false, chapterAtoms[0].isHidden());
CPPUNIT_ASSERT_EQUAL(0ULL, chapterAtoms[0].timeStart());
CPPUNIT_ASSERT_EQUAL(40000ULL, chapterAtoms[0].timeEnd());
CPPUNIT_ASSERT_EQUAL(1U, chapterAtoms[0].displayList().size());
CPPUNIT_ASSERT_EQUAL(String("Chapter 1"), chapterAtoms[0].displayList()[0].string());
CPPUNIT_ASSERT_EQUAL(String("eng"), chapterAtoms[0].displayList()[0].language());
CPPUNIT_ASSERT_EQUAL(2ULL, chapterAtoms[1].uid());
CPPUNIT_ASSERT_EQUAL(false, chapterAtoms[1].isHidden());
CPPUNIT_ASSERT_EQUAL(40000ULL, chapterAtoms[1].timeStart());
CPPUNIT_ASSERT_EQUAL(80000ULL, chapterAtoms[1].timeEnd());
CPPUNIT_ASSERT_EQUAL(2U, chapterAtoms[1].displayList().size());
CPPUNIT_ASSERT_EQUAL(String("Chapter 2"), chapterAtoms[1].displayList()[0].string());
CPPUNIT_ASSERT_EQUAL(String("eng"), chapterAtoms[1].displayList()[0].language());
CPPUNIT_ASSERT_EQUAL(String("Kapitel 2"), chapterAtoms[1].displayList()[1].string());
CPPUNIT_ASSERT_EQUAL(String("deu"), chapterAtoms[1].displayList()[1].language());
CPPUNIT_ASSERT_EQUAL(3ULL, chapterAtoms[2].uid());
CPPUNIT_ASSERT_EQUAL(true, chapterAtoms[2].isHidden());
CPPUNIT_ASSERT_EQUAL(80000ULL, chapterAtoms[2].timeStart());
CPPUNIT_ASSERT_EQUAL(120000ULL, chapterAtoms[2].timeEnd());
CPPUNIT_ASSERT_EQUAL(1U, chapterAtoms[2].displayList().size());
CPPUNIT_ASSERT_EQUAL(String("Chapter 3"), chapterAtoms[2].displayList()[0].string());
CPPUNIT_ASSERT_EQUAL(String("und"), chapterAtoms[2].displayList()[0].language());
CPPUNIT_ASSERT(f.setComplexProperties("CHAPTERS", {chapterEdition2}));
CPPUNIT_ASSERT(f.save());
}
{
Matroska::File f(newname.c_str(), true, AudioProperties::Accurate);
CPPUNIT_ASSERT(f.isValid());
CPPUNIT_ASSERT(f.tag(false));
CPPUNIT_ASSERT(!f.attachments(false));
auto chapters = f.chapters(false);
CPPUNIT_ASSERT(chapters);
CPPUNIT_ASSERT_EQUAL(StringList({"DURATION", "CHAPTERS"}), f.complexPropertyKeys());
auto chaptersProperties = f.complexProperties("CHAPTERS");
CPPUNIT_ASSERT_EQUAL(1U, chaptersProperties.size());
CPPUNIT_ASSERT_EQUAL(chapterEdition2, chaptersProperties.front());
CPPUNIT_ASSERT_EQUAL(1U, chapters->chapterEditionList().size());
const auto &edition = chapters->chapterEditionList().front();
CPPUNIT_ASSERT_EQUAL(false, edition.isDefault());
CPPUNIT_ASSERT_EQUAL(true, edition.isOrdered());
CPPUNIT_ASSERT_EQUAL(321ULL, edition.uid());
const auto &chapterAtoms = edition.chapterList();
CPPUNIT_ASSERT_EQUAL(1U, chapterAtoms.size());
CPPUNIT_ASSERT_EQUAL(1234567890ULL, chapterAtoms[0].uid());
CPPUNIT_ASSERT_EQUAL(false, chapterAtoms[0].isHidden());
CPPUNIT_ASSERT_EQUAL(10000ULL, chapterAtoms[0].timeStart());
CPPUNIT_ASSERT_EQUAL(0ULL, chapterAtoms[0].timeEnd());
CPPUNIT_ASSERT_EQUAL(1U, chapterAtoms[0].displayList().size());
CPPUNIT_ASSERT_EQUAL(String("Chapter A"), chapterAtoms[0].displayList()[0].string());
CPPUNIT_ASSERT_EQUAL(String(), chapterAtoms[0].displayList()[0].language());
const Matroska::ChapterEdition edition2 = chapters->chapterEditionList().front();
chapters->removeChapterEdition(321ULL);
chapters->addChapterEdition(edition1);
chapters->addChapterEdition(edition2);
Matroska::AttachedFile attachedFile;
attachedFile.setFileName("folder.png");
attachedFile.setMediaType("image/png");
attachedFile.setDescription("Cover");
attachedFile.setData(ByteVector("PNG data"));
attachedFile.setUID(1763187649ULL);
f.attachments(true)->addAttachedFile(attachedFile);
CPPUNIT_ASSERT(f.save());
}
{
Matroska::File f(newname.c_str(), true, AudioProperties::Accurate);
CPPUNIT_ASSERT(f.isValid());
CPPUNIT_ASSERT(f.tag(false));
CPPUNIT_ASSERT(f.attachments(false));
CPPUNIT_ASSERT(f.chapters(false));
CPPUNIT_ASSERT_EQUAL(StringList({"DURATION", "PICTURE", "CHAPTERS"}),
f.complexPropertyKeys());
auto chaptersProperties = f.complexProperties("CHAPTERS");
CPPUNIT_ASSERT_EQUAL(2U, chaptersProperties.size());
CPPUNIT_ASSERT_EQUAL(chapterEdition1, chaptersProperties.front());
CPPUNIT_ASSERT_EQUAL(chapterEdition2, chaptersProperties.back());
f.attachments()->clear();
f.chapters()->clear();
CPPUNIT_ASSERT(f.save());
}
{
Matroska::File f(newname.c_str(), true, AudioProperties::Accurate);
CPPUNIT_ASSERT(f.isValid());
CPPUNIT_ASSERT(f.tag(false));
CPPUNIT_ASSERT(!f.attachments(false));
}
// Check if file with initial tags is same as original file
const ByteVector origData = PlainFile(TEST_FILE_PATH_C("tags-before-cues.mkv")).readAll();
const ByteVector fileData = PlainFile(newname.c_str()).readAll();
CPPUNIT_ASSERT(origData == fileData);
}
};
CPPUNIT_TEST_SUITE_REGISTRATION(TestMatroska);

View File

@ -158,6 +158,7 @@
#include "matroskaattachedfile.h"
#include "matroskaattachments.h"
#include "matroskachapters.h"
using namespace std;
using namespace TagLib;
@ -312,6 +313,7 @@ public:
CPPUNIT_ASSERT_EQUAL(classSize(3, true), sizeof(TagLib::Matroska::Tag));
CPPUNIT_ASSERT_EQUAL(classSize(0, true), sizeof(TagLib::Matroska::Element));
CPPUNIT_ASSERT_EQUAL(classSize(1, true), sizeof(TagLib::Matroska::Attachments));
CPPUNIT_ASSERT_EQUAL(classSize(1, true), sizeof(TagLib::Matroska::Chapters));
CPPUNIT_ASSERT_EQUAL(classSize(0, false), sizeof(TagLib::Matroska::SimpleTag));
CPPUNIT_ASSERT_EQUAL(classSize(0, false), sizeof(TagLib::Matroska::AttachedFile));
#endif