Skip to content

Commit b625be5

Browse files
committed
Support for Matroska chapters
1 parent d2bf7e7 commit b625be5

18 files changed

Lines changed: 1364 additions & 10 deletions

examples/matroskareader.cpp

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
#include "matroskasimpletag.h"
55
#include "matroskaattachments.h"
66
#include "matroskaattachedfile.h"
7+
#include "matroskachapters.h"
8+
#include "matroskachapteredition.h"
79
#include "tstring.h"
810
#include "tutils.h"
911
#include "tbytevector.h"
@@ -66,16 +68,48 @@ int main(int argc, char *argv[])
6668
const TagLib::String &mediaType = attachedFile.mediaType();
6769
PRINT_PRETTY("Media Type", !mediaType.isEmpty() ? mediaType.toCString(false) : "None");
6870
PRINT_PRETTY("Data Size",
69-
TagLib::Utils::formatString("%u byte(s)",attachedFile.data().size()).toCString(false)
71+
TagLib::Utils::formatString("%u byte(s)", attachedFile.data().size()).toCString(false)
7072
);
7173
PRINT_PRETTY("UID",
72-
TagLib::Utils::formatString("%llu",attachedFile.uid()).toCString(false)
74+
TagLib::Utils::formatString("%llu", attachedFile.uid()).toCString(false)
7375
);
7476
}
7577
}
7678
else
7779
printf("File has no attachments\n");
7880

