Tag Parser 12.3.1
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
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 auto context = std::string("validating Matroska file index (cues)");
86 auto cuesElementsFound = false;
87 if (m_firstElement) {
88 auto ids = std::unordered_set<EbmlElement::IdentifierType>();
89 auto cueTimeFound = false, cueTrackPositionsFound = false;
90 auto clusterElement = std::unique_ptr<EbmlElement>();
91 auto pos = std::uint64_t(), prevClusterSize = std::uint64_t(), currentOffset = std::uint64_t();
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
395
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;
631 muxingApplications().emplace_back(subElement->readString());
632 break;
634 writingApplications().emplace_back(subElement->readString());
635 break;
636 }
637 subElement = subElement->nextSibling();
638 }
639 // add empty string as title for segment if no
640 // "Title"-element has been specified
641 if (!hasTitle) {
642 m_titles.emplace_back();
643 }
644 if (rawDuration > 0.0) {
645 m_duration += TimeSpan::fromSeconds(rawDuration * static_cast<double>(timeScale) / 1000000000.0);
646 }
647 }
648}
649
655void MatroskaContainer::readTrackStatisticsFromTags(Diagnostics &diag)
656{
657 if (tracks().empty() || tags().empty()) {
658 return;
659 }
660 for (const auto &track : tracks()) {
662 }
663}
664
666{
667 CPP_UTILITIES_UNUSED(progress)
668
669 static const string context("parsing tags of Matroska container");
670 auto flags = MatroskaTagFlags::None;
673 }
674 for (EbmlElement *const element : m_tagsElements) {
675 try {
676 element->parse(diag);
677 for (EbmlElement *subElement = element->firstChild(); subElement; subElement = subElement->nextSibling()) {
678 subElement->parse(diag);
679 switch (subElement->id()) {
680 case MatroskaIds::Tag:
681 m_tags.emplace_back(make_unique<MatroskaTag>());
682 try {
683 m_tags.back()->parse2(*subElement, flags, diag);
684 } catch (const NoDataFoundException &) {
685 m_tags.pop_back();
686 } catch (const Failure &) {
687 diag.emplace_back(DiagLevel::Critical, argsToString("Unable to parse tag ", m_tags.size(), '.'), context);
688 }
689 break;
690 case EbmlIds::Crc32:
691 case EbmlIds::Void:
692 break;
693 default:
694 diag.emplace_back(DiagLevel::Warning, "\"Tags\"-element contains unknown child. It will be ignored.", context);
695 }
696 }
697 } catch (const Failure &) {
698 diag.emplace_back(DiagLevel::Critical, "Element structure seems to be invalid.", context);
699 readTrackStatisticsFromTags(diag);
700 throw;
701 }
702 }
703 readTrackStatisticsFromTags(diag);
704}
705
707{
708 static const string context("parsing tracks of Matroska container");
709 for (EbmlElement *element : m_tracksElements) {
710 try {
711 element->parse(diag);
712 for (EbmlElement *subElement = element->firstChild(); subElement; subElement = subElement->nextSibling()) {
713 subElement->parse(diag);
714 switch (subElement->id()) {
716 m_tracks.emplace_back(make_unique<MatroskaTrack>(*subElement));
717 try {
718 m_tracks.back()->parseHeader(diag, progress);
719 } catch (const NoDataFoundException &) {
720 m_tracks.pop_back();
721 } catch (const Failure &) {
722 diag.emplace_back(DiagLevel::Critical, argsToString("Unable to parse track ", m_tracks.size(), '.'), context);
723 }
724 break;
725 case EbmlIds::Crc32:
726 case EbmlIds::Void:
727 break;
728 default:
729 diag.emplace_back(DiagLevel::Warning,
730 "\"Tracks\"-element contains unknown child element \"" % subElement->idToString() + "\". It will be ignored.", context);
731 }
732 }
733 } catch (const Failure &) {
734 diag.emplace_back(DiagLevel::Critical, "Element structure seems to be invalid.", context);
735 readTrackStatisticsFromTags(diag);
736 throw;
737 }
738 }
739 readTrackStatisticsFromTags(diag);
740}
741
743{
744 static const string context("parsing editions/chapters of Matroska container");
745 for (EbmlElement *element : m_chaptersElements) {
746 try {
747 element->parse(diag);
748 for (EbmlElement *subElement = element->firstChild(); subElement; subElement = subElement->nextSibling()) {
749 subElement->parse(diag);
750 switch (subElement->id()) {
752 m_editionEntries.emplace_back(make_unique<MatroskaEditionEntry>(subElement));
753 try {
754 m_editionEntries.back()->parseNested(diag, progress);
755 } catch (const NoDataFoundException &) {
756 m_editionEntries.pop_back();
757 } catch (const Failure &) {
758 diag.emplace_back(DiagLevel::Critical, argsToString("Unable to parse edition entry ", m_editionEntries.size(), '.'), context);
759 }
760 break;
761 case EbmlIds::Crc32:
762 case EbmlIds::Void:
763 break;
764 default:
765 diag.emplace_back(DiagLevel::Warning,
766 "\"Chapters\"-element contains unknown child element \"" % subElement->idToString() + "\". It will be ignored.", context);
767 }
768 }
769 } catch (const Failure &) {
770 diag.emplace_back(DiagLevel::Critical, "Element structure seems to be invalid.", context);
771 throw;
772 }
773 }
774}
775
777{
778 CPP_UTILITIES_UNUSED(progress)
779
780 static const string context("parsing attachments of Matroska container");
781 for (EbmlElement *element : m_attachmentsElements) {
782 try {
783 element->parse(diag);
784 for (EbmlElement *subElement = element->firstChild(); subElement; subElement = subElement->nextSibling()) {
785 subElement->parse(diag);
786 switch (subElement->id()) {
788 m_attachments.emplace_back(make_unique<MatroskaAttachment>());
789 try {
790 m_attachments.back()->parse(subElement, diag);
791 } catch (const NoDataFoundException &) {
792 m_attachments.pop_back();
793 } catch (const Failure &) {
794 diag.emplace_back(DiagLevel::Critical, argsToString("Unable to parse attached file ", m_attachments.size(), '.'), context);
795 }
796 break;
797 case EbmlIds::Crc32:
798 case EbmlIds::Void:
799 break;
800 default:
801 diag.emplace_back(DiagLevel::Warning,
802 "\"Attachments\"-element contains unknown child element \"" % subElement->idToString() + "\". It will be ignored.", context);
803 }
804 }
805 } catch (const Failure &) {
806 diag.emplace_back(DiagLevel::Critical, "Element structure seems to be invalid.", context);
807 throw;
808 }
809 }
810}
811
816 : hasCrc32(false)
817 , cuesElement(nullptr)
818 , infoDataSize(0)
819 , firstClusterElement(nullptr)
821 , startOffset(0)
822 , newPadding(0)
823 , totalDataSize(0)
824 , totalSize(0)
825 , newDataOffset(0)
827 {
828 }
830
840 std::uint64_t infoDataSize;
842 std::vector<std::uint64_t> clusterSizes;
846 std::uint64_t clusterEndOffset;
848 std::uint64_t startOffset;
850 std::uint64_t newPadding;
852 std::uint64_t totalDataSize;
854 std::uint64_t totalSize;
856 std::uint64_t newDataOffset;
859};
860
862{
863 static const string context("making Matroska container");
864 progress.updateStep("Calculating element sizes ...");
865
866 // basic validation of original file
867 if (!isHeaderParsed()) {
868 diag.emplace_back(DiagLevel::Critical, "The header has not been parsed yet.", context);
869 throw InvalidDataException();
870 }
871 switch (fileInfo().attachmentsParsingStatus()) {
874 break;
875 default:
876 diag.emplace_back(DiagLevel::Critical, "Attachments have to be parsed without critical errors before changes can be applied.", context);
877 throw InvalidDataException();
878 }
879
880 // define variables for parsing the elements of the original file
881 EbmlElement *level0Element = firstElement();
882 if (!level0Element) {
883 diag.emplace_back(DiagLevel::Critical, "No EBML elements could be found.", context);
884 throw InvalidDataException();
885 }
886 EbmlElement *level1Element, *level2Element;
887
888 // define variables needed for precalculation of "Tags"- and "Attachments"-element
889 std::vector<MatroskaTagMaker> tagMaker;
890 tagMaker.reserve(tags().size());
891 std::uint64_t tagElementsSize = 0;
892 std::uint64_t tagsSize;
893 std::vector<MatroskaAttachmentMaker> attachmentMaker;
894 attachmentMaker.reserve(m_attachments.size());
895 std::uint64_t attachedFileElementsSize = 0;
896 std::uint64_t attachmentsSize;
897 std::vector<MatroskaTrackHeaderMaker> trackHeaderMaker;
898 trackHeaderMaker.reserve(tracks().size());
899 std::uint64_t trackHeaderElementsSize = 0;
900 std::uint64_t trackHeaderSize;
901
902 // define variables to store sizes, offsets and other information required to make a header and "Segment"-elements
903 // current segment index
904 unsigned int segmentIndex = 0;
905 // segment specific data
906 std::vector<SegmentData> segmentData;
907 // offset of the segment which is currently written / offset of "Cues"-element in segment
908 std::uint64_t offset;
909 // current total offset (including EBML header)
910 std::uint64_t totalOffset;
911 // current write offset (used to calculate positions)
912 std::uint64_t currentPosition = 0;
913 // holds the offsets of all CRC-32 elements and the length of the enclosing block
914 std::vector<std::tuple<std::uint64_t, std::uint64_t>> crc32Offsets;
915 // size length used to make size denotations
916 std::uint8_t sizeLength;
917 // sizes and offsets for cluster calculation
918 std::uint64_t clusterSize, clusterReadSize, clusterReadOffset;
919
920 // define variables needed to manage file layout
921 // -> use the preferred tag position by default (might be changed later if not forced)
922 ElementPosition newTagPos = fileInfo().tagPosition();
923 // -> current tag position (determined later)
925 // -> use the preferred cue position by default (might be changed later if not forced)
926 ElementPosition newCuesPos = fileInfo().indexPosition();
927 // --> current cue position (determined later)
928 ElementPosition currentCuesPos = ElementPosition::Keep;
929 // -> index of the last segment
930 unsigned int lastSegmentIndex = numeric_limits<unsigned int>::max();
931 // -> holds new padding
932 std::uint64_t newPadding;
933 // -> whether rewrite is required (always required when forced to rewrite)
934 bool rewriteRequired = fileInfo().isForcingRewrite() || !fileInfo().saveFilePath().empty();
935
936 // calculate EBML header size
937 // -> sub element ID sizes
938 std::uint64_t ebmlHeaderDataSize = 2 * 7;
939 // -> content and size denotation length of numeric sub elements
940 for (auto headerValue :
941 initializer_list<std::uint64_t>{ m_version, m_readVersion, m_maxIdLength, m_maxSizeLength, m_doctypeVersion, m_doctypeReadVersion }) {
942 ebmlHeaderDataSize += sizeLength = EbmlElement::calculateUIntegerLength(headerValue);
943 ebmlHeaderDataSize += EbmlElement::calculateSizeDenotationLength(sizeLength);
944 }
945 // -> content and size denotation length of string sub elements
946 ebmlHeaderDataSize += m_doctype.size();
947 ebmlHeaderDataSize += EbmlElement::calculateSizeDenotationLength(m_doctype.size());
948 const std::uint64_t ebmlHeaderSize = 4 + EbmlElement::calculateSizeDenotationLength(ebmlHeaderDataSize) + ebmlHeaderDataSize;
949
950 // calculate size of "WritingLib"-element
951 const auto &muxingApps = const_cast<const MatroskaContainer *>(this)->muxingApplications();
952 const auto muxingAppName = (fileInfo().fileHandlingFlags() & MediaFileHandlingFlags::PreserveMuxingApplication && !muxingApps.empty())
953 ? std::string_view(muxingApps.front())
954 : std::string_view(APP_NAME " v" APP_VERSION);
955 const auto muxingAppElementTotalSize = std::uint64_t(2 + EbmlElement::calculateSizeDenotationLength(muxingAppName.size()) + muxingAppName.size());
956
957 // calculate size of "WritingApp"-element
958 const auto writingApps = const_cast<const MatroskaContainer *>(this)->writingApplications();
959 const auto writingAppName = (fileInfo().fileHandlingFlags() & MediaFileHandlingFlags::PreserveWritingApplication && !writingApps.empty())
960 ? std::string_view(writingApps.front())
961 : std::string_view(fileInfo().writingApplication().empty() ? muxingAppName : std::string_view(fileInfo().writingApplication()));
962 const auto writingAppElementTotalSize
963 = std::uint64_t(2 + EbmlElement::calculateSizeDenotationLength(writingAppName.size()) + writingAppName.size());
964
965 try {
966 // calculate size of "Tags"-element
967 for (auto &tag : tags()) {
968 try {
969 const auto &maker = tagMaker.emplace_back(tag->prepareMaking(diag));
970 if (maker.requiredSize() > 3) {
971 // a tag of 3 bytes size is empty and can be skipped
972 tagElementsSize += maker.requiredSize();
973 }
974 } catch (const Failure &) {
975 }
976 }
977 tagsSize = tagElementsSize ? 4 + EbmlElement::calculateSizeDenotationLength(tagElementsSize) + tagElementsSize : 0;
978
979 // calculate size of "Attachments"-element
980 for (auto &attachment : m_attachments) {
981 if (!attachment->isIgnored()) {
982 try {
983 const auto &maker = attachmentMaker.emplace_back(attachment->prepareMaking(diag));
984 if (maker.requiredSize() > 3) {
985 // an attachment of 3 bytes size is empty and can be skipped
986 attachedFileElementsSize += maker.requiredSize();
987 }
988 } catch (const Failure &) {
989 }
990 }
991 }
992 attachmentsSize
993 = attachedFileElementsSize ? 4 + EbmlElement::calculateSizeDenotationLength(attachedFileElementsSize) + attachedFileElementsSize : 0;
994
995 // calculate size of "Tracks"-element
996 for (auto &track : tracks()) {
997 try {
998 const auto &maker = trackHeaderMaker.emplace_back(track->prepareMakingHeader(diag));
999 if (maker.requiredSize() > 3) {
1000 // a track header of 3 bytes size is empty and can be skipped
1001 trackHeaderElementsSize += maker.requiredSize();
1002 }
1003 } catch (const Failure &) {
1004 }
1005 }
1006 trackHeaderSize
1007 = trackHeaderElementsSize ? 4 + EbmlElement::calculateSizeDenotationLength(trackHeaderElementsSize) + trackHeaderElementsSize : 0;
1008
1009 // inspect layout of original file
1010 // - number of segments
1011 // - position of tags relative to the media data
1012 try {
1013 for (bool firstClusterFound = false, firstTagFound = false; level0Element; level0Element = level0Element->nextSibling()) {
1014 level0Element->parse(diag);
1015 switch (level0Element->id()) {
1017 ++lastSegmentIndex;
1018 for (level1Element = level0Element->firstChild(); level1Element && !firstClusterFound && !firstTagFound;
1019 level1Element = level1Element->nextSibling()) {
1020 level1Element->parse(diag);
1021 switch (level1Element->id()) {
1022 case MatroskaIds::Tags:
1024 firstTagFound = true;
1025 break;
1027 firstClusterFound = true;
1028 }
1029 }
1030 if (firstTagFound) {
1031 currentTagPos = ElementPosition::BeforeData;
1032 } else if (firstClusterFound) {
1033 currentTagPos = ElementPosition::AfterData;
1034 }
1035 }
1036 }
1037
1038 // now the number of segments is known -> allocate segment specific data
1039 segmentData.resize(lastSegmentIndex + 1);
1040
1041 // now the current tag/cue position might be known
1042 if (newTagPos == ElementPosition::Keep) {
1043 if ((newTagPos = currentTagPos) == ElementPosition::Keep) {
1044 newTagPos = ElementPosition::BeforeData;
1045 }
1046 }
1047
1048 } catch (const Failure &) {
1049 diag.emplace_back(DiagLevel::Critical,
1050 "Unable to parse content in top-level element at " % numberToString(level0Element->startOffset()) + " of original file.", context);
1051 throw;
1052 }
1053
1054 progress.nextStepOrStop("Calculating offsets of elements before cluster ...");
1055 calculateSegmentData:
1056 // define variables to store sizes, offsets and other information required to make a header and "Segment"-elements
1057 // -> current "pretent" write offset
1058 std::uint64_t currentOffset = ebmlHeaderSize;
1059 // -> current read offset (used to calculate positions)
1060 std::uint64_t readOffset = 0;
1061 // -> index of current element during iteration
1062 unsigned int index;
1063
1064 // if rewriting is required always use the preferred tag/cue position
1065 if (rewriteRequired) {
1066 newTagPos = fileInfo().tagPosition();
1067 if (newTagPos == ElementPosition::Keep) {
1068 if ((newTagPos = currentTagPos) == ElementPosition::Keep) {
1069 newTagPos = ElementPosition::BeforeData;
1070 }
1071 }
1072 newCuesPos = fileInfo().indexPosition();
1073 }
1074
1075 // calculate sizes and other information required to make segments
1076 for (level0Element = firstElement(), currentPosition = newPadding = segmentIndex = 0; level0Element;
1077 level0Element = level0Element->nextSibling()) {
1078 switch (level0Element->id()) {
1079 case EbmlIds::Header:
1080 // header size has already been calculated
1081 break;
1082
1083 case EbmlIds::Void:
1084 case EbmlIds::Crc32:
1085 // level 0 "Void"- and "Checksum"-elements are omitted
1086 break;
1087
1088 case MatroskaIds::Segment: {
1089 // get reference to the current segment data instance
1090 SegmentData &segment = segmentData[segmentIndex];
1091
1092 // parse original "Cues"-element (if present)
1093 if (!segment.cuesElement && (segment.cuesElement = level0Element->childById(MatroskaIds::Cues, diag))) {
1094 segment.cuesUpdater.parse(segment.cuesElement, diag);
1095 }
1096
1097 // get first "Cluster"-element
1098 if (!segment.firstClusterElement) {
1099 segment.firstClusterElement = level0Element->childById(MatroskaIds::Cluster, diag);
1100 }
1101
1102 // determine current/new cue position
1103 if (segment.cuesElement && segment.firstClusterElement) {
1104 currentCuesPos = segment.cuesElement->startOffset() < segment.firstClusterElement->startOffset() ? ElementPosition::BeforeData
1106 if (newCuesPos == ElementPosition::Keep) {
1107 newCuesPos = currentCuesPos;
1108 }
1109 } else if (newCuesPos == ElementPosition::Keep) {
1110 newCuesPos = ElementPosition::BeforeData;
1111 }
1112
1113 // set start offset of the segment in the new file
1114 segment.startOffset = currentOffset;
1115
1116 // check whether the segment has a CRC-32 element
1117 segment.hasCrc32 = level0Element->firstChild() && level0Element->firstChild()->id() == EbmlIds::Crc32;
1118
1119 // precalculate the size of the segment
1120 calculateSegmentSize:
1121
1122 // pretent writing "CRC-32"-element (which either present and 6 byte long or omitted)
1123 segment.totalDataSize = segment.hasCrc32 ? 6 : 0;
1124
1125 // pretend writing "SeekHead"-element
1126 segment.totalDataSize += segment.seekInfo.actualSize();
1127
1128 // pretend writing "SegmentInfo"-element
1129 for (level1Element = level0Element->childById(MatroskaIds::SegmentInfo, diag), index = 0; level1Element;
1130 level1Element = level1Element->siblingById(MatroskaIds::SegmentInfo, diag), ++index) {
1131 // update offset in "SeekHead"-element
1132 if (segment.seekInfo.push(index, MatroskaIds::SegmentInfo, currentPosition + segment.totalDataSize)) {
1133 goto calculateSegmentSize;
1134 } else {
1135 // add size of "SegmentInfo"-element
1136 // -> size of "MuxingApp"- and "WritingApp"-element
1137 segment.infoDataSize = muxingAppElementTotalSize + writingAppElementTotalSize;
1138 // -> add size of "Title"-element
1139 if (segmentIndex < m_titles.size()) {
1140 const auto &title = m_titles[segmentIndex];
1141 if (!title.empty()) {
1142 segment.infoDataSize += 2 + EbmlElement::calculateSizeDenotationLength(title.size()) + title.size();
1143 }
1144 }
1145 // -> add size of other children
1146 for (level2Element = level1Element->firstChild(); level2Element; level2Element = level2Element->nextSibling()) {
1147 level2Element->parse(diag);
1148 switch (level2Element->id()) {
1149 case EbmlIds::Void: // skipped
1150 case EbmlIds::Crc32: // skipped
1151 case MatroskaIds::Title: // calculated separately
1152 case MatroskaIds::MuxingApp: // calculated separately
1153 case MatroskaIds::WrittingApp: // calculated separately
1154 break;
1155 default:
1156 level2Element->makeBuffer();
1157 segment.infoDataSize += level2Element->totalSize();
1158 }
1159 }
1160 // -> calculate total size
1162 }
1163 }
1164
1165 // pretend writing "Tracks"-element
1166 if (trackHeaderSize) {
1167 // update offsets in "SeekHead"-element
1168 if (segment.seekInfo.push(0, MatroskaIds::Tracks, currentPosition + segment.totalDataSize)) {
1169 goto calculateSegmentSize;
1170 } else {
1171 // add size of "Tracks"-element
1172 segment.totalDataSize += trackHeaderSize;
1173 }
1174 }
1175
1176 // pretend writing "Chapters"-element
1177 for (level1Element = level0Element->childById(MatroskaIds::Chapters, diag), index = 0; level1Element;
1178 level1Element = level1Element->siblingById(MatroskaIds::Chapters, diag), ++index) {
1179 // update offset in "SeekHead"-element
1180 if (segment.seekInfo.push(index, MatroskaIds::Chapters, currentPosition + segment.totalDataSize)) {
1181 goto calculateSegmentSize;
1182 } else {
1183 // add size of element
1184 level1Element->makeBuffer();
1185 segment.totalDataSize += level1Element->totalSize();
1186 }
1187 }
1188
1189 // "Tags"- and "Attachments"-element are written in either the first or the last segment
1190 // and either before "Cues"- and "Cluster"-elements or after these elements
1191 // depending on the desired tag position (at the front/at the end)
1192 if (newTagPos == ElementPosition::BeforeData && segmentIndex == 0) {
1193 // pretend writing "Tags"-element
1194 if (tagsSize) {
1195 // update offsets in "SeekHead"-element
1196 if (segment.seekInfo.push(0, MatroskaIds::Tags, currentPosition + segment.totalDataSize)) {
1197 goto calculateSegmentSize;
1198 } else {
1199 // add size of "Tags"-element
1200 segment.totalDataSize += tagsSize;
1201 }
1202 }
1203 // pretend writing "Attachments"-element
1204 if (attachmentsSize) {
1205 // update offsets in "SeekHead"-element
1206 if (segment.seekInfo.push(0, MatroskaIds::Attachments, currentPosition + segment.totalDataSize)) {
1207 goto calculateSegmentSize;
1208 } else {
1209 // add size of "Attachments"-element
1210 segment.totalDataSize += attachmentsSize;
1211 }
1212 }
1213 }
1214
1215 offset = segment.totalDataSize; // save current offset (offset before "Cues"-element)
1216
1217 // pretend writing "Cues"-element
1218 if (newCuesPos == ElementPosition::BeforeData && segment.cuesElement) {
1219 // update offset of "Cues"-element in "SeekHead"-element
1220 if (segment.seekInfo.push(0, MatroskaIds::Cues, currentPosition + segment.totalDataSize)) {
1221 goto calculateSegmentSize;
1222 } else {
1223 // add size of "Cues"-element
1224 progress.updateStep("Calculating cluster offsets and index size ...");
1225 addCuesElementSize:
1226 segment.totalDataSize += segment.cuesUpdater.totalSize();
1227 }
1228 } else {
1229 progress.updateStep("Calculating cluster offsets ...");
1230 }
1231
1232 // decide whether it is necessary to rewrite the entire file (if not already rewriting)
1233 if (!rewriteRequired) {
1234 // find first "Cluster"-element
1235 if ((level1Element = segment.firstClusterElement)) {
1236 // just before the first "Cluster"-element
1237 // -> calculate total offset (excluding size denotation and incomplete index)
1238 totalOffset = currentOffset + 4 + segment.totalDataSize;
1239
1240 if (totalOffset <= segment.firstClusterElement->startOffset()) {
1241 // the padding might be big enough, but
1242 // - the segment might become bigger (subsequent tags and attachments)
1243 // - the header size hasn't been taken into account yet
1244 // - seek information for first cluster and subsequent tags and attachments hasn't been taken into account
1245
1246 // assume the size denotation length doesn't change -> use length from original file
1247 if (level0Element->headerSize() <= 4 || level0Element->headerSize() > 12) {
1248 // validate original header size
1249 diag.emplace_back(DiagLevel::Critical, "Header size of \"Segment\"-element from original file is invalid.", context);
1250 throw InvalidDataException();
1251 }
1252 segment.sizeDenotationLength = static_cast<std::uint8_t>(level0Element->headerSize() - 4u);
1253
1254 nonRewriteCalculations:
1255 // pretend writing "Cluster"-elements assuming there is no rewrite required
1256 // -> update offset in "SeakHead"-element
1257 if (segment.seekInfo.push(
1258 0, MatroskaIds::Cluster, level1Element->startOffset() - 4 - segment.sizeDenotationLength - ebmlHeaderSize)) {
1259 goto calculateSegmentSize;
1260 }
1261 // -> update offset of "Cluster"-element in "Cues"-element and get end offset of last "Cluster"-element
1262 bool cuesInvalidated = false;
1263 for (index = 0; level1Element; level1Element = level1Element->siblingById(MatroskaIds::Cluster, diag), ++index) {
1264 clusterReadOffset = level1Element->startOffset() - level0Element->dataOffset() + readOffset;
1265 segment.clusterEndOffset = level1Element->endOffset();
1266 if (segment.cuesElement
1267 && segment.cuesUpdater.updateOffsets(
1268 clusterReadOffset, level1Element->startOffset() - 4 - segment.sizeDenotationLength - ebmlHeaderSize)
1269 && newCuesPos == ElementPosition::BeforeData) {
1270 cuesInvalidated = true;
1271 }
1272 // check whether aborted (because this loop might take some seconds to process)
1273 progress.stopIfAborted();
1274 // update the progress percentage (using offset / file size should be accurate enough)
1275 if (index % 50 == 0) {
1276 progress.updateStepPercentage(static_cast<std::uint8_t>(level1Element->dataOffset() * 100 / fileInfo().size()));
1277 }
1278 }
1279 if (cuesInvalidated) {
1280 segment.totalDataSize = offset;
1281 goto addCuesElementSize;
1282 }
1283 segment.totalDataSize = segment.clusterEndOffset - currentOffset - 4 - segment.sizeDenotationLength;
1284
1285 // pretend writing "Cues"-element
1286 progress.updateStep("Calculating offsets of elements after cluster ...");
1287 if (newCuesPos == ElementPosition::AfterData && segment.cuesElement) {
1288 // update offset of "Cues"-element in "SeekHead"-element
1289 if (segment.seekInfo.push(0, MatroskaIds::Cues, currentPosition + segment.totalDataSize)) {
1290 goto calculateSegmentSize;
1291 } else {
1292 // add size of "Cues"-element
1293 segment.totalDataSize += segment.cuesUpdater.totalSize();
1294 }
1295 }
1296
1297 if (newTagPos == ElementPosition::AfterData && segmentIndex == lastSegmentIndex) {
1298 // pretend writing "Tags"-element
1299 if (tagsSize) {
1300 // update offsets in "SeekHead"-element
1301 if (segment.seekInfo.push(0, MatroskaIds::Tags, currentPosition + segment.totalDataSize)) {
1302 goto calculateSegmentSize;
1303 } else {
1304 // add size of "Tags"-element
1305 segment.totalDataSize += tagsSize;
1306 }
1307 }
1308 // pretend writing "Attachments"-element
1309 if (attachmentsSize) {
1310 // update offsets in "SeekHead"-element
1311 if (segment.seekInfo.push(0, MatroskaIds::Attachments, currentPosition + segment.totalDataSize)) {
1312 goto calculateSegmentSize;
1313 } else {
1314 // add size of "Attachments"-element
1315 segment.totalDataSize += attachmentsSize;
1316 }
1317 }
1318 }
1319
1320 // calculate total offset again (taking everything into account)
1321 // -> check whether assumed size denotation was correct
1322 if (segment.sizeDenotationLength != (sizeLength = EbmlElement::calculateSizeDenotationLength(segment.totalDataSize))) {
1323 // assumption was wrong -> recalculate with new length
1324 segment.sizeDenotationLength = sizeLength;
1325 level1Element = segment.firstClusterElement;
1326 goto nonRewriteCalculations;
1327 }
1328
1329 totalOffset = currentOffset + 4 + sizeLength + offset;
1330 // offset does not include size of "Cues"-element
1331 if (newCuesPos == ElementPosition::BeforeData) {
1332 totalOffset += segment.cuesUpdater.totalSize();
1333 }
1334 if (totalOffset <= segment.firstClusterElement->startOffset()) {
1335 // calculate new padding
1336 if (segment.newPadding != 1) {
1337 // "Void"-element is at least 2 byte long -> can't add 1 byte padding
1338 newPadding += (segment.newPadding = segment.firstClusterElement->startOffset() - totalOffset);
1339 } else {
1340 rewriteRequired = true;
1341 }
1342 } else {
1343 rewriteRequired = true;
1344 }
1345 } else {
1346 rewriteRequired = true;
1347 }
1348 } else {
1349 diag.emplace_back(DiagLevel::Warning, argsToString("There are no clusters in segment ", segmentIndex, "."), context);
1350 }
1351
1352 if (rewriteRequired) {
1353 if (newTagPos != ElementPosition::AfterData
1354 && (!fileInfo().forceTagPosition()
1355 || (fileInfo().tagPosition() == ElementPosition::Keep && currentTagPos == ElementPosition::Keep))) {
1356 // rewriting might be avoided by writing the tags at the end
1357 newTagPos = ElementPosition::AfterData;
1358 rewriteRequired = false;
1359 } else if (newCuesPos != ElementPosition::AfterData
1360 && (!fileInfo().forceIndexPosition()
1361 || (fileInfo().indexPosition() == ElementPosition::Keep && currentCuesPos == ElementPosition::Keep))) {
1362 // rewriting might be avoided by writing the cues at the end
1363 newCuesPos = ElementPosition::AfterData;
1364 rewriteRequired = false;
1365 }
1366 // do calculations again for rewriting / changed element order
1367 goto calculateSegmentData;
1368 }
1369 } else {
1370 // if rewrite is required, pretend writing the remaining elements to compute total segment size and cluster sizes
1371
1372 // pretend writing "Void"-element (only if there is at least one "Cluster"-element in the segment)
1373 if (!segmentIndex && rewriteRequired && (level1Element = level0Element->childById(MatroskaIds::Cluster, diag))) {
1374 // simply use the preferred padding
1375 segment.totalDataSize += (segment.newPadding = newPadding = fileInfo().preferredPadding());
1376 }
1377
1378 // pretend writing "Cluster"-element
1379 segment.clusterSizes.clear();
1380 bool cuesInvalidated = false;
1381 for (index = 0; level1Element; level1Element = level1Element->siblingById(MatroskaIds::Cluster, diag), ++index) {
1382 // update offset of "Cluster"-element in "Cues"-element
1383 clusterReadOffset = level1Element->startOffset() - level0Element->dataOffset() + readOffset;
1384 if (segment.cuesElement && segment.cuesUpdater.updateOffsets(clusterReadOffset, currentPosition + segment.totalDataSize)
1385 && newCuesPos == ElementPosition::BeforeData) {
1386 cuesInvalidated = true;
1387 } else {
1388 if (index == 0 && segment.seekInfo.push(index, MatroskaIds::Cluster, currentPosition + segment.totalDataSize)) {
1389 goto calculateSegmentSize;
1390 } else {
1391 // add size of "Cluster"-element
1392 clusterSize = clusterReadSize = 0;
1393 for (level2Element = level1Element->firstChild(); level2Element; level2Element = level2Element->nextSibling()) {
1394 level2Element->parse(diag);
1395 if (segment.cuesElement
1396 && segment.cuesUpdater.updateRelativeOffsets(clusterReadOffset, clusterReadSize, clusterSize)
1397 && newCuesPos == ElementPosition::BeforeData) {
1398 cuesInvalidated = true;
1399 }
1400 switch (level2Element->id()) {
1401 case EbmlIds::Void:
1402 case EbmlIds::Crc32:
1403 break;
1405 clusterSize += 1u + 1u + EbmlElement::calculateUIntegerLength(currentPosition + segment.totalDataSize);
1406 break;
1407 default:
1408 clusterSize += level2Element->totalSize();
1409 }
1410 clusterReadSize += level2Element->totalSize();
1411 }
1412 segment.clusterSizes.push_back(clusterSize);
1413 segment.totalDataSize += 4u + EbmlElement::calculateSizeDenotationLength(clusterSize) + clusterSize;
1414 }
1415 }
1416 // check whether aborted (because this loop might take some seconds to process)
1417 progress.stopIfAborted();
1418 // update the progress percentage (using offset / file size should be accurate enough)
1419 if ((index % 50 == 0) && fileInfo().size()) {
1420 progress.updateStepPercentage(static_cast<std::uint8_t>(level1Element->dataOffset() * 100 / fileInfo().size()));
1421 }
1422 // TODO: reduce code duplication for aborting and progress updates
1423 }
1424 // check whether the total size of the "Cues"-element has been invalidated and recompute cluster if required
1425 if (cuesInvalidated) {
1426 // reset element size to previously saved offset of "Cues"-element
1427 segment.totalDataSize = offset;
1428 goto addCuesElementSize;
1429 }
1430
1431 // pretend writing "Cues"-element
1432 progress.updateStep("Calculating offsets of elements after cluster ...");
1433 if (newCuesPos == ElementPosition::AfterData && segment.cuesElement) {
1434 // update offset of "Cues"-element in "SeekHead"-element
1435 if (segment.seekInfo.push(0, MatroskaIds::Cues, currentPosition + segment.totalDataSize)) {
1436 goto calculateSegmentSize;
1437 } else {
1438 // add size of "Cues"-element
1439 segment.totalDataSize += segment.cuesUpdater.totalSize();
1440 }
1441 }
1442
1443 // "Tags"- and "Attachments"-element are written in either the first or the last segment
1444 // and either before "Cues"- and "Cluster"-elements or after these elements
1445 // depending on the desired tag position (at the front/at the end)
1446 if (newTagPos == ElementPosition::AfterData && segmentIndex == lastSegmentIndex) {
1447 // pretend writing "Tags"-element
1448 if (tagsSize) {
1449 // update offsets in "SeekHead"-element
1450 if (segment.seekInfo.push(0, MatroskaIds::Tags, currentPosition + segment.totalDataSize)) {
1451 goto calculateSegmentSize;
1452 } else {
1453 // add size of "Tags"-element
1454 segment.totalDataSize += tagsSize;
1455 }
1456 }
1457 // pretend writing "Attachments"-element
1458 if (attachmentsSize) {
1459 // update offsets in "SeekHead"-element
1460 if (segment.seekInfo.push(0, MatroskaIds::Attachments, currentPosition + segment.totalDataSize)) {
1461 goto calculateSegmentSize;
1462 } else {
1463 // add size of "Attachments"-element
1464 segment.totalDataSize += attachmentsSize;
1465 }
1466 }
1467 }
1468 }
1469
1470 // increase the current segment index
1471 ++segmentIndex;
1472
1473 // increase write offsets by the size of the segment which size has just been computed
1475 currentPosition += segment.totalSize;
1476 currentOffset += segment.totalSize;
1477
1478 // increase the read offset by the size of the segment read from the original file
1479 readOffset += level0Element->totalSize();
1480
1481 break;
1482 }
1483 default:
1484 // just copy any unknown top-level elements
1485 diag.emplace_back(DiagLevel::Warning,
1486 "The top-level element \"" % level0Element->idToString() + "\" of the original file is unknown and will just be copied.",
1487 context);
1488 currentOffset += level0Element->totalSize();
1489 readOffset += level0Element->totalSize();
1490 }
1491 }
1492
1493 if (!rewriteRequired) {
1494 // check whether the new padding is ok according to specifications
1495 if ((rewriteRequired = (newPadding > fileInfo().maxPadding() || newPadding < fileInfo().minPadding()))) {
1496 // need to recalculate segment data for rewrite
1497 goto calculateSegmentData;
1498 }
1499 }
1500
1501 } catch (const OperationAbortedException &) {
1502 diag.emplace_back(DiagLevel::Information, "Applying new tag information has been aborted.", context);
1503 throw;
1504 } catch (const Failure &) {
1505 diag.emplace_back(DiagLevel::Critical, "Parsing the original file failed.", context);
1506 throw;
1507 } catch (const std::ios_base::failure &failure) {
1508 diag.emplace_back(DiagLevel::Critical, argsToString("An IO error occurred when parsing the original file: ", failure.what()), context);
1509 throw;
1510 }
1511
1512 // setup stream(s) for writing
1513 // -> update status
1514 progress.nextStepOrStop("Preparing streams ...");
1515
1516 // -> define variables needed to handle output stream and backup stream (required when rewriting the file)
1517 string originalPath = fileInfo().path(), backupPath;
1518 NativeFileStream &outputStream = fileInfo().stream();
1519 NativeFileStream backupStream; // create a stream to open the backup/original file for the case rewriting the file is required
1520 BinaryWriter outputWriter(&outputStream);
1521 char buff[8]; // buffer used to make size denotations
1522
1523 if (rewriteRequired) {
1524 if (fileInfo().saveFilePath().empty()) {
1525 // move current file to temp dir and reopen it as backupStream, recreate original file
1526 try {
1527 BackupHelper::createBackupFileCanonical(fileInfo().backupDirectory(), originalPath, backupPath, outputStream, backupStream);
1528 // recreate original file, define buffer variables
1529 outputStream.open(originalPath, ios_base::out | ios_base::binary | ios_base::trunc);
1530 } catch (const std::ios_base::failure &failure) {
1531 diag.emplace_back(
1532 DiagLevel::Critical, argsToString("Creation of temporary file (to rewrite the original file) failed: ", failure.what()), context);
1533 throw;
1534 }
1535 } else {
1536 // open the current file as backupStream and create a new outputStream at the specified "save file path"
1537 try {
1538 backupStream.exceptions(ios_base::badbit | ios_base::failbit);
1539 backupStream.open(BasicFileInfo::pathForOpen(fileInfo().path()).data(), ios_base::in | ios_base::binary);
1540 fileInfo().close();
1541 outputStream.open(BasicFileInfo::pathForOpen(fileInfo().saveFilePath()).data(), ios_base::out | ios_base::binary | ios_base::trunc);
1542 } catch (const std::ios_base::failure &failure) {
1543 diag.emplace_back(DiagLevel::Critical, argsToString("Opening streams to write output file failed: ", failure.what()), context);
1544 throw;
1545 }
1546 }
1547
1548 // set backup stream as associated input stream since we need the original elements to write the new file
1549 setStream(backupStream);
1550
1551 // TODO: reduce code duplication
1552
1553 } else { // !rewriteRequired
1554 // buffer currently assigned attachments
1555 for (auto &maker : attachmentMaker) {
1556 maker.bufferCurrentAttachments(diag);
1557 }
1558
1559 // reopen original file to ensure it is opened for writing
1560 try {
1561 fileInfo().close();
1562 outputStream.open(fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary);
1563 } catch (const std::ios_base::failure &failure) {
1564 diag.emplace_back(DiagLevel::Critical, argsToString("Opening the file with write permissions failed: ", failure.what()), context);
1565 throw;
1566 }
1567 }
1568
1569 // start actual writing
1570 try {
1571 // write EBML header
1572 progress.nextStepOrStop("Writing EBML header ...");
1573 outputWriter.writeUInt32BE(EbmlIds::Header);
1574 sizeLength = EbmlElement::makeSizeDenotation(ebmlHeaderDataSize, buff);
1575 outputStream.write(buff, sizeLength);
1578 EbmlElement::makeSimpleElement(outputStream, EbmlIds::MaxIdLength, m_maxIdLength);
1579 EbmlElement::makeSimpleElement(outputStream, EbmlIds::MaxSizeLength, m_maxSizeLength);
1583
1584 // iterates through all level 0 elements of the original file
1585 for (level0Element = firstElement(), segmentIndex = 0, currentPosition = 0; level0Element; level0Element = level0Element->nextSibling()) {
1586
1587 // write all level 0 elements of the original file
1588 switch (level0Element->id()) {
1589 case EbmlIds::Header:
1590 // header has already been written -> skip it here
1591 break;
1592
1593 case EbmlIds::Void:
1594 case EbmlIds::Crc32:
1595 // level 0 "Void"- and "Checksum"-elements are omitted
1596 break;
1597
1598 case MatroskaIds::Segment: {
1599 // get reference to the current segment data instance
1600 SegmentData &segment = segmentData[segmentIndex];
1601
1602 // write "Segment"-element actually
1603 progress.updateStep("Writing segment header ...");
1604 outputWriter.writeUInt32BE(MatroskaIds::Segment);
1605 sizeLength = EbmlElement::makeSizeDenotation(segment.totalDataSize, buff);
1606 outputStream.write(buff, sizeLength);
1607 segment.newDataOffset = offset = static_cast<std::uint64_t>(outputStream.tellp()); // store segment data offset here
1608
1609 // write CRC-32 element ...
1610 if (segment.hasCrc32) {
1611 // ... if the original element had a CRC-32 element
1612 *buff = static_cast<char>(EbmlIds::Crc32);
1613 *(buff + 1) = static_cast<char>(0x84); // length denotation: 4 byte
1614 // set the value after writing the element
1615 crc32Offsets.emplace_back(outputStream.tellp(), segment.totalDataSize);
1616 outputStream.write(buff, 6);
1617 }
1618
1619 // write "SeekHead"-element (except there is no seek information for the current segment)
1620 segment.seekInfo.make(outputStream, diag);
1621
1622 // write "SegmentInfo"-element
1623 for (level1Element = level0Element->childById(MatroskaIds::SegmentInfo, diag); level1Element;
1624 level1Element = level1Element->siblingById(MatroskaIds::SegmentInfo, diag)) {
1625 // -> write ID and size
1626 outputWriter.writeUInt32BE(MatroskaIds::SegmentInfo);
1627 sizeLength = EbmlElement::makeSizeDenotation(segment.infoDataSize, buff);
1628 outputStream.write(buff, sizeLength);
1629 // -> write children
1630 for (level2Element = level1Element->firstChild(); level2Element; level2Element = level2Element->nextSibling()) {
1631 switch (level2Element->id()) {
1632 case EbmlIds::Void: // skipped
1633 case EbmlIds::Crc32: // skipped
1634 case MatroskaIds::Title: // written separately
1635 case MatroskaIds::MuxingApp: // written separately
1636 case MatroskaIds::WrittingApp: // written separately
1637 break;
1638 default:
1639 level2Element->copyBuffer(outputStream);
1640 level2Element->discardBuffer();
1641 }
1642 }
1643 // -> write "Title"-element
1644 if (segmentIndex < m_titles.size()) {
1645 const auto &title = m_titles[segmentIndex];
1646 if (!title.empty()) {
1648 }
1649 }
1650 // -> write "MuxingApp"- and "WritingApp"-element
1651 EbmlElement::makeSimpleElement(outputStream, MatroskaIds::MuxingApp, muxingAppName);
1652 EbmlElement::makeSimpleElement(outputStream, MatroskaIds::WrittingApp, writingAppName);
1653 }
1654
1655 // write "Tracks"-element
1656 if (trackHeaderElementsSize) {
1657 outputWriter.writeUInt32BE(MatroskaIds::Tracks);
1658 sizeLength = EbmlElement::makeSizeDenotation(trackHeaderElementsSize, buff);
1659 outputStream.write(buff, sizeLength);
1660 for (auto &maker : trackHeaderMaker) {
1661 maker.make(outputStream);
1662 }
1663 }
1664
1665 // write "Chapters"-element
1666 for (level1Element = level0Element->childById(MatroskaIds::Chapters, diag); level1Element;
1667 level1Element = level1Element->siblingById(MatroskaIds::Chapters, diag)) {
1668 level1Element->copyBuffer(outputStream);
1669 level1Element->discardBuffer();
1670 }
1671
1672 if (newTagPos == ElementPosition::BeforeData && segmentIndex == 0) {
1673 // write "Tags"-element
1674 if (tagsSize) {
1675 outputWriter.writeUInt32BE(MatroskaIds::Tags);
1676 sizeLength = EbmlElement::makeSizeDenotation(tagElementsSize, buff);
1677 outputStream.write(buff, sizeLength);
1678 for (auto &maker : tagMaker) {
1679 maker.make(outputStream);
1680 }
1681 }
1682 // write "Attachments"-element
1683 if (attachmentsSize) {
1684 outputWriter.writeUInt32BE(MatroskaIds::Attachments);
1685 sizeLength = EbmlElement::makeSizeDenotation(attachedFileElementsSize, buff);
1686 outputStream.write(buff, sizeLength);
1687 for (auto &maker : attachmentMaker) {
1688 maker.make(outputStream, diag);
1689 }
1690 }
1691 }
1692
1693 // write "Cues"-element
1694 if (newCuesPos == ElementPosition::BeforeData && segment.cuesElement) {
1695 segment.cuesUpdater.make(outputStream, diag);
1696 }
1697
1698 // write padding / "Void"-element
1699 if (segment.newPadding) {
1700 // calculate length
1701 std::uint64_t voidLength;
1702 if (segment.newPadding < 64) {
1703 sizeLength = 1;
1704 *buff = static_cast<char>(voidLength = segment.newPadding - 2) | static_cast<char>(0x80);
1705 } else {
1706 sizeLength = 8;
1707 BE::getBytes(static_cast<std::uint64_t>((voidLength = segment.newPadding - 9) | 0x100000000000000), buff);
1708 }
1709 // write header
1710 outputWriter.writeByte(EbmlIds::Void);
1711 outputStream.write(buff, sizeLength);
1712 // write zeroes
1713 MediaFileInfo::writePadding(outputStream, voidLength);
1714 }
1715
1716 // write media data / "Cluster"-elements
1717 level1Element = level0Element->childById(MatroskaIds::Cluster, diag);
1718 if (rewriteRequired) {
1719 // update status, check whether the operation has been aborted
1720 progress.nextStepOrStop("Writing cluster ...",
1721 static_cast<std::uint8_t>((static_cast<std::uint64_t>(outputStream.tellp()) - offset) * 100 / segment.totalDataSize));
1722 // write "Cluster"-element
1723 auto clusterSizesIterator = segment.clusterSizes.cbegin();
1724 unsigned int index = 0;
1725 for (; level1Element; level1Element = level1Element->siblingById(MatroskaIds::Cluster, diag), ++clusterSizesIterator, ++index) {
1726 // calculate position of cluster in segment
1727 clusterSize = currentPosition + (static_cast<std::uint64_t>(outputStream.tellp()) - offset);
1728 // write header; checking whether clusterSizesIterator is valid shouldn't be necessary
1729 outputWriter.writeUInt32BE(MatroskaIds::Cluster);
1730 sizeLength = EbmlElement::makeSizeDenotation(*clusterSizesIterator, buff);
1731 outputStream.write(buff, sizeLength);
1732 // write children
1733 for (level2Element = level1Element->firstChild(); level2Element; level2Element = level2Element->nextSibling()) {
1734 switch (level2Element->id()) {
1735 case EbmlIds::Void:
1736 case EbmlIds::Crc32:
1737 break;
1739 EbmlElement::makeSimpleElement(outputStream, MatroskaIds::Position, clusterSize);
1740 break;
1741 default:
1742 level2Element->copyEntirely(outputStream, diag, nullptr);
1743 }
1744 }
1745 // update percentage, check whether the operation has been aborted
1746 progress.stopIfAborted();
1747 if (index % 50 == 0) {
1748 progress.updateStepPercentage(
1749 static_cast<std::uint8_t>((static_cast<std::uint64_t>(outputStream.tellp()) - offset) * 100 / segment.totalDataSize));
1750 }
1751 }
1752 } else {
1753 // can't just skip existing "Cluster"-elements: "Position"-elements must be updated
1754 progress.nextStepOrStop("Updating cluster ...",
1755 static_cast<std::uint8_t>((static_cast<std::uint64_t>(outputStream.tellp()) - offset) * 100 / segment.totalDataSize));
1756 for (; level1Element; level1Element = level1Element->nextSibling()) {
1757 for (level2Element = level1Element->firstChild(); level2Element; level2Element = level2Element->nextSibling()) {
1758 switch (level2Element->id()) {
1760 // calculate new position
1761 sizeLength = EbmlElement::makeUInteger(level1Element->startOffset() - segmentData.front().newDataOffset, buff,
1762 level2Element->dataSize() > 8 ? 8 : static_cast<std::uint8_t>(level2Element->dataSize()));
1763 // new position can only applied if it doesn't need more bytes than the previous position
1764 if (level2Element->dataSize() < sizeLength) {
1765 // can't update position -> void position elements ("Position"-elements seem a bit useless anyways)
1766 outputStream.seekp(static_cast<streamoff>(level2Element->startOffset()));
1767 outputStream.put(static_cast<char>(EbmlIds::Void));
1768 } else {
1769 // update position
1770 outputStream.seekp(static_cast<streamoff>(level2Element->dataOffset()));
1771 outputStream.write(buff, sizeLength);
1772 }
1773 break;
1774 default:;
1775 }
1776 }
1777 }
1778 // skip existing "Cluster"-elements
1779 outputStream.seekp(static_cast<streamoff>(segment.clusterEndOffset));
1780 }
1781
1782 progress.updateStep("Writing segment tail ...");
1783
1784 // write "Cues"-element
1785 if (newCuesPos == ElementPosition::AfterData && segment.cuesElement) {
1786 segment.cuesUpdater.make(outputStream, diag);
1787 }
1788
1789 if (newTagPos == ElementPosition::AfterData && segmentIndex == lastSegmentIndex) {
1790 // write "Tags"-element
1791 if (tagsSize) {
1792 outputWriter.writeUInt32BE(MatroskaIds::Tags);
1793 sizeLength = EbmlElement::makeSizeDenotation(tagElementsSize, buff);
1794 outputStream.write(buff, sizeLength);
1795 for (auto &maker : tagMaker) {
1796 maker.make(outputStream);
1797 }
1798 }
1799 // write "Attachments"-element
1800 if (attachmentsSize) {
1801 outputWriter.writeUInt32BE(MatroskaIds::Attachments);
1802 sizeLength = EbmlElement::makeSizeDenotation(attachedFileElementsSize, buff);
1803 outputStream.write(buff, sizeLength);
1804 for (auto &maker : attachmentMaker) {
1805 maker.make(outputStream, diag);
1806 }
1807 }
1808 }
1809
1810 // increase the current segment index
1811 ++segmentIndex;
1812
1813 // increase write offsets by the size of the segment which has just been written
1814 currentPosition += segment.totalSize;
1815
1816 break;
1817 }
1818 default:
1819 // just copy any unknown top-level elements
1820 level0Element->copyEntirely(outputStream, diag, nullptr);
1821 currentPosition += level0Element->totalSize();
1822 }
1823 }
1824
1825 // reparse what is written so far
1826 progress.updateStep("Reparsing output file ...");
1827 if (rewriteRequired) {
1828 // report new size
1829 fileInfo().reportSizeChanged(static_cast<std::uint64_t>(outputStream.tellp()));
1830
1831 // "save as path" is now the regular path
1832 if (!fileInfo().saveFilePath().empty()) {
1833 fileInfo().reportPathChanged(fileInfo().saveFilePath());
1834 fileInfo().setSaveFilePath(string());
1835 }
1836
1837 // the outputStream needs to be reopened to be able to read again
1838 outputStream.close();
1839 outputStream.open(fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary);
1840 setStream(outputStream);
1841 } else {
1842 const auto newSize = static_cast<std::uint64_t>(outputStream.tellp());
1843 if (newSize < fileInfo().size()) {
1844 // file is smaller after the modification -> truncate
1845 // -> close stream before truncating
1846 outputStream.close();
1847 // -> truncate file
1848 auto ec = std::error_code();
1849 std::filesystem::resize_file(makeNativePath(fileInfo().path()), newSize, ec);
1850 if (!ec) {
1851 fileInfo().reportSizeChanged(newSize);
1852 } else {
1853 diag.emplace_back(DiagLevel::Critical, "Unable to truncate the file: " + ec.message(), context);
1854 }
1855 // -> reopen the stream again
1856 outputStream.open(fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary);
1857 } else {
1858 // file is longer after the modification -> just report new size
1859 fileInfo().reportSizeChanged(newSize);
1860 }
1861 }
1862 reset();
1863 try {
1864 parseHeader(diag, progress);
1865 } catch (const OperationAbortedException &) {
1866 throw;
1867 } catch (const Failure &) {
1868 diag.emplace_back(DiagLevel::Critical, "Unable to reparse the header of the new file.", context);
1869 throw;
1870 }
1871
1872 // update CRC-32 checksums
1873 if (!crc32Offsets.empty()) {
1874 progress.updateStep("Updating CRC-32 checksums ...");
1875 for (const auto &crc32Offset : crc32Offsets) {
1876 outputStream.seekg(static_cast<streamoff>(get<0>(crc32Offset) + 6));
1877 outputStream.seekp(static_cast<streamoff>(get<0>(crc32Offset) + 2));
1878 writer().writeUInt32LE(reader().readCrc32(get<1>(crc32Offset) - 6));
1879 }
1880 }
1881
1882 // prevent deferring final write operations (to catch and handle possible errors here)
1883 outputStream.flush();
1884
1885 // handle errors (which might have been occurred after renaming/creating backup file)
1886 } catch (...) {
1887 BackupHelper::handleFailureAfterFileModifiedCanonical(fileInfo(), originalPath, backupPath, outputStream, backupStream, diag, context);
1888 }
1889}
1890
1891} // 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
const std::vector< std::string > & writingApplications() const
Returns the writing applications specified as meta-data.
const std::vector< std::string > & muxingApplications() const
Returns the muxing applications specified as meta-data.
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.
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.
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...
The GenericContainer class helps parsing header, track, tag and chapter information of a file.
const std::vector< std::unique_ptr< MatroskaTrack > > & tracks() const
const std::vector< std::unique_ptr< MatroskaTag > > & tags() const
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()
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()
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...
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.
Implementation of GenericContainer<MediaFileInfo, MatroskaTag, MatroskaTrack, EbmlElement>.
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.
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".
MediaFileHandlingFlags fileHandlingFlags()
Returns the currently configured file handling flags.
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...
ElementPosition tagPosition() const
Returns the position (in the output file) where the tag information is written when applying changes.
static void writePadding(std::ostream &outputStream, uint64_t size)
Writes the specified number of zeroes to outputStream.
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....
The exception that is thrown when an operation has been stopped and thus not successfully completed b...
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, needed because cluster elements are not necessarily copied as-is so they're size might...
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)