Tag Parser 11.2.0
C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags
matroskacontainer.cpp
Go to the documentation of this file.
2#include "./ebmlid.h"
3#include "./matroskacues.h"
5#include "./matroskaid.h"
7
8#include "../backuphelper.h"
9#include "../exceptions.h"
10#include "../mediafileinfo.h"
11
12#include "resources/config.h"
13
14#include <c++utilities/conversion/stringbuilder.h>
15#include <c++utilities/conversion/stringconversion.h>
16
17#include <unistd.h>
18
19#include <chrono>
20#include <functional>
21#include <initializer_list>
22#include <limits>
23#include <memory>
24#include <random>
25#include <unordered_set>
26
27using namespace std;
28using namespace std::placeholders;
29using namespace CppUtilities;
30
31namespace TagParser {
32
38std::uint64_t MatroskaContainer::m_maxFullParseSize = 0x3200000; // FIXME v11: move to MediaFileInfo
39
43MatroskaContainer::MatroskaContainer(MediaFileInfo &fileInfo, std::uint64_t startOffset)
45 , m_maxIdLength(4)
46 , m_maxSizeLength(8)
47 , m_segmentCount(0)
48{
49 m_version = 1;
50 m_readVersion = 1;
51 m_doctype = "matroska";
54}
55
57{
58}
59
61{
63 m_maxIdLength = 4;
64 m_maxSizeLength = 8;
65 m_version = 1;
66 m_readVersion = 1;
67 m_doctype = "matroska";
70 m_tracksElements.clear();
71 m_segmentInfoElements.clear();
72 m_tagsElements.clear();
73 m_chaptersElements.clear();
74 m_attachmentsElements.clear();
75 m_seekInfos.clear();
76 m_editionEntries.clear();
77 m_attachments.clear();
78 m_segmentCount = 0;
79}
80
86{
87 static const string context("validating Matroska file index (cues)");
88 bool cuesElementsFound = false;
89 if (m_firstElement) {
90 unordered_set<EbmlElement::IdentifierType> ids;
91 bool cueTimeFound = false, cueTrackPositionsFound = false;
92 unique_ptr<EbmlElement> clusterElement;
93 std::uint64_t pos, prevClusterSize = 0, currentOffset = 0;
94 // iterate through all segments
95 for (EbmlElement *segmentElement = m_firstElement->siblingById(MatroskaIds::Segment, diag); segmentElement;
96 segmentElement = segmentElement->siblingById(MatroskaIds::Segment, diag)) {
97 segmentElement->parse(diag);
98 // iterate through all child elements of the segment (only "Cues"- and "Cluster"-elements are relevant for this method)
99 for (EbmlElement *segmentChildElement = segmentElement->firstChild(); segmentChildElement;
100 segmentChildElement = segmentChildElement->nextSibling()) {
101 progress.stopIfAborted();
102 segmentChildElement->parse(diag);
103 switch (segmentChildElement->id()) {
104 case EbmlIds::Void:
105 case EbmlIds::Crc32:
106 break;
108 cuesElementsFound = true;
109 // parse children of "Cues"-element ("CuePoint"-elements)
110 for (EbmlElement *cuePointElement = segmentChildElement->firstChild(); cuePointElement;
111 cuePointElement = cuePointElement->nextSibling()) {
112 progress.stopIfAborted();
113 cuePointElement->parse(diag);
114 cueTimeFound = cueTrackPositionsFound = false; // to validate quantity of these elements
115 switch (cuePointElement->id()) {
116 case EbmlIds::Void:
117 case EbmlIds::Crc32:
118 break;
120 // parse children of "CuePoint"-element
121 for (EbmlElement *cuePointChildElement = cuePointElement->firstChild(); cuePointChildElement;
122 cuePointChildElement = cuePointChildElement->nextSibling()) {
123 cuePointChildElement->parse(diag);
124 switch (cuePointChildElement->id()) {
126 // validate uniqueness
127 if (cueTimeFound) {
128 diag.emplace_back(
129 DiagLevel::Warning, "\"CuePoint\"-element contains multiple \"CueTime\" elements.", context);
130 } else {
131 cueTimeFound = true;
132 }
133 break;
135 cueTrackPositionsFound = true;
136 ids.clear();
137 clusterElement.reset();
138 for (EbmlElement *subElement = cuePointChildElement->firstChild(); subElement;
139 subElement = subElement->nextSibling()) {
140 subElement->parse(diag);
141 switch (subElement->id()) {
148 // validate uniqueness
149 if (ids.count(subElement->id())) {
150 diag.emplace_back(DiagLevel::Warning,
151 "\"CueTrackPositions\"-element contains multiple \"" % subElement->idToString() + "\" elements.",
152 context);
153 } else {
154 ids.insert(subElement->id());
155 }
156 break;
157 case EbmlIds::Crc32:
158 case EbmlIds::Void:
160 break;
161 default:
162 diag.emplace_back(DiagLevel::Warning,
163 "\"CueTrackPositions\"-element contains unknown element \"" % subElement->idToString() + "\".",
164 context);
165 }
166 switch (subElement->id()) {
167 case EbmlIds::Void:
168 case EbmlIds::Crc32:
170 break;
172 // validate "Cluster" position denoted by "CueClusterPosition"-element
173 clusterElement = make_unique<EbmlElement>(
174 *this, segmentElement->dataOffset() + subElement->readUInteger() - currentOffset);
175 try {
176 clusterElement->parse(diag);
177 if (clusterElement->id() != MatroskaIds::Cluster) {
178 diag.emplace_back(DiagLevel::Critical,
179 "\"CueClusterPosition\" element at " % numberToString(subElement->startOffset())
180 + " does not point to \"Cluster\"-element (points to "
181 + numberToString(clusterElement->startOffset()) + ").",
182 context);
183 }
184 } catch (const Failure &) {
185 }
186 break;
188 // read "Block" position denoted by "CueRelativePosition"-element (validate later since the "Cluster"-element is needed to validate)
189 pos = subElement->readUInteger();
190 break;
192 break;
194 break;
196 break;
198 break;
199 default:;
200 }
201 }
202 // validate existence of mandatory elements
203 if (!ids.count(MatroskaIds::CueTrack)) {
204 diag.emplace_back(DiagLevel::Warning,
205 "\"CueTrackPositions\"-element does not contain mandatory element \"CueTrack\".", context);
206 }
207 if (!clusterElement) {
208 diag.emplace_back(DiagLevel::Warning,
209 "\"CueTrackPositions\"-element does not contain mandatory element \"CueClusterPosition\".", context);
210 } else if (ids.count(MatroskaIds::CueRelativePosition)) {
211 // validate "Block" position denoted by "CueRelativePosition"-element
212 EbmlElement referenceElement(*this, clusterElement->dataOffset() + pos);
213 try {
214 referenceElement.parse(diag);
215 switch (referenceElement.id()) {
219 break;
220 default:
221 diag.emplace_back(DiagLevel::Critical,
222 "\"CueRelativePosition\" element does not point to \"Block\"-, \"BlockGroup\", or "
223 "\"SimpleBlock\"-element (points to "
224 % numberToString(referenceElement.startOffset())
225 + ").",
226 context);
227 }
228 } catch (const Failure &) {
229 }
230 }
231 break;
232 case EbmlIds::Crc32:
233 case EbmlIds::Void:
234 break;
235 default:
236 diag.emplace_back(DiagLevel::Warning,
237 "\"CuePoint\"-element contains unknown element \"" % cuePointElement->idToString() + "\".", context);
238 }
239 }
240 // validate existence of mandatory elements
241 if (!cueTimeFound) {
242 diag.emplace_back(
243 DiagLevel::Warning, "\"CuePoint\"-element does not contain mandatory element \"CueTime\".", context);
244 }
245 if (!cueTrackPositionsFound) {
246 diag.emplace_back(
247 DiagLevel::Warning, "\"CuePoint\"-element does not contain mandatory element \"CueClusterPosition\".", context);
248 }
249 break;
250 default:;
251 }
252 }
253 break;
255 // parse children of "Cluster"-element
256 for (EbmlElement *clusterElementChild = segmentChildElement->firstChild(); clusterElementChild;
257 clusterElementChild = clusterElementChild->nextSibling()) {
258 clusterElementChild->parse(diag);
259 switch (clusterElementChild->id()) {
260 case EbmlIds::Void:
261 case EbmlIds::Crc32:
262 break;
264 // validate position
265 if ((pos = clusterElementChild->readUInteger()) > 0
266 && (segmentChildElement->startOffset() - segmentElement->dataOffset() + currentOffset) != pos) {
267 diag.emplace_back(DiagLevel::Critical,
268 argsToString("\"Position\"-element at ", clusterElementChild->startOffset(), " points to ", pos,
269 " which is not the offset of the containing \"Cluster\"-element."),
270 context);
271 }
272 break;
274 // validate prev size
275 if ((pos = clusterElementChild->readUInteger()) != prevClusterSize) {
276 diag.emplace_back(DiagLevel::Critical,
277 argsToString("\"PrevSize\"-element at ", clusterElementChild->startOffset(), " should be ", prevClusterSize,
278 " but is ", pos, "."),
279 context);
280 }
281 break;
282 default:;
283 }
284 }
285 prevClusterSize = segmentChildElement->totalSize();
286 break;
287 default:;
288 }
289 }
290 currentOffset += segmentElement->totalSize();
291 }
292 }
293 // add a warning when no index could be found
294 if (!cuesElementsFound) {
295 diag.emplace_back(DiagLevel::Information, "No \"Cues\"-elements (index) found.", context);
296 }
297}
298
302bool sameOffset(std::uint64_t offset, const EbmlElement *element)
303{
304 return element->startOffset() == offset;
305}
306
311inline bool excludesOffset(const vector<EbmlElement *> &elements, std::uint64_t offset)
312{
313 return find_if(elements.cbegin(), elements.cend(), std::bind(sameOffset, offset, _1)) == elements.cend();
314}
315
317{
318 for (const auto &entry : m_editionEntries) {
319 const auto &chapters = entry->chapters();
320 if (index < chapters.size()) {
321 return chapters[index].get();
322 } else {
323 index -= chapters.size();
324 }
325 }
326 return nullptr;
327}
328
330{
331 size_t count = 0;
332 for (const auto &entry : m_editionEntries) {
333 count += entry->chapters().size();
334 }
335 return count;
336}
337
339{
340 // generate unique ID
341 static const auto randomEngine(
342 default_random_engine(static_cast<default_random_engine::result_type>(chrono::system_clock::now().time_since_epoch().count())));
343 std::uint64_t attachmentId;
344 auto dice(bind(uniform_int_distribution<decltype(attachmentId)>(), randomEngine));
345 std::uint8_t tries = 0;
346generateRandomId:
347 attachmentId = dice();
348 if (tries < 0xFF) {
349 for (const auto &attachment : m_attachments) {
350 if (attachmentId == attachment->id()) {
351 ++tries;
352 goto generateRandomId;
353 }
354 }
355 }
356 // create new attachment, set ID
357 m_attachments.emplace_back(make_unique<MatroskaAttachment>());
358 auto &attachment = m_attachments.back();
359 attachment->setId(attachmentId);
360 return attachment.get();
361}
362
368{
369 if (!m_firstElement || m_segmentCount != 1) {
371 }
372 const auto *const segmentElement = m_firstElement->siblingByIdIncludingThis(MatroskaIds::Segment, diag);
373 if (!segmentElement) {
375 }
376 for (const EbmlElement *childElement = segmentElement->firstChild(); childElement; childElement = childElement->nextSibling()) {
377 if (childElement->id() == elementId) {
379 } else if (childElement->id() == MatroskaIds::Cluster) {
380 for (const auto &seekInfo : m_seekInfos) {
381 for (const auto &info : seekInfo->info()) {
382 if (info.first == elementId) {
384 }
385 }
386 }
388 }
389 }
391}
392
394{
396}
397
399{
401}
402
404{
405 CPP_UTILITIES_UNUSED(progress)
406
407 static const string context("parsing header of Matroska container");
408 // reset old results
409 m_firstElement = make_unique<EbmlElement>(*this, startOffset());
410 m_additionalElements.clear();
411 m_tracksElements.clear();
412 m_segmentInfoElements.clear();
413 m_tagsElements.clear();
414 m_seekInfos.clear();
415 m_segmentCount = 0;
416 std::uint64_t currentOffset = 0;
417 vector<MatroskaSeekInfo>::difference_type seekInfosIndex = 0;
418
419 // loop through all top level elements
420 for (EbmlElement *topLevelElement = m_firstElement.get(); topLevelElement; topLevelElement = topLevelElement->nextSibling()) {
421 try {
422 topLevelElement->parse(diag);
423 switch (topLevelElement->id()) {
424 case EbmlIds::Header:
425 for (EbmlElement *subElement = topLevelElement->firstChild(); subElement; subElement = subElement->nextSibling()) {
426 try {
427 subElement->parse(diag);
428 switch (subElement->id()) {
429 case EbmlIds::Version:
430 m_version = subElement->readUInteger();
431 break;
433 m_readVersion = subElement->readUInteger();
434 break;
435 case EbmlIds::DocType:
436 m_doctype = subElement->readString();
437 break;
439 m_doctypeVersion = subElement->readUInteger();
440 break;
442 m_doctypeReadVersion = subElement->readUInteger();
443 break;
445 m_maxIdLength = subElement->readUInteger();
446 if (m_maxIdLength > EbmlElement::maximumIdLengthSupported()) {
447 diag.emplace_back(DiagLevel::Critical,
448 argsToString("Maximum EBML element ID length greater than ", EbmlElement::maximumIdLengthSupported(),
449 " bytes is not supported."),
450 context);
451 throw InvalidDataException();
452 }
453 break;
455 m_maxSizeLength = subElement->readUInteger();
456 if (m_maxSizeLength > EbmlElement::maximumSizeLengthSupported()) {
457 diag.emplace_back(DiagLevel::Critical,
458 argsToString("Maximum EBML element size length greater than ", EbmlElement::maximumSizeLengthSupported(),
459 " bytes is not supported."),
460 context);
461 throw InvalidDataException();
462 }
463 break;
464 }
465 } catch (const Failure &) {
466 diag.emplace_back(DiagLevel::Critical, "Unable to parse all children of EBML header.", context);
467 break;
468 }
469 }
470 break;
472 ++m_segmentCount;
473 for (EbmlElement *subElement = topLevelElement->firstChild(); subElement; subElement = subElement->nextSibling()) {
474 try {
475 subElement->parse(diag);
476 switch (subElement->id()) {
478 m_seekInfos.emplace_back(make_unique<MatroskaSeekInfo>());
479 m_seekInfos.back()->parse(subElement, diag);
480 break;
482 if (excludesOffset(m_tracksElements, subElement->startOffset())) {
483 m_tracksElements.push_back(subElement);
484 }
485 break;
487 if (excludesOffset(m_segmentInfoElements, subElement->startOffset())) {
488 m_segmentInfoElements.push_back(subElement);
489 }
490 break;
492 if (excludesOffset(m_tagsElements, subElement->startOffset())) {
493 m_tagsElements.push_back(subElement);
494 }
495 break;
497 if (excludesOffset(m_chaptersElements, subElement->startOffset())) {
498 m_chaptersElements.push_back(subElement);
499 }
500 break;
502 if (excludesOffset(m_attachmentsElements, subElement->startOffset())) {
503 m_attachmentsElements.push_back(subElement);
504 }
505 break;
507 // stop as soon as the first cluster has been reached if all relevant information has been gathered
508 // -> take elements from seek tables within this segment into account
509 for (auto i = m_seekInfos.cbegin() + seekInfosIndex, end = m_seekInfos.cend(); i != end; ++i, ++seekInfosIndex) {
510 for (const auto &infoPair : (*i)->info()) {
511 std::uint64_t offset = currentOffset + topLevelElement->dataOffset() + infoPair.second;
512 if (offset >= fileInfo().size()) {
513 diag.emplace_back(DiagLevel::Critical,
514 argsToString("Offset (", offset, ") denoted by \"SeekHead\" element is invalid."), context);
515 } else {
516 auto element = make_unique<EbmlElement>(*this, offset);
517 try {
518 element->parse(diag);
519 if (element->id() != infoPair.first) {
520 diag.emplace_back(DiagLevel::Critical,
521 argsToString("ID of element ", element->idToString(), " at ", offset,
522 " does not match the ID denoted in the \"SeekHead\" element (0x",
523 numberToString(infoPair.first, 16u), ")."),
524 context);
525 }
526 switch (element->id()) {
528 if (excludesOffset(m_segmentInfoElements, offset)) {
529 m_additionalElements.emplace_back(move(element));
530 m_segmentInfoElements.emplace_back(m_additionalElements.back().get());
531 }
532 break;
534 if (excludesOffset(m_tracksElements, offset)) {
535 m_additionalElements.emplace_back(move(element));
536 m_tracksElements.emplace_back(m_additionalElements.back().get());
537 }
538 break;
540 if (excludesOffset(m_tagsElements, offset)) {
541 m_additionalElements.emplace_back(move(element));
542 m_tagsElements.emplace_back(m_additionalElements.back().get());
543 }
544 break;
546 if (excludesOffset(m_chaptersElements, offset)) {
547 m_additionalElements.emplace_back(move(element));
548 m_chaptersElements.emplace_back(m_additionalElements.back().get());
549 }
550 break;
552 if (excludesOffset(m_attachmentsElements, offset)) {
553 m_additionalElements.emplace_back(move(element));
554 m_attachmentsElements.emplace_back(m_additionalElements.back().get());
555 }
556 break;
557 default:;
558 }
559 } catch (const Failure &) {
560 diag.emplace_back(DiagLevel::Critical,
561 argsToString("Can not parse element at ", offset, " (denoted using \"SeekHead\" element)."), context);
562 }
563 }
564 }
565 }
566 // -> stop if tracks and tags have been found or the file exceeds the max. size to fully process
567 if (((!m_tracksElements.empty() && !m_tagsElements.empty()) || fileInfo().size() > m_maxFullParseSize)
568 && !m_segmentInfoElements.empty()) {
569 goto finish;
570 }
571 break;
572 }
573 } catch (const Failure &) {
574 diag.emplace_back(DiagLevel::Critical, "Unable to parse all children of \"Segment\"-element.", context);
575 break;
576 }
577 }
578 currentOffset += topLevelElement->totalSize();
579 break;
580 default:;
581 }
582 } catch (const Failure &) {
583 diag.emplace_back(
584 DiagLevel::Critical, argsToString("Unable to parse top-level element at ", topLevelElement->startOffset(), '.'), context);
585 break;
586 }
587 }
588
589 // finally parse the "Info"-element and fetch "EditionEntry"-elements
590finish:
591 try {
592 parseSegmentInfo(diag);
593 } catch (const Failure &) {
594 diag.emplace_back(DiagLevel::Critical, "Unable to parse EBML (segment) \"Info\"-element.", context);
595 }
596}
597
607void MatroskaContainer::parseSegmentInfo(Diagnostics &diag)
608{
609 if (m_segmentInfoElements.empty()) {
610 throw NoDataFoundException();
611 }
612 m_duration = TimeSpan();
613 for (EbmlElement *element : m_segmentInfoElements) {
614 element->parse(diag);
615 EbmlElement *subElement = element->firstChild();
616 double rawDuration = 0.0;
617 std::uint64_t timeScale = 1000000;
618 bool hasTitle = false;
619 while (subElement) {
620 subElement->parse(diag);
621 switch (subElement->id()) {
623 m_titles.emplace_back(subElement->readString());
624 hasTitle = true;
625 break;
627 rawDuration = subElement->readFloat();
628 break;
630 timeScale = subElement->readUInteger();
631 break;
632 }
633 subElement = subElement->nextSibling();
634 }
635 // add empty string as title for segment if no
636 // "Title"-element has been specified
637 if (!hasTitle) {
638 m_titles.emplace_back();
639 }
640 if (rawDuration > 0.0) {
641 m_duration += TimeSpan::fromSeconds(rawDuration * static_cast<double>(timeScale) / 1000000000.0);
642 }
643 }
644}
645
651void MatroskaContainer::readTrackStatisticsFromTags(Diagnostics &diag)
652{
653 if (tracks().empty() || tags().empty()) {
654 return;
655 }
656 for (const auto &track : tracks()) {
658 }
659}
660
662{
663 CPP_UTILITIES_UNUSED(progress)
664
665 static const string context("parsing tags of Matroska container");
666 auto flags = MatroskaTagFlags::None;
669 }
670 for (EbmlElement *const element : m_tagsElements) {
671 try {
672 element->parse(diag);
673 for (EbmlElement *subElement = element->firstChild(); subElement; subElement = subElement->nextSibling()) {
674 subElement->parse(diag);
675 switch (subElement->id()) {
676 case MatroskaIds::Tag:
677 m_tags.emplace_back(make_unique<MatroskaTag>());
678 try {
679 m_tags.back()->parse2(*subElement, flags, diag);
680 } catch (const NoDataFoundException &) {
681 m_tags.pop_back();
682 } catch (const Failure &) {
683 diag.emplace_back(DiagLevel::Critical, argsToString("Unable to parse tag ", m_tags.size(), '.'), context);
684 }
685 break;
686 case EbmlIds::Crc32:
687 case EbmlIds::Void:
688 break;
689 default:
690 diag.emplace_back(DiagLevel::Warning, "\"Tags\"-element contains unknown child. It will be ignored.", context);
691 }
692 }
693 } catch (const Failure &) {
694 diag.emplace_back(DiagLevel::Critical, "Element structure seems to be invalid.", context);
695 readTrackStatisticsFromTags(diag);
696 throw;
697 }
698 }
699 readTrackStatisticsFromTags(diag);
700}
701
703{
704 static const string context("parsing tracks of Matroska container");
705 for (EbmlElement *element : m_tracksElements) {
706 try {
707 element->parse(diag);
708 for (EbmlElement *subElement = element->firstChild(); subElement; subElement = subElement->nextSibling()) {
709 subElement->parse(diag);
710 switch (subElement->id()) {
712 m_tracks.emplace_back(make_unique<MatroskaTrack>(*subElement));
713 try {
714 m_tracks.back()->parseHeader(diag, progress);
715 } catch (const NoDataFoundException &) {
716 m_tracks.pop_back();
717 } catch (const Failure &) {
718 diag.emplace_back(DiagLevel::Critical, argsToString("Unable to parse track ", m_tracks.size(), '.'), context);
719 }
720 break;
721 case EbmlIds::Crc32:
722 case EbmlIds::Void:
723 break;
724 default:
725 diag.emplace_back(DiagLevel::Warning,
726 "\"Tracks\"-element contains unknown child element \"" % subElement->idToString() + "\". It will be ignored.", context);
727 }
728 }
729 } catch (const Failure &) {
730 diag.emplace_back(DiagLevel::Critical, "Element structure seems to be invalid.", context);
731 readTrackStatisticsFromTags(diag);
732 throw;
733 }
734 }
735 readTrackStatisticsFromTags(diag);
736}
737
739{
740 static const string context("parsing editions/chapters of Matroska container");
741 for (EbmlElement *element : m_chaptersElements) {
742 try {
743 element->parse(diag);
744 for (EbmlElement *subElement = element->firstChild(); subElement; subElement = subElement->nextSibling()) {
745 subElement->parse(diag);
746 switch (subElement->id()) {
748 m_editionEntries.emplace_back(make_unique<MatroskaEditionEntry>(subElement));
749 try {
750 m_editionEntries.back()->parseNested(diag, progress);
751 } catch (const NoDataFoundException &) {
752 m_editionEntries.pop_back();
753 } catch (const Failure &) {
754 diag.emplace_back(DiagLevel::Critical, argsToString("Unable to parse edition entry ", m_editionEntries.size(), '.'), context);
755 }
756 break;
757 case EbmlIds::Crc32:
758 case EbmlIds::Void:
759 break;
760 default:
761 diag.emplace_back(DiagLevel::Warning,
762 "\"Chapters\"-element contains unknown child element \"" % subElement->idToString() + "\". It will be ignored.", context);
763 }
764 }
765 } catch (const Failure &) {
766 diag.emplace_back(DiagLevel::Critical, "Element structure seems to be invalid.", context);
767 throw;
768 }
769 }
770}
771
773{
774 CPP_UTILITIES_UNUSED(progress)
775
776 static const string context("parsing attachments of Matroska container");
777 for (EbmlElement *element : m_attachmentsElements) {
778 try {
779 element->parse(diag);
780 for (EbmlElement *subElement = element->firstChild(); subElement; subElement = subElement->nextSibling()) {
781 subElement->parse(diag);
782 switch (subElement->id()) {
784 m_attachments.emplace_back(make_unique<MatroskaAttachment>());
785 try {
786 m_attachments.back()->parse(subElement, diag);
787 } catch (const NoDataFoundException &) {
788 m_attachments.pop_back();
789 } catch (const Failure &) {
790 diag.emplace_back(DiagLevel::Critical, argsToString("Unable to parse attached file ", m_attachments.size(), '.'), context);
791 }
792 break;
793 case EbmlIds::Crc32:
794 case EbmlIds::Void:
795 break;
796 default:
797 diag.emplace_back(DiagLevel::Warning,
798 "\"Attachments\"-element contains unknown child element \"" % subElement->idToString() + "\". It will be ignored.", context);
799 }
800 }
801 } catch (const Failure &) {
802 diag.emplace_back(DiagLevel::Critical, "Element structure seems to be invalid.", context);
803 throw;
804 }
805 }
806}
807
812 : hasCrc32(false)
813 , cuesElement(nullptr)
814 , infoDataSize(0)
815 , firstClusterElement(nullptr)
817 , startOffset(0)
818 , newPadding(0)
819 , totalDataSize(0)
820 , totalSize(0)
821 , newDataOffset(0)
823 {
824 }
825
835 std::uint64_t infoDataSize;
837 vector<std::uint64_t> clusterSizes;
841 std::uint64_t clusterEndOffset;
843 std::uint64_t startOffset;
845 std::uint64_t newPadding;
847 std::uint64_t totalDataSize;
849 std::uint64_t totalSize;
851 std::uint64_t newDataOffset;
854};
855
857{
858 static const string context("making Matroska container");
859 progress.updateStep("Calculating element sizes ...");
860
861 // basic validation of original file
862 if (!isHeaderParsed()) {
863 diag.emplace_back(DiagLevel::Critical, "The header has not been parsed yet.", context);
864 throw InvalidDataException();
865 }
866 switch (fileInfo().attachmentsParsingStatus()) {
869 break;
870 default:
871 diag.emplace_back(DiagLevel::Critical, "Attachments have to be parsed without critical errors before changes can be applied.", context);
872 throw InvalidDataException();
873 }
874
875 // define variables for parsing the elements of the original file
876 EbmlElement *level0Element = firstElement();
877 if (!level0Element) {
878 diag.emplace_back(DiagLevel::Critical, "No EBML elements could be found.", context);
879 throw InvalidDataException();
880 }
881 EbmlElement *level1Element, *level2Element;
882
883 // define variables needed for precalculation of "Tags"- and "Attachments"-element
884 vector<MatroskaTagMaker> tagMaker;
885 tagMaker.reserve(tags().size());
886 std::uint64_t tagElementsSize = 0;
887 std::uint64_t tagsSize;
888 vector<MatroskaAttachmentMaker> attachmentMaker;
889 attachmentMaker.reserve(m_attachments.size());
890 std::uint64_t attachedFileElementsSize = 0;
891 std::uint64_t attachmentsSize;
892 vector<MatroskaTrackHeaderMaker> trackHeaderMaker;
893 trackHeaderMaker.reserve(tracks().size());
894 std::uint64_t trackHeaderElementsSize = 0;
895 std::uint64_t trackHeaderSize;
896
897 // define variables to store sizes, offsets and other information required to make a header and "Segment"-elements
898 // current segment index
899 unsigned int segmentIndex = 0;
900 // segment specific data
901 vector<SegmentData> segmentData;
902 // offset of the segment which is currently written / offset of "Cues"-element in segment
903 std::uint64_t offset;
904 // current total offset (including EBML header)
905 std::uint64_t totalOffset;
906 // current write offset (used to calculate positions)
907 std::uint64_t currentPosition = 0;
908 // holds the offsets of all CRC-32 elements and the length of the enclosing block
909 vector<tuple<std::uint64_t, std::uint64_t>> crc32Offsets;
910 // size length used to make size denotations
911 std::uint8_t sizeLength;
912 // sizes and offsets for cluster calculation
913 std::uint64_t clusterSize, clusterReadSize, clusterReadOffset;
914
915 // define variables needed to manage file layout
916 // -> use the preferred tag position by default (might be changed later if not forced)
917 ElementPosition newTagPos = fileInfo().tagPosition();
918 // -> current tag position (determined later)
920 // -> use the preferred cue position by default (might be changed later if not forced)
921 ElementPosition newCuesPos = fileInfo().indexPosition();
922 // --> current cue position (determined later)
923 ElementPosition currentCuesPos = ElementPosition::Keep;
924 // -> index of the last segment
925 unsigned int lastSegmentIndex = numeric_limits<unsigned int>::max();
926 // -> holds new padding
927 std::uint64_t newPadding;
928 // -> whether rewrite is required (always required when forced to rewrite)
929 bool rewriteRequired = fileInfo().isForcingRewrite() || !fileInfo().saveFilePath().empty();
930
931 // calculate EBML header size
932 // -> sub element ID sizes
933 std::uint64_t ebmlHeaderDataSize = 2 * 7;
934 // -> content and size denotation length of numeric sub elements
935 for (auto headerValue :
936 initializer_list<std::uint64_t>{ m_version, m_readVersion, m_maxIdLength, m_maxSizeLength, m_doctypeVersion, m_doctypeReadVersion }) {
937 ebmlHeaderDataSize += sizeLength = EbmlElement::calculateUIntegerLength(headerValue);
938 ebmlHeaderDataSize += EbmlElement::calculateSizeDenotationLength(sizeLength);
939 }
940 // -> content and size denotation length of string sub elements
941 ebmlHeaderDataSize += m_doctype.size();
942 ebmlHeaderDataSize += EbmlElement::calculateSizeDenotationLength(m_doctype.size());
943 const std::uint64_t ebmlHeaderSize = 4 + EbmlElement::calculateSizeDenotationLength(ebmlHeaderDataSize) + ebmlHeaderDataSize;
944
945 // calculate size of "WritingLib"-element
946 constexpr std::string_view muxingAppName = APP_NAME " v" APP_VERSION;
947 constexpr std::uint64_t muxingAppElementTotalSize = 2 + 1 + muxingAppName.size();
948
949 // calculate size of "WritingApp"-element
950 const std::uint64_t writingAppElementDataSize
951 = fileInfo().writingApplication().empty() ? muxingAppName.size() : fileInfo().writingApplication().size();
952 const std::uint64_t writingAppElementTotalSize = 2 + 1 + writingAppElementDataSize;
953
954 try {
955 // calculate size of "Tags"-element
956 for (auto &tag : tags()) {
957 try {
958 const auto &maker = tagMaker.emplace_back(tag->prepareMaking(diag));
959 if (maker.requiredSize() > 3) {
960 // a tag of 3 bytes size is empty and can be skipped
961 tagElementsSize += maker.requiredSize();
962 }
963 } catch (const Failure &) {
964 }
965 }
966 tagsSize = tagElementsSize ? 4 + EbmlElement::calculateSizeDenotationLength(tagElementsSize) + tagElementsSize : 0;
967
968 // calculate size of "Attachments"-element
969 for (auto &attachment : m_attachments) {
970 if (!attachment->isIgnored()) {
971 try {
972 const auto &maker = attachmentMaker.emplace_back(attachment->prepareMaking(diag));
973 if (maker.requiredSize() > 3) {
974 // an attachment of 3 bytes size is empty and can be skipped
975 attachedFileElementsSize += maker.requiredSize();
976 }
977 } catch (const Failure &) {
978 }
979 }
980 }
981 attachmentsSize
982 = attachedFileElementsSize ? 4 + EbmlElement::calculateSizeDenotationLength(attachedFileElementsSize) + attachedFileElementsSize : 0;
983
984 // calculate size of "Tracks"-element
985 for (auto &track : tracks()) {
986 try {
987 const auto &maker = trackHeaderMaker.emplace_back(track->prepareMakingHeader(diag));
988 if (maker.requiredSize() > 3) {
989 // a track header of 3 bytes size is empty and can be skipped
990 trackHeaderElementsSize += maker.requiredSize();
991 }
992 } catch (const Failure &) {
993 }
994 }
995 trackHeaderSize
996 = trackHeaderElementsSize ? 4 + EbmlElement::calculateSizeDenotationLength(trackHeaderElementsSize) + trackHeaderElementsSize : 0;
997
998 // inspect layout of original file
999 // - number of segments
1000 // - position of tags relative to the media data
1001 try {
1002 for (bool firstClusterFound = false, firstTagFound = false; level0Element; level0Element = level0Element->nextSibling()) {
1003 level0Element->parse(diag);
1004 switch (level0Element->id()) {
1006 ++lastSegmentIndex;
1007 for (level1Element = level0Element->firstChild(); level1Element && !firstClusterFound && !firstTagFound;
1008 level1Element = level1Element->nextSibling()) {
1009 level1Element->parse(diag);
1010 switch (level1Element->id()) {
1011 case MatroskaIds::Tags:
1013 firstTagFound = true;
1014 break;
1016 firstClusterFound = true;
1017 }
1018 }
1019 if (firstTagFound) {
1020 currentTagPos = ElementPosition::BeforeData;
1021 } else if (firstClusterFound) {
1022 currentTagPos = ElementPosition::AfterData;
1023 }
1024 }
1025 }
1026
1027 // now the number of segments is known -> allocate segment specific data
1028 segmentData.resize(lastSegmentIndex + 1);
1029
1030 // now the current tag/cue position might be known
1031 if (newTagPos == ElementPosition::Keep) {
1032 if ((newTagPos = currentTagPos) == ElementPosition::Keep) {
1033 newTagPos = ElementPosition::BeforeData;
1034 }
1035 }
1036
1037 } catch (const Failure &) {
1038 diag.emplace_back(DiagLevel::Critical,
1039 "Unable to parse content in top-level element at " % numberToString(level0Element->startOffset()) + " of original file.", context);
1040 throw;
1041 }
1042
1043 progress.nextStepOrStop("Calculating offsets of elements before cluster ...");
1044 calculateSegmentData:
1045 // define variables to store sizes, offsets and other information required to make a header and "Segment"-elements
1046 // -> current "pretent" write offset
1047 std::uint64_t currentOffset = ebmlHeaderSize;
1048 // -> current read offset (used to calculate positions)
1049 std::uint64_t readOffset = 0;
1050 // -> index of current element during iteration
1051 unsigned int index;
1052
1053 // if rewriting is required always use the preferred tag/cue position
1054 if (rewriteRequired) {
1055 newTagPos = fileInfo().tagPosition();
1056 if (newTagPos == ElementPosition::Keep) {
1057 if ((newTagPos = currentTagPos) == ElementPosition::Keep) {
1058 newTagPos = ElementPosition::BeforeData;
1059 }
1060 }
1061 newCuesPos = fileInfo().indexPosition();
1062 }
1063
1064 // calculate sizes and other information required to make segments
1065 for (level0Element = firstElement(), currentPosition = newPadding = segmentIndex = 0; level0Element;
1066 level0Element = level0Element->nextSibling()) {
1067 switch (level0Element->id()) {
1068 case EbmlIds::Header:
1069 // header size has already been calculated
1070 break;
1071
1072 case EbmlIds::Void:
1073 case EbmlIds::Crc32:
1074 // level 0 "Void"- and "Checksum"-elements are omitted
1075 break;
1076
1077 case MatroskaIds::Segment: {
1078 // get reference to the current segment data instance
1079 SegmentData &segment = segmentData[segmentIndex];
1080
1081 // parse original "Cues"-element (if present)
1082 if (!segment.cuesElement && (segment.cuesElement = level0Element->childById(MatroskaIds::Cues, diag))) {
1083 segment.cuesUpdater.parse(segment.cuesElement, diag);
1084 }
1085
1086 // get first "Cluster"-element
1087 if (!segment.firstClusterElement) {
1088 segment.firstClusterElement = level0Element->childById(MatroskaIds::Cluster, diag);
1089 }
1090
1091 // determine current/new cue position
1092 if (segment.cuesElement && segment.firstClusterElement) {
1093 currentCuesPos = segment.cuesElement->startOffset() < segment.firstClusterElement->startOffset() ? ElementPosition::BeforeData
1095 if (newCuesPos == ElementPosition::Keep) {
1096 newCuesPos = currentCuesPos;
1097 }
1098 } else if (newCuesPos == ElementPosition::Keep) {
1099 newCuesPos = ElementPosition::BeforeData;
1100 }
1101
1102 // set start offset of the segment in the new file
1103 segment.startOffset = currentOffset;
1104
1105 // check whether the segment has a CRC-32 element
1106 segment.hasCrc32 = level0Element->firstChild() && level0Element->firstChild()->id() == EbmlIds::Crc32;
1107
1108 // precalculate the size of the segment
1109 calculateSegmentSize:
1110
1111 // pretent writing "CRC-32"-element (which either present and 6 byte long or omitted)
1112 segment.totalDataSize = segment.hasCrc32 ? 6 : 0;
1113
1114 // pretend writing "SeekHead"-element
1115 segment.totalDataSize += segment.seekInfo.actualSize();
1116
1117 // pretend writing "SegmentInfo"-element
1118 for (level1Element = level0Element->childById(MatroskaIds::SegmentInfo, diag), index = 0; level1Element;
1119 level1Element = level1Element->siblingById(MatroskaIds::SegmentInfo, diag), ++index) {
1120 // update offset in "SeekHead"-element
1121 if (segment.seekInfo.push(index, MatroskaIds::SegmentInfo, currentPosition + segment.totalDataSize)) {
1122 goto calculateSegmentSize;
1123 } else {
1124 // add size of "SegmentInfo"-element
1125 // -> size of "MuxingApp"- and "WritingApp"-element
1126 segment.infoDataSize = muxingAppElementTotalSize + writingAppElementTotalSize;
1127 // -> add size of "Title"-element
1128 if (segmentIndex < m_titles.size()) {
1129 const auto &title = m_titles[segmentIndex];
1130 if (!title.empty()) {
1132 }
1133 }
1134 // -> add size of other children
1135 for (level2Element = level1Element->firstChild(); level2Element; level2Element = level2Element->nextSibling()) {
1136 level2Element->parse(diag);
1137 switch (level2Element->id()) {
1138 case EbmlIds::Void: // skipped
1139 case EbmlIds::Crc32: // skipped
1140 case MatroskaIds::Title: // calculated separately
1141 case MatroskaIds::MuxingApp: // calculated separately
1142 case MatroskaIds::WrittingApp: // calculated separately
1143 break;
1144 default:
1145 level2Element->makeBuffer();
1146 segment.infoDataSize += level2Element->totalSize();
1147 }
1148 }
1149 // -> calculate total size
1151 }
1152 }
1153
1154 // pretend writing "Tracks"-element
1155 if (trackHeaderSize) {
1156 // update offsets in "SeekHead"-element
1157 if (segment.seekInfo.push(0, MatroskaIds::Tracks, currentPosition + segment.totalDataSize)) {
1158 goto calculateSegmentSize;
1159 } else {
1160 // add size of "Tracks"-element
1161 segment.totalDataSize += trackHeaderSize;
1162 }
1163 }
1164
1165 // pretend writing "Chapters"-element
1166 for (level1Element = level0Element->childById(MatroskaIds::Chapters, diag), index = 0; level1Element;
1167 level1Element = level1Element->siblingById(MatroskaIds::Chapters, diag), ++index) {
1168 // update offset in "SeekHead"-element
1169 if (segment.seekInfo.push(index, MatroskaIds::Chapters, currentPosition + segment.totalDataSize)) {
1170 goto calculateSegmentSize;
1171 } else {
1172 // add size of element
1173 level1Element->makeBuffer();
1174 segment.totalDataSize += level1Element->totalSize();
1175 }
1176 }
1177
1178 // "Tags"- and "Attachments"-element are written in either the first or the last segment
1179 // and either before "Cues"- and "Cluster"-elements or after these elements
1180 // depending on the desired tag position (at the front/at the end)
1181 if (newTagPos == ElementPosition::BeforeData && segmentIndex == 0) {
1182 // pretend writing "Tags"-element
1183 if (tagsSize) {
1184 // update offsets in "SeekHead"-element
1185 if (segment.seekInfo.push(0, MatroskaIds::Tags, currentPosition + segment.totalDataSize)) {
1186 goto calculateSegmentSize;
1187 } else {
1188 // add size of "Tags"-element
1189 segment.totalDataSize += tagsSize;
1190 }
1191 }
1192 // pretend writing "Attachments"-element
1193 if (attachmentsSize) {
1194 // update offsets in "SeekHead"-element
1195 if (segment.seekInfo.push(0, MatroskaIds::Attachments, currentPosition + segment.totalDataSize)) {
1196 goto calculateSegmentSize;
1197 } else {
1198 // add size of "Attachments"-element
1199 segment.totalDataSize += attachmentsSize;
1200 }
1201 }
1202 }
1203
1204 offset = segment.totalDataSize; // save current offset (offset before "Cues"-element)
1205
1206 // pretend writing "Cues"-element
1207 if (newCuesPos == ElementPosition::BeforeData && segment.cuesElement) {
1208 // update offset of "Cues"-element in "SeekHead"-element
1209 if (segment.seekInfo.push(0, MatroskaIds::Cues, currentPosition + segment.totalDataSize)) {
1210 goto calculateSegmentSize;
1211 } else {
1212 // add size of "Cues"-element
1213 progress.updateStep("Calculating cluster offsets and index size ...");
1214 addCuesElementSize:
1215 segment.totalDataSize += segment.cuesUpdater.totalSize();
1216 }
1217 } else {
1218 progress.updateStep("Calculating cluster offsets ...");
1219 }
1220
1221 // decided whether it is necessary to rewrite the entire file (if not already rewriting)
1222 if (!rewriteRequired) {
1223 // find first "Cluster"-element
1224 if ((level1Element = segment.firstClusterElement)) {
1225 // just before the first "Cluster"-element
1226 // -> calculate total offset (excluding size denotation and incomplete index)
1227 totalOffset = currentOffset + 4 + segment.totalDataSize;
1228
1229 if (totalOffset <= segment.firstClusterElement->startOffset()) {
1230 // the padding might be big enough, but
1231 // - the segment might become bigger (subsequent tags and attachments)
1232 // - the header size hasn't been taken into account yet
1233 // - seek information for first cluster and subsequent tags and attachments hasn't been taken into account
1234
1235 // assume the size denotation length doesn't change -> use length from original file
1236 if (level0Element->headerSize() <= 4 || level0Element->headerSize() > 12) {
1237 // validate original header size
1238 diag.emplace_back(DiagLevel::Critical, "Header size of \"Segment\"-element from original file is invalid.", context);
1239 throw InvalidDataException();
1240 }
1241 segment.sizeDenotationLength = static_cast<std::uint8_t>(level0Element->headerSize() - 4u);
1242
1243 nonRewriteCalculations:
1244 // pretend writing "Cluster"-elements assuming there is no rewrite required
1245 // -> update offset in "SeakHead"-element
1246 if (segment.seekInfo.push(
1247 0, MatroskaIds::Cluster, level1Element->startOffset() - 4 - segment.sizeDenotationLength - ebmlHeaderSize)) {
1248 goto calculateSegmentSize;
1249 }
1250 // -> update offset of "Cluster"-element in "Cues"-element and get end offset of last "Cluster"-element
1251 bool cuesInvalidated = false;
1252 for (index = 0; level1Element; level1Element = level1Element->siblingById(MatroskaIds::Cluster, diag), ++index) {
1253 clusterReadOffset = level1Element->startOffset() - level0Element->dataOffset() + readOffset;
1254 segment.clusterEndOffset = level1Element->endOffset();
1255 if (segment.cuesElement
1256 && segment.cuesUpdater.updateOffsets(
1257 clusterReadOffset, level1Element->startOffset() - 4 - segment.sizeDenotationLength - ebmlHeaderSize)
1258 && newCuesPos == ElementPosition::BeforeData) {
1259 cuesInvalidated = true;
1260 }
1261 // check whether aborted (because this loop might take some seconds to process)
1262 progress.stopIfAborted();
1263 // update the progress percentage (using offset / file size should be accurate enough)
1264 if (index % 50 == 0) {
1265 progress.updateStepPercentage(static_cast<std::uint8_t>(level1Element->dataOffset() * 100 / fileInfo().size()));
1266 }
1267 }
1268 if (cuesInvalidated) {
1269 segment.totalDataSize = offset;
1270 goto addCuesElementSize;
1271 }
1272 segment.totalDataSize = segment.clusterEndOffset - currentOffset - 4 - segment.sizeDenotationLength;
1273
1274 // pretend writing "Cues"-element
1275 progress.updateStep("Calculating offsets of elements after cluster ...");
1276 if (newCuesPos == ElementPosition::AfterData && segment.cuesElement) {
1277 // update offset of "Cues"-element in "SeekHead"-element
1278 if (segment.seekInfo.push(0, MatroskaIds::Cues, currentPosition + segment.totalDataSize)) {
1279 goto calculateSegmentSize;
1280 } else {
1281 // add size of "Cues"-element
1282 segment.totalDataSize += segment.cuesUpdater.totalSize();
1283 }
1284 }
1285
1286 if (newTagPos == ElementPosition::AfterData && segmentIndex == lastSegmentIndex) {
1287 // pretend writing "Tags"-element
1288 if (tagsSize) {
1289 // update offsets in "SeekHead"-element
1290 if (segment.seekInfo.push(0, MatroskaIds::Tags, currentPosition + segment.totalDataSize)) {
1291 goto calculateSegmentSize;
1292 } else {
1293 // add size of "Tags"-element
1294 segment.totalDataSize += tagsSize;
1295 }
1296 }
1297 // pretend writing "Attachments"-element
1298 if (attachmentsSize) {
1299 // update offsets in "SeekHead"-element
1300 if (segment.seekInfo.push(0, MatroskaIds::Attachments, currentPosition + segment.totalDataSize)) {
1301 goto calculateSegmentSize;
1302 } else {
1303 // add size of "Attachments"-element
1304 segment.totalDataSize += attachmentsSize;
1305 }
1306 }
1307 }
1308
1309 // calculate total offset again (taking everything into account)
1310 // -> check whether assumed size denotation was correct
1311 if (segment.sizeDenotationLength != (sizeLength = EbmlElement::calculateSizeDenotationLength(segment.totalDataSize))) {
1312 // assumption was wrong -> recalculate with new length
1313 segment.sizeDenotationLength = sizeLength;
1314 level1Element = segment.firstClusterElement;
1315 goto nonRewriteCalculations;
1316 }
1317
1318 totalOffset = currentOffset + 4 + sizeLength + offset;
1319 // offset does not include size of "Cues"-element
1320 if (newCuesPos == ElementPosition::BeforeData) {
1321 totalOffset += segment.cuesUpdater.totalSize();
1322 }
1323 if (totalOffset <= segment.firstClusterElement->startOffset()) {
1324 // calculate new padding
1325 if (segment.newPadding != 1) {
1326 // "Void"-element is at least 2 byte long -> can't add 1 byte padding
1327 newPadding += (segment.newPadding = segment.firstClusterElement->startOffset() - totalOffset);
1328 } else {
1329 rewriteRequired = true;
1330 }
1331 } else {
1332 rewriteRequired = true;
1333 }
1334 } else {
1335 rewriteRequired = true;
1336 }
1337 } else {
1338 diag.emplace_back(DiagLevel::Warning, argsToString("There are no clusters in segment ", segmentIndex, "."), context);
1339 }
1340
1341 if (rewriteRequired) {
1342 if (newTagPos != ElementPosition::AfterData
1343 && (!fileInfo().forceTagPosition()
1344 || (fileInfo().tagPosition() == ElementPosition::Keep && currentTagPos == ElementPosition::Keep))) {
1345 // rewriting might be avoided by writing the tags at the end
1346 newTagPos = ElementPosition::AfterData;
1347 rewriteRequired = false;
1348 } else if (newCuesPos != ElementPosition::AfterData
1350 || (fileInfo().indexPosition() == ElementPosition::Keep && currentCuesPos == ElementPosition::Keep))) {
1351 // rewriting might be avoided by writing the cues at the end
1352 newCuesPos = ElementPosition::AfterData;
1353 rewriteRequired = false;
1354 }
1355 // do calculations again for rewriting / changed element order
1356 goto calculateSegmentData;
1357 }
1358 } else {
1359 // if rewrite is required, pretend writing the remaining elements to compute total segment size
1360
1361 // pretend writing "Void"-element (only if there is at least one "Cluster"-element in the segment)
1362 if (!segmentIndex && rewriteRequired && (level1Element = level0Element->childById(MatroskaIds::Cluster, diag))) {
1363 // simply use the preferred padding
1364 segment.totalDataSize += (segment.newPadding = newPadding = fileInfo().preferredPadding());
1365 }
1366
1367 // pretend writing "Cluster"-element
1368 segment.clusterSizes.clear();
1369 bool cuesInvalidated = false;
1370 for (index = 0; level1Element; level1Element = level1Element->siblingById(MatroskaIds::Cluster, diag), ++index) {
1371 // update offset of "Cluster"-element in "Cues"-element
1372 clusterReadOffset = level1Element->startOffset() - level0Element->dataOffset() + readOffset;
1373 if (segment.cuesElement && segment.cuesUpdater.updateOffsets(clusterReadOffset, currentPosition + segment.totalDataSize)
1374 && newCuesPos == ElementPosition::BeforeData) {
1375 cuesInvalidated = true;
1376 } else {
1377 if (index == 0 && segment.seekInfo.push(index, MatroskaIds::Cluster, currentPosition + segment.totalDataSize)) {
1378 goto calculateSegmentSize;
1379 } else {
1380 // add size of "Cluster"-element
1381 clusterSize = clusterReadSize = 0;
1382 for (level2Element = level1Element->firstChild(); level2Element; level2Element = level2Element->nextSibling()) {
1383 level2Element->parse(diag);
1384 if (segment.cuesElement
1385 && segment.cuesUpdater.updateRelativeOffsets(clusterReadOffset, clusterReadSize, clusterSize)
1386 && newCuesPos == ElementPosition::BeforeData) {
1387 cuesInvalidated = true;
1388 }
1389 switch (level2Element->id()) {
1390 case EbmlIds::Void:
1391 case EbmlIds::Crc32:
1392 break;
1394 clusterSize += 1u + 1u + EbmlElement::calculateUIntegerLength(currentPosition + segment.totalDataSize);
1395 break;
1396 default:
1397 clusterSize += level2Element->totalSize();
1398 }
1399 clusterReadSize += level2Element->totalSize();
1400 }
1401 segment.clusterSizes.push_back(clusterSize);
1402 segment.totalDataSize += 4u + EbmlElement::calculateSizeDenotationLength(clusterSize) + clusterSize;
1403 }
1404 }
1405 // check whether aborted (because this loop might take some seconds to process)
1406 progress.stopIfAborted();
1407 // update the progress percentage (using offset / file size should be accurate enough)
1408 if ((index % 50 == 0) && fileInfo().size()) {
1409 progress.updateStepPercentage(static_cast<std::uint8_t>(level1Element->dataOffset() * 100 / fileInfo().size()));
1410 }
1411 // TODO: reduce code duplication for aborting and progress updates
1412 }
1413 // check whether the total size of the "Cues"-element has been invalidated and recompute cluster if required
1414 if (cuesInvalidated) {
1415 // reset element size to previously saved offset of "Cues"-element
1416 segment.totalDataSize = offset;
1417 goto addCuesElementSize;
1418 }
1419
1420 // pretend writing "Cues"-element
1421 progress.updateStep("Calculating offsets of elements after cluster ...");
1422 if (newCuesPos == ElementPosition::AfterData && segment.cuesElement) {
1423 // update offset of "Cues"-element in "SeekHead"-element
1424 if (segment.seekInfo.push(0, MatroskaIds::Cues, currentPosition + segment.totalDataSize)) {
1425 goto calculateSegmentSize;
1426 } else {
1427 // add size of "Cues"-element
1428 segment.totalDataSize += segment.cuesUpdater.totalSize();
1429 }
1430 }
1431
1432 // "Tags"- and "Attachments"-element are written in either the first or the last segment
1433 // and either before "Cues"- and "Cluster"-elements or after these elements
1434 // depending on the desired tag position (at the front/at the end)
1435 if (newTagPos == ElementPosition::AfterData && segmentIndex == lastSegmentIndex) {
1436 // pretend writing "Tags"-element
1437 if (tagsSize) {
1438 // update offsets in "SeekHead"-element
1439 if (segment.seekInfo.push(0, MatroskaIds::Tags, currentPosition + segment.totalDataSize)) {
1440 goto calculateSegmentSize;
1441 } else {
1442 // add size of "Tags"-element
1443 segment.totalDataSize += tagsSize;
1444 }
1445 }
1446 // pretend writing "Attachments"-element
1447 if (attachmentsSize) {
1448 // update offsets in "SeekHead"-element
1449 if (segment.seekInfo.push(0, MatroskaIds::Attachments, currentPosition + segment.totalDataSize)) {
1450 goto calculateSegmentSize;
1451 } else {
1452 // add size of "Attachments"-element
1453 segment.totalDataSize += attachmentsSize;
1454 }
1455 }
1456 }
1457 }
1458
1459 // increase the current segment index
1460 ++segmentIndex;
1461
1462 // increase write offsets by the size of the segment which size has just been computed
1464 currentPosition += segment.totalSize;
1465 currentOffset += segment.totalSize;
1466
1467 // increase the read offset by the size of the segment read from the original file
1468 readOffset += level0Element->totalSize();
1469
1470 break;
1471 }
1472 default:
1473 // just copy any unknown top-level elements
1474 diag.emplace_back(DiagLevel::Warning,
1475 "The top-level element \"" % level0Element->idToString() + "\" of the original file is unknown and will just be copied.",
1476 context);
1477 currentOffset += level0Element->totalSize();
1478 readOffset += level0Element->totalSize();
1479 }
1480 }
1481
1482 if (!rewriteRequired) {
1483 // check whether the new padding is ok according to specifications
1484 if ((rewriteRequired = (newPadding > fileInfo().maxPadding() || newPadding < fileInfo().minPadding()))) {
1485 // need to recalculate segment data for rewrite
1486 goto calculateSegmentData;
1487 }
1488 }
1489
1490 } catch (const OperationAbortedException &) {
1491 diag.emplace_back(DiagLevel::Information, "Applying new tag information has been aborted.", context);
1492 throw;
1493 } catch (const Failure &) {
1494 diag.emplace_back(DiagLevel::Critical, "Parsing the original file failed.", context);
1495 throw;
1496 } catch (const std::ios_base::failure &failure) {
1497 diag.emplace_back(DiagLevel::Critical, argsToString("An IO error occurred when parsing the original file: ", failure.what()), context);
1498 throw;
1499 }
1500
1501 // setup stream(s) for writing
1502 // -> update status
1503 progress.nextStepOrStop("Preparing streams ...");
1504
1505 // -> define variables needed to handle output stream and backup stream (required when rewriting the file)
1506 string originalPath = fileInfo().path(), backupPath;
1507 NativeFileStream &outputStream = fileInfo().stream();
1508 NativeFileStream backupStream; // create a stream to open the backup/original file for the case rewriting the file is required
1509 BinaryWriter outputWriter(&outputStream);
1510 char buff[8]; // buffer used to make size denotations
1511
1512 if (rewriteRequired) {
1513 if (fileInfo().saveFilePath().empty()) {
1514 // move current file to temp dir and reopen it as backupStream, recreate original file
1515 try {
1516 BackupHelper::createBackupFileCanonical(fileInfo().backupDirectory(), originalPath, backupPath, outputStream, backupStream);
1517 // recreate original file, define buffer variables
1518 outputStream.open(originalPath, ios_base::out | ios_base::binary | ios_base::trunc);
1519 } catch (const std::ios_base::failure &failure) {
1520 diag.emplace_back(
1521 DiagLevel::Critical, argsToString("Creation of temporary file (to rewrite the original file) failed: ", failure.what()), context);
1522 throw;
1523 }
1524 } else {
1525 // open the current file as backupStream and create a new outputStream at the specified "save file path"
1526 try {
1527 backupStream.exceptions(ios_base::badbit | ios_base::failbit);
1528 backupStream.open(BasicFileInfo::pathForOpen(fileInfo().path()).data(), ios_base::in | ios_base::binary);
1529 fileInfo().close();
1530 outputStream.open(BasicFileInfo::pathForOpen(fileInfo().saveFilePath()).data(), ios_base::out | ios_base::binary | ios_base::trunc);
1531 } catch (const std::ios_base::failure &failure) {
1532 diag.emplace_back(DiagLevel::Critical, argsToString("Opening streams to write output file failed: ", failure.what()), context);
1533 throw;
1534 }
1535 }
1536
1537 // set backup stream as associated input stream since we need the original elements to write the new file
1538 setStream(backupStream);
1539
1540 // TODO: reduce code duplication
1541
1542 } else { // !rewriteRequired
1543 // buffer currently assigned attachments
1544 for (auto &maker : attachmentMaker) {
1545 maker.bufferCurrentAttachments(diag);
1546 }
1547
1548 // reopen original file to ensure it is opened for writing
1549 try {
1550 fileInfo().close();
1551 outputStream.open(fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary);
1552 } catch (const std::ios_base::failure &failure) {
1553 diag.emplace_back(DiagLevel::Critical, argsToString("Opening the file with write permissions failed: ", failure.what()), context);
1554 throw;
1555 }
1556 }
1557
1558 // start actual writing
1559 try {
1560 // write EBML header
1561 progress.nextStepOrStop("Writing EBML header ...");
1562 outputWriter.writeUInt32BE(EbmlIds::Header);
1563 sizeLength = EbmlElement::makeSizeDenotation(ebmlHeaderDataSize, buff);
1564 outputStream.write(buff, sizeLength);
1567 EbmlElement::makeSimpleElement(outputStream, EbmlIds::MaxIdLength, m_maxIdLength);
1568 EbmlElement::makeSimpleElement(outputStream, EbmlIds::MaxSizeLength, m_maxSizeLength);
1572
1573 // iterates through all level 0 elements of the original file
1574 for (level0Element = firstElement(), segmentIndex = 0, currentPosition = 0; level0Element; level0Element = level0Element->nextSibling()) {
1575
1576 // write all level 0 elements of the original file
1577 switch (level0Element->id()) {
1578 case EbmlIds::Header:
1579 // header has already been written -> skip it here
1580 break;
1581
1582 case EbmlIds::Void:
1583 case EbmlIds::Crc32:
1584 // level 0 "Void"- and "Checksum"-elements are omitted
1585 break;
1586
1587 case MatroskaIds::Segment: {
1588 // get reference to the current segment data instance
1589 SegmentData &segment = segmentData[segmentIndex];
1590
1591 // write "Segment"-element actually
1592 progress.updateStep("Writing segment header ...");
1593 outputWriter.writeUInt32BE(MatroskaIds::Segment);
1594 sizeLength = EbmlElement::makeSizeDenotation(segment.totalDataSize, buff);
1595 outputStream.write(buff, sizeLength);
1596 segment.newDataOffset = offset = static_cast<std::uint64_t>(outputStream.tellp()); // store segment data offset here
1597
1598 // write CRC-32 element ...
1599 if (segment.hasCrc32) {
1600 // ... if the original element had a CRC-32 element
1601 *buff = static_cast<char>(EbmlIds::Crc32);
1602 *(buff + 1) = static_cast<char>(0x84); // length denotation: 4 byte
1603 // set the value after writing the element
1604 crc32Offsets.emplace_back(outputStream.tellp(), segment.totalDataSize);
1605 outputStream.write(buff, 6);
1606 }
1607
1608 // write "SeekHead"-element (except there is no seek information for the current segment)
1609 segment.seekInfo.make(outputStream, diag);
1610
1611 // write "SegmentInfo"-element
1612 for (level1Element = level0Element->childById(MatroskaIds::SegmentInfo, diag); level1Element;
1613 level1Element = level1Element->siblingById(MatroskaIds::SegmentInfo, diag)) {
1614 // -> write ID and size
1615 outputWriter.writeUInt32BE(MatroskaIds::SegmentInfo);
1616 sizeLength = EbmlElement::makeSizeDenotation(segment.infoDataSize, buff);
1617 outputStream.write(buff, sizeLength);
1618 // -> write children
1619 for (level2Element = level1Element->firstChild(); level2Element; level2Element = level2Element->nextSibling()) {
1620 switch (level2Element->id()) {
1621 case EbmlIds::Void: // skipped
1622 case EbmlIds::Crc32: // skipped
1623 case MatroskaIds::Title: // written separately
1624 case MatroskaIds::MuxingApp: // written separately
1625 case MatroskaIds::WrittingApp: // written separately
1626 break;
1627 default:
1628 level2Element->copyBuffer(outputStream);
1629 level2Element->discardBuffer();
1630 }
1631 }
1632 // -> write "Title"-element
1633 if (segmentIndex < m_titles.size()) {
1634 const auto &title = m_titles[segmentIndex];
1635 if (!title.empty()) {
1637 }
1638 }
1639 // -> write "MuxingApp"- and "WritingApp"-element
1640 EbmlElement::makeSimpleElement(outputStream, MatroskaIds::MuxingApp, muxingAppName);
1642 fileInfo().writingApplication().empty() ? muxingAppName : fileInfo().writingApplication());
1643 }
1644
1645 // write "Tracks"-element
1646 if (trackHeaderElementsSize) {
1647 outputWriter.writeUInt32BE(MatroskaIds::Tracks);
1648 sizeLength = EbmlElement::makeSizeDenotation(trackHeaderElementsSize, buff);
1649 outputStream.write(buff, sizeLength);
1650 for (auto &maker : trackHeaderMaker) {
1651 maker.make(outputStream);
1652 }
1653 }
1654
1655 // write "Chapters"-element
1656 for (level1Element = level0Element->childById(MatroskaIds::Chapters, diag); level1Element;
1657 level1Element = level1Element->siblingById(MatroskaIds::Chapters, diag)) {
1658 level1Element->copyBuffer(outputStream);
1659 level1Element->discardBuffer();
1660 }
1661
1662 if (newTagPos == ElementPosition::BeforeData && segmentIndex == 0) {
1663 // write "Tags"-element
1664 if (tagsSize) {
1665 outputWriter.writeUInt32BE(MatroskaIds::Tags);
1666 sizeLength = EbmlElement::makeSizeDenotation(tagElementsSize, buff);
1667 outputStream.write(buff, sizeLength);
1668 for (auto &maker : tagMaker) {
1669 maker.make(outputStream);
1670 }
1671 }
1672 // write "Attachments"-element
1673 if (attachmentsSize) {
1674 outputWriter.writeUInt32BE(MatroskaIds::Attachments);
1675 sizeLength = EbmlElement::makeSizeDenotation(attachedFileElementsSize, buff);
1676 outputStream.write(buff, sizeLength);
1677 for (auto &maker : attachmentMaker) {
1678 maker.make(outputStream, diag);
1679 }
1680 }
1681 }
1682
1683 // write "Cues"-element
1684 if (newCuesPos == ElementPosition::BeforeData && segment.cuesElement) {
1685 segment.cuesUpdater.make(outputStream, diag);
1686 }
1687
1688 // write padding / "Void"-element
1689 if (segment.newPadding) {
1690 // calculate length
1691 std::uint64_t voidLength;
1692 if (segment.newPadding < 64) {
1693 sizeLength = 1;
1694 *buff = static_cast<char>(voidLength = segment.newPadding - 2) | static_cast<char>(0x80);
1695 } else {
1696 sizeLength = 8;
1697 BE::getBytes(static_cast<std::uint64_t>((voidLength = segment.newPadding - 9) | 0x100000000000000), buff);
1698 }
1699 // write header
1700 outputWriter.writeByte(EbmlIds::Void);
1701 outputStream.write(buff, sizeLength);
1702 // write zeroes
1703 for (; voidLength; --voidLength) {
1704 outputStream.put(0);
1705 }
1706 }
1707
1708 // write media data / "Cluster"-elements
1709 level1Element = level0Element->childById(MatroskaIds::Cluster, diag);
1710 if (rewriteRequired) {
1711 // update status, check whether the operation has been aborted
1712 progress.nextStepOrStop("Writing cluster ...",
1713 static_cast<std::uint8_t>((static_cast<std::uint64_t>(outputStream.tellp()) - offset) * 100 / segment.totalDataSize));
1714 // write "Cluster"-element
1715 auto clusterSizesIterator = segment.clusterSizes.cbegin();
1716 unsigned int index = 0;
1717 for (; level1Element; level1Element = level1Element->siblingById(MatroskaIds::Cluster, diag), ++clusterSizesIterator, ++index) {
1718 // calculate position of cluster in segment
1719 clusterSize = currentPosition + (static_cast<std::uint64_t>(outputStream.tellp()) - offset);
1720 // write header; checking whether clusterSizesIterator is valid shouldn't be necessary
1721 outputWriter.writeUInt32BE(MatroskaIds::Cluster);
1722 sizeLength = EbmlElement::makeSizeDenotation(*clusterSizesIterator, buff);
1723 outputStream.write(buff, sizeLength);
1724 // write children
1725 for (level2Element = level1Element->firstChild(); level2Element; level2Element = level2Element->nextSibling()) {
1726 switch (level2Element->id()) {
1727 case EbmlIds::Void:
1728 case EbmlIds::Crc32:
1729 break;
1731 EbmlElement::makeSimpleElement(outputStream, MatroskaIds::Position, clusterSize);
1732 break;
1733 default:
1734 level2Element->copyEntirely(outputStream, diag, nullptr);
1735 }
1736 }
1737 // update percentage, check whether the operation has been aborted
1738 progress.stopIfAborted();
1739 if (index % 50 == 0) {
1740 progress.updateStepPercentage(
1741 static_cast<std::uint8_t>((static_cast<std::uint64_t>(outputStream.tellp()) - offset) * 100 / segment.totalDataSize));
1742 }
1743 }
1744 } else {
1745 // can't just skip existing "Cluster"-elements: "Position"-elements must be updated
1746 progress.nextStepOrStop("Updating cluster ...",
1747 static_cast<std::uint8_t>((static_cast<std::uint64_t>(outputStream.tellp()) - offset) * 100 / segment.totalDataSize));
1748 for (; level1Element; level1Element = level1Element->nextSibling()) {
1749 for (level2Element = level1Element->firstChild(); level2Element; level2Element = level2Element->nextSibling()) {
1750 switch (level2Element->id()) {
1752 // calculate new position
1753 sizeLength = EbmlElement::makeUInteger(level1Element->startOffset() - segmentData.front().newDataOffset, buff,
1754 level2Element->dataSize() > 8 ? 8 : static_cast<std::uint8_t>(level2Element->dataSize()));
1755 // new position can only applied if it doesn't need more bytes than the previous position
1756 if (level2Element->dataSize() < sizeLength) {
1757 // can't update position -> void position elements ("Position"-elements seem a bit useless anyways)
1758 outputStream.seekp(static_cast<streamoff>(level2Element->startOffset()));
1759 outputStream.put(static_cast<char>(EbmlIds::Void));
1760 } else {
1761 // update position
1762 outputStream.seekp(static_cast<streamoff>(level2Element->dataOffset()));
1763 outputStream.write(buff, sizeLength);
1764 }
1765 break;
1766 default:;
1767 }
1768 }
1769 }
1770 // skip existing "Cluster"-elements
1771 outputStream.seekp(static_cast<streamoff>(segment.clusterEndOffset));
1772 }
1773
1774 progress.updateStep("Writing segment tail ...");
1775
1776 // write "Cues"-element
1777 if (newCuesPos == ElementPosition::AfterData && segment.cuesElement) {
1778 segment.cuesUpdater.make(outputStream, diag);
1779 }
1780
1781 if (newTagPos == ElementPosition::AfterData && segmentIndex == lastSegmentIndex) {
1782 // write "Tags"-element
1783 if (tagsSize) {
1784 outputWriter.writeUInt32BE(MatroskaIds::Tags);
1785 sizeLength = EbmlElement::makeSizeDenotation(tagElementsSize, buff);
1786 outputStream.write(buff, sizeLength);
1787 for (auto &maker : tagMaker) {
1788 maker.make(outputStream);
1789 }
1790 }
1791 // write "Attachments"-element
1792 if (attachmentsSize) {
1793 outputWriter.writeUInt32BE(MatroskaIds::Attachments);
1794 sizeLength = EbmlElement::makeSizeDenotation(attachedFileElementsSize, buff);
1795 outputStream.write(buff, sizeLength);
1796 for (auto &maker : attachmentMaker) {
1797 maker.make(outputStream, diag);
1798 }
1799 }
1800 }
1801
1802 // increase the current segment index
1803 ++segmentIndex;
1804
1805 // increase write offsets by the size of the segment which has just been written
1806 currentPosition += segment.totalSize;
1807
1808 break;
1809 }
1810 default:
1811 // just copy any unknown top-level elements
1812 level0Element->copyEntirely(outputStream, diag, nullptr);
1813 currentPosition += level0Element->totalSize();
1814 }
1815 }
1816
1817 // reparse what is written so far
1818 progress.updateStep("Reparsing output file ...");
1819 if (rewriteRequired) {
1820 // report new size
1821 fileInfo().reportSizeChanged(static_cast<std::uint64_t>(outputStream.tellp()));
1822
1823 // "save as path" is now the regular path
1824 if (!fileInfo().saveFilePath().empty()) {
1825 fileInfo().reportPathChanged(fileInfo().saveFilePath());
1826 fileInfo().setSaveFilePath(string());
1827 }
1828
1829 // the outputStream needs to be reopened to be able to read again
1830 outputStream.close();
1831 outputStream.open(fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary);
1832 setStream(outputStream);
1833 } else {
1834 const auto newSize = static_cast<std::uint64_t>(outputStream.tellp());
1835 if (newSize < fileInfo().size()) {
1836 // file is smaller after the modification -> truncate
1837 // -> close stream before truncating
1838 outputStream.close();
1839 // -> truncate file
1840 if (truncate(fileInfo().path().c_str(), static_cast<iostream::off_type>(newSize)) == 0) {
1841 fileInfo().reportSizeChanged(newSize);
1842 } else {
1843 diag.emplace_back(DiagLevel::Critical, "Unable to truncate the file.", context);
1844 }
1845 // -> reopen the stream again
1846 outputStream.open(fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary);
1847 } else {
1848 // file is longer after the modification -> just report new size
1849 fileInfo().reportSizeChanged(newSize);
1850 }
1851 }
1852 reset();
1853 try {
1854 parseHeader(diag, progress);
1855 } catch (const OperationAbortedException &) {
1856 throw;
1857 } catch (const Failure &) {
1858 diag.emplace_back(DiagLevel::Critical, "Unable to reparse the header of the new file.", context);
1859 throw;
1860 }
1861
1862 // update CRC-32 checksums
1863 if (!crc32Offsets.empty()) {
1864 progress.updateStep("Updating CRC-32 checksums ...");
1865 for (const auto &crc32Offset : crc32Offsets) {
1866 outputStream.seekg(static_cast<streamoff>(get<0>(crc32Offset) + 6));
1867 outputStream.seekp(static_cast<streamoff>(get<0>(crc32Offset) + 2));
1868 writer().writeUInt32LE(reader().readCrc32(get<1>(crc32Offset) - 6));
1869 }
1870 }
1871
1872 // prevent deferring final write operations (to catch and handle possible errors here)
1873 outputStream.flush();
1874
1875 // handle errors (which might have been occurred after renaming/creating backup file)
1876 } catch (...) {
1877 BackupHelper::handleFailureAfterFileModifiedCanonical(fileInfo(), originalPath, backupPath, outputStream, backupStream, diag, context);
1878 }
1879}
1880
1881} // namespace TagParser
The AbortableProgressFeedback class provides feedback about an ongoing operation via callbacks.
void stopIfAborted() const
Throws an OperationAbortedException if aborted.
void nextStepOrStop(const std::string &step, std::uint8_t stepPercentage=0)
Throws an OperationAbortedException if aborted; otherwise the data for the next step is set.
std::uint64_t id() const
Returns the ID of the attachment.
bool isIgnored() const
Returns whether the attachment is ignored/omitted when rewriting the container.
void setId(std::uint64_t id)
Sets the ID of the attachment.
std::uint64_t startOffset() const
Returns the start offset in the related stream.
std::vector< std::string > m_titles
bool isHeaderParsed() const
Returns an indication whether the header has been parsed yet.
void setStream(std::iostream &stream)
Sets the related stream.
std::uint32_t timeScale() const
Returns the time scale of the file if known; otherwise returns 0.
CppUtilities::BinaryWriter & writer()
Returns the related BinaryWriter.
void parseHeader(Diagnostics &diag, AbortableProgressFeedback &progress)
Parses the header if not parsed yet.
CppUtilities::BinaryReader & reader()
Returns the related BinaryReader.
CppUtilities::TimeSpan m_duration
void reportPathChanged(std::string_view newPath)
Call this function to report that the path changed.
const std::string & path() const
Returns the path of the current file.
std::uint64_t size() const
Returns size of the current file in bytes.
CppUtilities::NativeFileStream & stream()
Returns the std::fstream for the current instance.
Definition: basicfileinfo.h:85
void close()
A possibly opened std::fstream will be closed.
static std::string_view pathForOpen(std::string_view url)
Returns removes the "file:/" prefix from url to be able to pass it to functions like open(),...
void reportSizeChanged(std::uint64_t newSize)
Call this function to report that the size changed.
void updateStep(const std::string &step, std::uint8_t stepPercentage=0)
Updates the current step and invokes the first callback specified on construction.
void updateStepPercentage(std::uint8_t stepPercentage)
Updates the current step percentage and invokes the second callback specified on construction (or the...
The Diagnostics class is a container for DiagMessage.
Definition: diagnostics.h:156
The EbmlElement class helps to parse EBML files such as Matroska files.
Definition: ebmlelement.h:32
static std::uint8_t makeUInteger(std::uint64_t value, char *buff)
Writes value to buff.
static void makeSimpleElement(std::ostream &stream, IdentifierType id, std::uint64_t content)
Makes a simple EBML element.
static std::uint8_t calculateSizeDenotationLength(std::uint64_t size)
Returns the length of the size denotation for the specified size in byte.
std::string idToString() const
Converts the specified EBML ID to a printable string.
Definition: ebmlelement.h:71
static std::uint8_t makeSizeDenotation(std::uint64_t size, char *buff)
Makes the size denotation for the specified size and stores it to buff.
static std::uint8_t calculateUIntegerLength(std::uint64_t integer)
Returns the length of the specified unsigned integer in byte.
The class inherits from std::exception and serves as base class for exceptions thrown by the elements...
Definition: exceptions.h:11
The GenericContainer class helps parsing header, track, tag and chapter information of a file.
const std::vector< std::unique_ptr< MatroskaTrack > > & tracks() const
Returns the tracks of the file.
const std::vector< std::unique_ptr< MatroskaTag > > & tags() const
Returns the tags of the file.
EbmlElement * firstElement() const
Returns the first element of the file if available; otherwiese returns nullptr.
void reset() override
Discards all parsing results.
std::uint64_t startOffset() const
Returns the start offset in the related stream.
void discardBuffer()
Discards buffered data.
void copyEntirely(std::ostream &targetStream, Diagnostics &diag, AbortableProgressFeedback *progress)
Writes the entire element including all children to the specified targetStream.
std::uint32_t headerSize() const
Returns the header size of the element in byte.
std::uint64_t endOffset() const
Returns the offset of the first byte which doesn't belong to this element anymore.
const IdentifierType & id() const
Returns the element ID.
void copyBuffer(std::ostream &targetStream)
Copies buffered data to targetStream.
ImplementationType * childById(const IdentifierType &id, Diagnostics &diag)
Returns the first child with the specified id.
ImplementationType * nextSibling()
Returns the next sibling of the element.
ImplementationType * firstChild()
Returns the first child of the element.
static constexpr std::uint32_t maximumIdLengthSupported()
Returns the maximum id length supported by the class in byte.
DataSizeType dataSize() const
Returns the data size of the element in byte.
std::uint64_t totalSize() const
Returns the total size of the element.
void parse(Diagnostics &diag)
Parses the header information of the element which is read from the related stream at the start offse...
static constexpr std::uint32_t maximumSizeLengthSupported()
Returns the maximum size length supported by the class in byte.
std::uint64_t dataOffset() const
Returns the data offset of the element in the related stream.
void makeBuffer()
Buffers the element (header and data).
ImplementationType * siblingById(const IdentifierType &id, Diagnostics &diag)
Returns the first sibling with the specified id.
The exception that is thrown when the data to be parsed or to be made seems invalid and therefore can...
Definition: exceptions.h:25
Implementation of TagParser::AbstractAttachment for the Matroska container.
MatroskaAttachmentMaker prepareMaking(Diagnostics &diag)
Prepares making.
The MatroskaChapter class provides an implementation of AbstractAttachment for Matroska files.
void internalParseChapters(Diagnostics &diag, AbortableProgressFeedback &progress) override
Internally called to parse the chapters.
void internalParseHeader(Diagnostics &diag, AbortableProgressFeedback &progress) override
Internally called to parse the header.
std::size_t chapterCount() const override
Returns the number of chapters the container holds.
void internalParseTracks(Diagnostics &diag, AbortableProgressFeedback &progress) override
Internally called to parse the tracks.
ElementPosition determineTagPosition(Diagnostics &diag) const override
Determines the position of the tags inside the file.
MatroskaAttachment * createAttachment() override
Creates and returns a new attachment.
void reset() override
Discards all parsing results.
MatroskaContainer(MediaFileInfo &stream, std::uint64_t startOffset)
Constructs a new container for the specified fileInfo at the specified startOffset.
MatroskaAttachment * attachment(std::size_t index) override
Returns the attachment with the specified index.
void internalMakeFile(Diagnostics &diag, AbortableProgressFeedback &progress) override
Internally called to make the file.
MatroskaChapter * chapter(std::size_t index) override
Returns the chapter with the specified index.
ElementPosition determineIndexPosition(Diagnostics &diag) const override
Determines the position of the index.
void internalParseAttachments(Diagnostics &diag, AbortableProgressFeedback &progress) override
Internally called to parse the attachments.
ElementPosition determineElementPosition(std::uint64_t elementId, Diagnostics &diag) const
Determines the position of the element with the specified elementId.
void validateIndex(Diagnostics &diag, AbortableProgressFeedback &progress)
Validates the file index (cue entries).
void internalParseTags(Diagnostics &diag, AbortableProgressFeedback &progress) override
Internally called to parse the tags.
The MatroskaCuePositionUpdater class helps to rewrite the "Cues"-element with shifted positions.
Definition: matroskacues.h:64
std::uint64_t totalSize() const
Returns how many bytes will be written when calling the make() method.
bool updateOffsets(std::uint64_t originalOffset, std::uint64_t newOffset)
Sets the offset of the entries with the specified originalOffset to newOffset.
bool updateRelativeOffsets(std::uint64_t referenceOffset, std::uint64_t originalRelativeOffset, std::uint64_t newRelativeOffset)
Sets the relative offset of the entries with the specified originalRelativeOffset and the specified r...
void make(std::ostream &stream, Diagnostics &diag)
Writes the previously parsed "Cues"-element with updated positions to the specified stream.
void parse(EbmlElement *cuesElement, Diagnostics &diag)
Parses the specified cuesElement.
The MatroskaSeekInfo class helps parsing and making "SeekHead"-elements.
bool push(unsigned int index, EbmlElement::IdentifierType id, std::uint64_t offset)
Pushes the specified offset of an element with the specified id to the info.
void make(std::ostream &stream, Diagnostics &diag)
Writes a "SeekHead" element for the current instance to the specified stream.
std::uint64_t actualSize() const
Returns the number of bytes which will be written when calling the make() method.
Implementation of TagParser::Tag for the Matroska container.
Definition: matroskatag.h:72
MatroskaTagMaker prepareMaking(Diagnostics &diag)
Prepares making.
Definition: matroskatag.h:136
Implementation of TagParser::AbstractTrack for the Matroska container.
Definition: matroskatrack.h:48
MatroskaTrackHeaderMaker prepareMakingHeader(Diagnostics &diag) const
Prepares making header.
Definition: matroskatrack.h:86
void readStatisticsFromTags(const std::vector< std::unique_ptr< MatroskaTag > > &tags, Diagnostics &diag)
Reads track-specific statistics from the specified tags.
The MediaFileInfo class allows to read and write tag information providing a container/tag format ind...
Definition: mediafileinfo.h:76
bool isForcingRewrite() const
Returns whether forcing rewriting (when applying changes) is enabled.
void setSaveFilePath(std::string_view saveFilePath)
Sets the "save file path".
bool forceIndexPosition() const
Returns whether indexPosition() is forced.
const std::string & saveFilePath() const
Returns the "save file path" which has been set using setSaveFilePath().
std::size_t preferredPadding() const
Returns the padding to be written before the data block when applying changes and the file needs to b...
const std::string & writingApplication() const
Sets the writing application as container-level meta-data.
ElementPosition tagPosition() const
Returns the position (in the output file) where the tag information is written when applying changes.
bool forceTagPosition() const
Returns whether tagPosition() is forced.
ElementPosition indexPosition() const
Returns the position (in the output file) where the index is written when applying changes.
The exception that is thrown when the data to be parsed holds no parsable information (e....
Definition: exceptions.h:18
The exception that is thrown when an operation has been stopped and thus not successfully completed b...
Definition: exceptions.h:46
TAG_PARSER_EXPORT void handleFailureAfterFileModifiedCanonical(MediaFileInfo &fileInfo, const std::string &originalPath, const std::string &backupPath, CppUtilities::NativeFileStream &outputStream, CppUtilities::NativeFileStream &backupStream, Diagnostics &diag, const std::string &context="making file")
Handles a failure/abort which occurred after the file has been modified.
TAG_PARSER_EXPORT void createBackupFileCanonical(const std::string &backupDir, std::string &originalPath, std::string &backupPath, CppUtilities::NativeFileStream &originalStream, CppUtilities::NativeFileStream &backupStream)
Creates a backup file like createBackupFile() but canonicalizes originalPath before doing the backup.
constexpr TAG_PARSER_EXPORT std::string_view title()
Definition: matroskatagid.h:44
Contains all classes and functions of the TagInfo library.
Definition: aaccodebook.h:10
ElementPosition
Definition: settings.h:13
bool sameOffset(std::uint64_t offset, const EbmlElement *element)
Returns an indication whether offset equals the start offset of element.
bool excludesOffset(const vector< EbmlElement * > &elements, std::uint64_t offset)
Returns whether none of the specified elements have the specified offset.
The private SegmentData struct is used in MatroskaContainer::internalMakeFile() to store segment spec...
SegmentData()
Constructs a new segment data object.
bool hasCrc32
whether CRC-32 checksum is present
std::uint64_t newPadding
padding (in the new file)
std::uint64_t totalDataSize
total size of the segment data (in the new file, excluding header)
vector< std::uint64_t > clusterSizes
cluster sizes
std::uint8_t sizeDenotationLength
header size (in the new file)
std::uint64_t newDataOffset
data offset of the segment in the new file
std::uint64_t infoDataSize
size of the "SegmentInfo"-element
std::uint64_t startOffset
start offset (in the new file)
MatroskaSeekInfo seekInfo
used to make "SeekHead"-element
EbmlElement * firstClusterElement
first "Cluster"-element (original file)
std::uint64_t clusterEndOffset
end offset of last "Cluster"-element (original file)
MatroskaCuePositionUpdater cuesUpdater
used to make "Cues"-element
EbmlElement * cuesElement
"Cues"-element (original file)
std::uint64_t totalSize
total size of the segment data (in the new file, including header)