81+
TagLib::Matroska::Chapters *chapters = file.chapters();
82+
if(chapters) {
83+
printf("Chapters:\n");
84+
const TagLib::Matroska::Chapters::ChapterEditionList &editions = chapters->chapterEditionList();
85+
for(const auto &edition : editions) {
86+
if(edition.uid()) {
87+
PRINT_PRETTY("Edition UID", TagLib::Utils::formatString("%llu", edition.uid())
88+
.toCString(false));
89+
}
90+
PRINT_PRETTY("Edition Flags", TagLib::Utils::formatString("default=%d, ordered=%d",
91+
edition.isDefault(), edition.isOrdered())
92+
.toCString(false));
93+
printf("\n");
94+
for(const auto &chapter : edition.chapterList()) {
95+
PRINT_PRETTY("Chapter UID", TagLib::Utils::formatString("%llu", chapter.uid())
96+
.toCString(false));
97+
PRINT_PRETTY("Chapter flags", TagLib::Utils::formatString("hidden=%d", chapter.isHidden())
98+
.toCString(false));
99+
PRINT_PRETTY("Start-End", TagLib::Utils::formatString("%llu-%llu",
100+
chapter.timeStart(), chapter.timeEnd()).toCString(false));
101+
for(const auto &display : chapter.displayList()) {
102+
PRINT_PRETTY("Display", display.string().toCString(false));
103+
PRINT_PRETTY("Language", !display.language().isEmpty()
104+
? display.language().toCString(false) : "Not set");
105+
}
106+
printf("\n");
107+
}
108+
}
109+
}
110+
else
111+
printf("File has no chapters\n");
112+
79113
if(auto properties = dynamic_cast<const TagLib::Matroska::Properties *>(file.audioProperties())) {
80114
printf("Properties:\n");
81115
PRINT_PRETTY("Doc Type", properties->docType().toCString(false));

taglib/CMakeLists.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,9 @@ if(WITH_MATROSKA)
230230
set(tag_HDRS ${tag_HDRS}
231231
matroska/matroskaattachedfile.h
232232
matroska/matroskaattachments.h
233+
matroska/matroskachapter.h
234+
matroska/matroskachapteredition.h
235+
matroska/matroskachapters.h
233236
matroska/matroskacues.h
234237
matroska/matroskaelement.h
235238
matroska/matroskafile.h
@@ -242,6 +245,7 @@ if(WITH_MATROSKA)
242245
matroska/ebml/ebmlelement.h
243246
matroska/ebml/ebmlmasterelement.h
244247
matroska/ebml/ebmlmkattachments.h
248+
matroska/ebml/ebmlmkchapters.h
245249
matroska/ebml/ebmlmkcues.h
246250
matroska/ebml/ebmlmkseekhead.h
247251
matroska/ebml/ebmlmksegment.h
@@ -449,6 +453,9 @@ if(WITH_MATROSKA)
449453
set(matroska_SRCS
450454
matroska/matroskaattachedfile.cpp
451455
matroska/matroskaattachments.cpp
456+
matroska/matroskachapter.cpp
457+
matroska/matroskachapteredition.cpp
458+
matroska/matroskachapters.cpp
452459
matroska/matroskacues.cpp
453460
matroska/matroskaelement.cpp
454461
matroska/matroskafile.cpp
@@ -464,6 +471,7 @@ if(WITH_MATROSKA)
464471
matroska/ebml/ebmlelement.cpp
465472
matroska/ebml/ebmlmasterelement.cpp
466473
matroska/ebml/ebmlmkattachments.cpp
474+
matroska/ebml/ebmlmkchapters.cpp
467475
matroska/ebml/ebmlmkcues.cpp
468476
matroska/ebml/ebmlmkseekhead.cpp
469477
matroska/ebml/ebmlmksegment.cpp

taglib/matroska/ebml/ebmlelement.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#include "ebmlmksegment.h"
2828
#include "ebmlmktags.h"
2929
#include "ebmlmkattachments.h"
30+
#include "ebmlmkchapters.h"
3031
#include "ebmlmktracks.h"
3132
#include "ebmlstringelement.h"
3233
#include "ebmluintelement.h"
@@ -113,6 +114,19 @@ std::unique_ptr<EBML::Element> EBML::Element::factory(File &file)
113114
RETURN_ELEMENT_FOR_CASE(Id::MkCueCodecState);
114115
RETURN_ELEMENT_FOR_CASE(Id::MkCueReference);
115116
RETURN_ELEMENT_FOR_CASE(Id::MkCueRefTime);
117+
RETURN_ELEMENT_FOR_CASE(Id::MkChapters);
118+
RETURN_ELEMENT_FOR_CASE(Id::MkEditionEntry);
119+
RETURN_ELEMENT_FOR_CASE(Id::MkEditionUID);
120+
RETURN_ELEMENT_FOR_CASE(Id::MkEditionFlagDefault);
121+
RETURN_ELEMENT_FOR_CASE(Id::MkEditionFlagOrdered);
122+
RETURN_ELEMENT_FOR_CASE(Id::MkChapterAtom);
123+
RETURN_ELEMENT_FOR_CASE(Id::MkChapterUID);
124+
RETURN_ELEMENT_FOR_CASE(Id::MkChapterTimeStart);
125+
RETURN_ELEMENT_FOR_CASE(Id::MkChapterTimeEnd);
126+
RETURN_ELEMENT_FOR_CASE(Id::MkChapterFlagHidden);
127+
RETURN_ELEMENT_FOR_CASE(Id::MkChapterDisplay);
128+
RETURN_ELEMENT_FOR_CASE(Id::MkChapString);
129+
RETURN_ELEMENT_FOR_CASE(Id::MkChapLanguage);
116130
}
117131
return std::make_unique<Element>(id, sizeLength, dataSize);
118132
}

taglib/matroska/ebml/ebmlelement.h

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,19 @@ namespace TagLib::EBML {
8686
MkSamplingFrequency = 0xB5,
8787
MkBitDepth = 0x6264,
8888
MkChannels = 0x9F,
89+
MkChapters = 0x1043A770,
90+
MkEditionEntry = 0x45B9,
91+
MkEditionUID = 0x45BC,
92+
MkEditionFlagDefault = 0x45DB,
93+
MkEditionFlagOrdered = 0x45DD,
94+
MkChapterAtom = 0xB6,
95+
MkChapterUID = 0x73C4,
96+
MkChapterTimeStart = 0x91,
97+
MkChapterTimeEnd = 0x92,
98+
MkChapterFlagHidden = 0x98,
99+
MkChapterDisplay = 0x80,
100+
MkChapString = 0x85,
101+
MkChapLanguage = 0x437C,
89102
};
90103

91104
Element(Id id, int sizeLength, offset_t dataSize) :
@@ -134,6 +147,7 @@ namespace TagLib::EBML {
134147
class MkTags;
135148
class MkAttachments;
136149
class MkSeekHead;
150+
class MkChapters;
137151
class MkCues;
138152
class VoidElement;
139153

@@ -196,6 +210,19 @@ namespace TagLib::EBML {
196210
template <> struct GetElementTypeById<Element::Id::MkTitle> { using type = UTF8StringElement; };
197211
template <> struct GetElementTypeById<Element::Id::MkSamplingFrequency> { using type = FloatElement; };
198212
template <> struct GetElementTypeById<Element::Id::MkSeekHead> { using type = MkSeekHead; };
213+
template <> struct GetElementTypeById<Element::Id::MkChapters> { using type = MkChapters; };
214+
template <> struct GetElementTypeById<Element::Id::MkEditionEntry> { using type = MasterElement; };
215+
template <> struct GetElementTypeById<Element::Id::MkEditionUID> { using type = UIntElement; };
216+
template <> struct GetElementTypeById<Element::Id::MkEditionFlagDefault> { using type = UIntElement; };
217+
template <> struct GetElementTypeById<Element::Id::MkEditionFlagOrdered> { using type = UIntElement; };
218+
template <> struct GetElementTypeById<Element::Id::MkChapterAtom> { using type = MasterElement; };
219+
template <> struct GetElementTypeById<Element::Id::MkChapterUID> { using type = UIntElement; };
220+
template <> struct GetElementTypeById<Element::Id::MkChapterTimeStart> { using type = UIntElement; };
221+
template <> struct GetElementTypeById<Element::Id::MkChapterTimeEnd> { using type = UIntElement; };
222+
template <> struct GetElementTypeById<Element::Id::MkChapterFlagHidden> { using type = UIntElement; };
223+
template <> struct GetElementTypeById<Element::Id::MkChapterDisplay> { using type = MasterElement; };
224+
template <> struct GetElementTypeById<Element::Id::MkChapString> { using type = UTF8StringElement; };
225+
template <> struct GetElementTypeById<Element::Id::MkChapLanguage> { using type = Latin1StringElement; };
199226
template <> struct GetElementTypeById<Element::Id::VoidElement> { using type = VoidElement; };
200227

201228
template <Element::Id ID, typename T=typename GetElementTypeById<ID>::type>
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/***************************************************************************
2+
copyright : (C) 2025 by Urs Fleisch
3+
email : ufleisch@users.sourceforge.net
4+
***************************************************************************/
5+
6+
/***************************************************************************
7+
* This library is free software; you can redistribute it and/or modify *
8+
* it under the terms of the GNU Lesser General Public License version *
9+
* 2.1 as published by the Free Software Foundation. *
10+
* *
11+
* This library is distributed in the hope that it will be useful, but *
12+
* WITHOUT ANY WARRANTY; without even the implied warranty of *
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
14+
* Lesser General Public License for more details. *
15+
* *
16+
* You should have received a copy of the GNU Lesser General Public *
17+
* License along with this library; if not, write to the Free Software *
18+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
19+
* 02110-1301 USA *
20+
* *
21+
* Alternatively, this file is available under the Mozilla Public *
22+
* License Version 1.1. You may obtain a copy of the License at *
23+
* http://www.mozilla.org/MPL/ *
24+
***************************************************************************/
25+
26+
#include "ebmlmkchapters.h"
27+
#include "ebmlstringelement.h"
28+
#include "ebmluintelement.h"
29+
#include "matroskachapters.h"
30+
#include "matroskachapteredition.h"
31+
32+
using namespace TagLib;
33+
34+
std::unique_ptr<Matroska::Chapters> EBML::MkChapters::parse()
35+
{
36+
auto chapters = std::make_unique<Matroska::Chapters>();
37+
chapters->setOffset(offset);
38+
chapters->setSize(getSize());
39+
40+
for(const auto &element : elements) {
41+
if(element->getId() != Id::MkEditionEntry)
42+
continue;
43+
44+
List<Matroska::Chapter> editionChapters;
45+
Matroska::ChapterEdition::UID editionUid = 0;
46+
bool editionIsDefault = false;
47+
bool editionIsOrdered = false;
48+
auto edition = element_cast<Id::MkEditionEntry>(element);
49+
for(const auto &editionChild : *edition) {
50+
Id id = editionChild->getId();
51+
if(id == Id::MkEditionUID)
52+
editionUid = element_cast<Id::MkEditionUID>(editionChild)->getValue();
53+
else if(id == Id::MkEditionFlagDefault)
54+
editionIsDefault = element_cast<Id::MkEditionFlagDefault>(editionChild)->getValue() != 0;
55+
else if(id == Id::MkEditionFlagOrdered)
56+
editionIsOrdered = element_cast<Id::MkEditionFlagOrdered>(editionChild)->getValue() != 0;
57+
else if(id == Id::MkChapterAtom) {
58+
Matroska::Chapter::UID chapterUid = 0;
59+
Matroska::Chapter::Time chapterTimeStart = 0;
60+
Matroska::Chapter::Time chapterTimeEnd = 0;
61+
List<Matroska::Chapter::Display> chapterDisplays;
62+
bool chapterHidden = false;
63+
auto chapterAtom = element_cast<Id::MkChapterAtom>(editionChild);
64+
for(const auto &chapterChild : *chapterAtom) {
65+
Id cid = chapterChild->getId();
66+
if(cid == Id::MkChapterUID)
67+
chapterUid = element_cast<Id::MkChapterUID>(chapterChild)->getValue();
68+
else if(cid == Id::MkChapterTimeStart)
69+
chapterTimeStart = element_cast<Id::MkChapterTimeStart>(chapterChild)->getValue();
70+
else if(cid == Id::MkChapterTimeEnd)
71+
chapterTimeEnd = element_cast<Id::MkChapterTimeEnd>(chapterChild)->getValue();
72+
else if(cid == Id::MkChapterFlagHidden)
73+
chapterHidden = element_cast<Id::MkChapterFlagHidden>(chapterChild)->getValue() != 0;
74+
else if(cid == Id::MkChapterDisplay) {
75+
auto display = element_cast<Id::MkChapterDisplay>(chapterChild);
76+
String displayString;
77+
String displayLanguage;
78+
for(const auto &displayChild : *display) {
79+
Id did = displayChild->getId();
80+
if(did == Id::MkChapString)
81+
displayString = element_cast<Id::MkChapString>(displayChild)->getValue();
82+
else if(did == Id::MkChapLanguage)
83+
displayLanguage = element_cast<Id::MkChapLanguage>(displayChild)->getValue();
84+
}
85+
if(!displayString.isEmpty()) {
86+
chapterDisplays.append(Matroska::Chapter::Display(displayString, displayLanguage));
87+
}
88+
}
89+
}
90+
if(chapterUid) {
91+
editionChapters.append(Matroska::Chapter(
92+
chapterTimeStart, chapterTimeEnd, chapterDisplays, chapterUid, chapterHidden));
93+
}
94+
}
95+
}
96+
if(!editionChapters.isEmpty()) {
97+
chapters->addChapterEdition(Matroska::ChapterEdition(
98+
editionChapters, editionIsDefault, editionIsOrdered, editionUid));
99+
}
100+
}
101+
return chapters;
102+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/***************************************************************************
2+
copyright : (C) 2025 by Urs Fleisch
3+
email : ufleisch@users.sourceforge.net
4+
***************************************************************************/
5+
6+
/***************************************************************************
7+
* This library is free software; you can redistribute it and/or modify *
8+
* it under the terms of the GNU Lesser General Public License version *
9+
* 2.1 as published by the Free Software Foundation. *
10+
* *
11+
* This library is distributed in the hope that it will be useful, but *
12+
* WITHOUT ANY WARRANTY; without even the implied warranty of *
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
14+
* Lesser General Public License for more details. *
15+
* *
16+
* You should have received a copy of the GNU Lesser General Public *
17+
* License along with this library; if not, write to the Free Software *
18+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
19+
* 02110-1301 USA *
20+
* *
21+
* Alternatively, this file is available under the Mozilla Public *
22+
* License Version 1.1. You may obtain a copy of the License at *
23+
* http://www.mozilla.org/MPL/ *
24+
***************************************************************************/
25+
26+
#ifndef TAGLIB_EBMLMKCHAPTERS_H
27+
#define TAGLIB_EBMLMKCHAPTERS_H
28+
#ifndef DO_NOT_DOCUMENT
29+
30+
#include "ebmlmasterelement.h"
31+
#include "taglib.h"
32+
33+
namespace TagLib {
34+
namespace Matroska {
35+
class Chapters;
36+
}
37+
namespace EBML {
38+
class MkChapters : public MasterElement
39+
{
40+
public:
41+
MkChapters(int sizeLength, offset_t dataSize, offset_t offset) :
42+
MasterElement(Id::MkChapters, sizeLength, dataSize, offset)
43+
{
44+
}
45+
MkChapters(Id, int sizeLength, offset_t dataSize, offset_t offset) :
46+
MasterElement(Id::MkChapters, sizeLength, dataSize, offset)
47+
{
48+
}
49+
MkChapters() :
50+
MasterElement(Id::MkChapters, 0, 0, 0)
51+
{
52+
}
53+
std::unique_ptr<Matroska::Chapters> parse();
54+
};
55+
}
56+
}
57+
58+
#endif
59+
#endif

taglib/matroska/ebml/ebmlmksegment.cpp

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,11 @@
1919
***************************************************************************/
2020

2121
#include "ebmlmksegment.h"
22-
#include "ebmlmktags.h"
23-
#include "ebmlmkattachments.h"
24-
#include "ebmlmkseekhead.h"
25-
#include "ebmlmkinfo.h"
26-
#include "ebmlmktracks.h"
2722
#include "ebmlutils.h"
2823
#include "matroskafile.h"
2924
#include "matroskatag.h"
3025
#include "matroskaattachments.h"
26+
#include "matroskachapters.h"
3127
#include "matroskacues.h"
3228
#include "matroskaseekhead.h"
3329
#include "matroskasegment.h"
@@ -80,6 +76,11 @@ bool EBML::MkSegment::read(File &file)
8076
if(!attachments->read(file))
8177
return false;
8278
}
79+
else if(id == Id::MkChapters) {
80+
chapters = element_cast<Id::MkChapters>(std::move(element));
81+
if(!chapters->read(file))
82+
return false;
83+
}
8384
else {
8485
if(id == Id::VoidElement
8586
&& seekHead
@@ -103,6 +104,11 @@ std::unique_ptr<Matroska::Attachments> EBML::MkSegment::parseAttachments()
103104
return attachments ? attachments->parse() : nullptr;
104105
}
105106

107+
std::unique_ptr<Matroska::Chapters> EBML::MkSegment::parseChapters()
108+
{
109+
return chapters ? chapters->parse() : nullptr;
110+
}
111+
106112
std::unique_ptr<Matroska::SeekHead> EBML::MkSegment::parseSeekHead()
107113
{
108114
return seekHead ? seekHead->parse(segmentDataOffset()) : nullptr;

0 commit comments

Comments
 (0)