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