Tag Parser 12.1.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
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
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 + 1 + 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 = std::uint64_t(2 + 1 + writingAppName.size());
963
964 try {
965 // calculate size of "Tags"-element
966 for (auto &tag : tags()) {
967 try {
968 const auto &maker = tagMaker.emplace_back(tag->prepareMaking(diag));
969 if (maker.requiredSize() > 3) {
970 // a tag of 3 bytes size is empty and can be skipped
971 tagElementsSize += maker.requiredSize();
972 }
973 } catch (const Failure &) {
974 }
975 }
976 tagsSize = tagElementsSize ? 4 + EbmlElement::calculateSizeDenotationLength(tagElementsSize) + tagElementsSize : 0;
977
978 // calculate size of "Attachments"-element
979 for (auto &attachment : m_attachments) {
980 if (!attachment->isIgnored()) {
981 try {
982 const auto &maker = attachmentMaker.emplace_back(attachment->prepareMaking(diag));
983 if (maker.requiredSize() > 3) {
984 // an attachment of 3 bytes size is empty and can be skipped
985 attachedFileElementsSize += maker.requiredSize();
986 }
987 } catch (const Failure &) {
988 }
989 }
990 }
991 attachmentsSize
992 = attachedFileElementsSize ? 4 + EbmlElement::calculateSizeDenotationLength(attachedFileElementsSize) + attachedFileElementsSize : 0;
993
994 // calculate size of "Tracks"-element
995 for (auto &track : tracks()) {
996 try {
997 const auto &maker = trackHeaderMaker.emplace_back(track->prepareMakingHeader(diag));
998 if (maker.requiredSize() > 3) {
999 // a track header of 3 bytes size is empty and can be skipped
1000 trackHeaderElementsSize += maker.requiredSize();
1001 }
1002 } catch (const Failure &) {
1003 }
1004 }
1005 trackHeaderSize
1006 = trackHeaderElementsSize ? 4 + EbmlElement::calculateSizeDenotationLength(trackHeaderElementsSize) + trackHeaderElementsSize : 0;
1007
1008 // inspect layout of original file
1009 // - number of segments
1010 // - position of tags relative to the media data
1011 try {
1012 for (bool firstClusterFound = false, firstTagFound = false; level0Element; level0Element = level0Element->nextSibling()) {
1013 level0Element->parse(diag);
1014 switch (level0Element->id()) {
1016 ++lastSegmentIndex;
1017 for (level1Element = level0Element->firstChild(); level1Element && !firstClusterFound && !firstTagFound;
1018 level1Element = level1Element->nextSibling()) {
1019 level1Element->parse(diag);
1020 switch (level1Element->id()) {
1021 case MatroskaIds::Tags:
1023 firstTagFound = true;
1024 break;
1026 firstClusterFound = true;
1027 }
1028 }
1029 if (firstTagFound) {
1030 currentTagPos = ElementPosition::BeforeData;
1031 } else if (firstClusterFound) {
1032 currentTagPos = ElementPosition::AfterData;
1033 }
1034 }
1035 }
1036
1037 // now the number of segments is known -> allocate segment specific data
1038 segmentData.resize(lastSegmentIndex + 1);
1039
1040 // now the current tag/cue position might be known
1041 if (newTagPos == ElementPosition::Keep) {
1042 if ((newTagPos = currentTagPos) == ElementPosition::Keep) {
1043 newTagPos = ElementPosition::BeforeData;
1044 }
1045 }
1046
1047 } catch (const Failure &) {
1048 diag.emplace_back(DiagLevel::Critical,
1049 "Unable to parse content in top-level element at " % numberToString(level0Element->startOffset()) + " of original file.", context);
1050 throw;
1051 }
1052
1053 progress.nextStepOrStop("Calculating offsets of elements before cluster ...");
1054 calculateSegmentData:
1055 // define variables to store sizes, offsets and other information required to make a header and "Segment"-elements
1056 // -> current "pretent" write offset
1057 std::uint64_t currentOffset = ebmlHeaderSize;
1058 // -> current read offset (used to calculate positions)
1059 std::uint64_t readOffset = 0;
1060 // -> index of current element during iteration
1061 unsigned int index;
1062
1063 // if rewriting is required always use the preferred tag/cue position
1064 if (rewriteRequired) {
1065 newTagPos = fileInfo().tagPosition();
1066 if (newTagPos == ElementPosition::Keep) {
1067 if ((newTagPos = currentTagPos) == ElementPosition::Keep) {
1068 newTagPos = ElementPosition::BeforeData;
1069 }
1070 }
1071 newCuesPos = fileInfo().indexPosition();
1072 }
1073
1074 // calculate sizes and other information required to make segments
1075 for (level0Element = firstElement(), currentPosition = newPadding = segmentIndex = 0; level0Element;
1076 level0Element = level0Element->nextSibling()) {
1077 switch (level0Element->id()) {
1078 case EbmlIds::Header:
1079 // header size has already been calculated
1080 break;
1081
1082 case EbmlIds::Void:
1083 case EbmlIds::Crc32:
1084 // level 0 "Void"- and "Checksum"-elements are omitted
1085 break;
1086
1087 case MatroskaIds::Segment: {
1088 // get reference to the current segment data instance
1089 SegmentData &segment = segmentData[segmentIndex];
1090
1091 // parse original "Cues"-element (if present)
1092 if (!segment.cuesElement && (segment.cuesElement = level0Element->childById(MatroskaIds::Cues, diag))) {
1093 segment.cuesUpdater.parse(segment.cuesElement, diag);
1094 }
1095
1096 // get first "Cluster"-element
1097 if (!segment.firstClusterElement) {
1098 segment.firstClusterElement = level0Element->childById(MatroskaIds::Cluster, diag);
1099 }
1100
1101 // determine current/new cue position
1102 if (segment.cuesElement && segment.firstClusterElement) {
1103 currentCuesPos = segment.cuesElement->startOffset() < segment.firstClusterElement->startOffset() ? ElementPosition::BeforeData
1105 if (newCuesPos == ElementPosition::Keep) {
1106 newCuesPos = currentCuesPos;
1107 }
1108 } else if (newCuesPos == ElementPosition::Keep) {
1109 newCuesPos = ElementPosition::BeforeData;
1110 }
1111
1112 // set start offset of the segment in the new file
1113 segment.startOffset = currentOffset;
1114
1115 // check whether the segment has a CRC-32 element
1116 segment.hasCrc32 = level0Element->firstChild() && level0Element->firstChild()->id() == EbmlIds::Crc32;
1117
1118 // precalculate the size of the segment
1119 calculateSegmentSize:
1120
1121 // pretent writing "CRC-32"-element (which either present and 6 byte long or omitted)
1122 segment.totalDataSize = segment.hasCrc32 ? 6 : 0;
1123
1124 // pretend writing "SeekHead"-element
1125 segment.totalDataSize += segment.seekInfo.actualSize();
1126
1127 // pretend writing "SegmentInfo"-element
1128 for (level1Element = level0Element->childById(MatroskaIds::SegmentInfo, diag), index = 0; level1Element;
1129 level1Element = level1Element->siblingById(MatroskaIds::SegmentInfo, diag), ++index) {
1130 // update offset in "SeekHead"-element
1131 if (segment.seekInfo.push(index, MatroskaIds::SegmentInfo, currentPosition + segment.totalDataSize)) {
1132 goto calculateSegmentSize;
1133 } else {
1134 // add size of "SegmentInfo"-element
1135 // -> size of "MuxingApp"- and "WritingApp"-element
1136 segment.infoDataSize = muxingAppElementTotalSize + writingAppElementTotalSize;
1137 // -> add size of "Title"-element
1138 if (segmentIndex < m_titles.size()) {
1139 const auto &title = m_titles[segmentIndex];
1140 if (!title.empty()) {
1141 segment.infoDataSize += 2 + EbmlElement::calculateSizeDenotationLength(title.size()) + title.size();
1142 }
1143 }
1144 // -> add size of other children
1145 for (level2Element = level1Element->firstChild(); level2Element; level2Element = level2Element->nextSibling()) {
1146 level2Element->parse(diag);
1147 switch (level2Element->id()) {
1148 case EbmlIds::Void: // skipped
1149 case EbmlIds::Crc32: // skipped
1150 case MatroskaIds::Title: // calculated separately
1151 case MatroskaIds::MuxingApp: // calculated separately
1152 case MatroskaIds::WrittingApp: // calculated separately
1153 break;
1154 default:
1155 level2Element->makeBuffer();
1156 segment.infoDataSize += level2Element->totalSize();
1157 }
1158 }
1159 // -> calculate total size
1161 }
1162 }
1163
1164 // pretend writing "Tracks"-element
1165 if (trackHeaderSize) {
1166 // update offsets in "SeekHead"-element
1167 if (segment.seekInfo.push(0, MatroskaIds::Tracks, currentPosition + segment.totalDataSize)) {
1168 goto calculateSegmentSize;
1169 } else {
1170 // add size of "Tracks"-element
1171 segment.totalDataSize += trackHeaderSize;
1172 }
1173 }
1174
1175 // pretend writing "Chapters"-element
1176 for (level1Element = level0Element->childById(MatroskaIds::Chapters, diag), index = 0; level1Element;
1177 level1Element = level1Element->siblingById(MatroskaIds::Chapters, diag), ++index) {
1178 // update offset in "SeekHead"-element
1179 if (segment.seekInfo.push(index, MatroskaIds::Chapters, currentPosition + segment.totalDataSize)) {
1180 goto calculateSegmentSize;
1181 } else {
1182 // add size of element
1183 level1Element->makeBuffer();
1184 segment.totalDataSize += level1Element->totalSize();
1185 }
1186 }
1187
1188 // "Tags"- and "Attachments"-element are written in either the first or the last segment
1189 // and either before "Cues"- and "Cluster"-elements or after these elements
1190 // depending on the desired tag position (at the front/at the end)
1191 if (newTagPos == ElementPosition::BeforeData && segmentIndex == 0) {
1192 // pretend writing "Tags"-element
1193 if (tagsSize) {
1194 // update offsets in "SeekHead"-element
1195 if (segment.seekInfo.push(0, MatroskaIds::Tags, currentPosition + segment.totalDataSize)) {
1196 goto calculateSegmentSize;
1197 } else {
1198 // add size of "Tags"-element
1199 segment.totalDataSize += tagsSize;
1200 }
1201 }
1202 // pretend writing "Attachments"-element
1203 if (attachmentsSize) {
1204 // update offsets in "SeekHead"-element
1205 if (segment.seekInfo.push(0, MatroskaIds::Attachments, currentPosition + segment.totalDataSize)) {
1206 goto calculateSegmentSize;
1207 } else {
1208 // add size of "Attachments"-element
1209 segment.totalDataSize += attachmentsSize;
1210 }
1211 }
1212 }
1213
1214 offset = segment.totalDataSize; // save current offset (offset before "Cues"-element)
1215
1216 // pretend writing "Cues"-element
1217 if (newCuesPos == ElementPosition::BeforeData && segment.cuesElement) {
1218 // update offset of "Cues"-element in "SeekHead"-element
1219 if (segment.seekInfo.push(0, MatroskaIds::Cues, currentPosition + segment.totalDataSize)) {
1220 goto calculateSegmentSize;
1221 } else {
1222 // add size of "Cues"-element
1223 progress.updateStep("Calculating cluster offsets and index size ...");
1224 addCuesElementSize:
1225 segment.totalDataSize += segment.cuesUpdater.totalSize();
1226 }
1227 } else {
1228 progress.updateStep("Calculating cluster offsets ...");
1229 }
1230
1231 // decided whether it is necessary to rewrite the entire file (if not already rewriting)
1232 if (!rewriteRequired) {
1233 // find first "Cluster"-element
1234 if ((level1Element = segment.firstClusterElement)) {
1235 // just before the first "Cluster"-element
1236 // -> calculate total offset (excluding size denotation and incomplete index)
1237 totalOffset = currentOffset + 4 + segment.totalDataSize;
1238
1239 if (totalOffset <= segment.firstClusterElement->startOffset()) {
1240 // the padding might be big enough, but
1241 // - the segment might become bigger (subsequent tags and attachments)
1242 // - the header size hasn't been taken into account yet
1243 // - seek information for first cluster and subsequent tags and attachments hasn't been taken into account
1244
1245 // assume the size denotation length doesn't change -> use length from original file
1246 if (level0Element->headerSize() <= 4 || level0Element->headerSize() > 12) {
1247 // validate original header size
1248 diag.emplace_back(DiagLevel::Critical, "Header size of \"Segment\"-element from original file is invalid.", context);
1249 throw InvalidDataException();
1250 }
1251 segment.sizeDenotationLength = static_cast<std::uint8_t>(level0Element->headerSize() - 4u);
1252
1253 nonRewriteCalculations:
1254 // pretend writing "Cluster"-elements assuming there is no rewrite required
1255 // -> update offset in "SeakHead"-element
1256 if (segment.seekInfo.push(
1257 0, MatroskaIds::Cluster, level1Element->startOffset() - 4 - segment.sizeDenotationLength - ebmlHeaderSize)) {
1258 goto calculateSegmentSize;
1259 }
1260 // -> update offset of "Cluster"-element in "Cues"-element and get end offset of last "Cluster"-element
1261 bool cuesInvalidated = false;
1262 for (index = 0; level1Element; level1Element = level1Element->siblingById(MatroskaIds::Cluster, diag), ++index) {
1263 clusterReadOffset = level1Element->startOffset() - level0Element->dataOffset() + readOffset;
1264 segment.clusterEndOffset = level1Element->endOffset();
1265 if (segment.cuesElement
1266 && segment.cuesUpdater.updateOffsets(
1267 clusterReadOffset, level1Element->startOffset() - 4 - segment.sizeDenotationLength - ebmlHeaderSize)
1268 && newCuesPos == ElementPosition::BeforeData) {
1269 cuesInvalidated = true;
1270 }
1271 // check whether aborted (because this loop might take some seconds to process)
1272 progress.stopIfAborted();
1273 // update the progress percentage (using offset / file size should be accurate enough)
1274 if (index % 50 == 0) {
1275 progress.updateStepPercentage(static_cast<std::uint8_t>(level1Element->dataOffset() * 100 / fileInfo().size()));
1276 }
1277 }
1278 if (cuesInvalidated) {
1279 segment.totalDataSize = offset;
1280 goto addCuesElementSize;
1281 }
1282 segment.totalDataSize = segment.clusterEndOffset - currentOffset - 4 - segment.sizeDenotationLength;
1283
1284 // pretend writing "Cues"-element
1285 progress.updateStep("Calculating offsets of elements after cluster ...");
1286 if (newCuesPos == ElementPosition::AfterData && segment.cuesElement) {
1287 // update offset of "Cues"-element in "SeekHead"-element
1288 if (segment.seekInfo.push(0, MatroskaIds::Cues, currentPosition + segment.totalDataSize)) {
1289 goto calculateSegmentSize;
1290 } else {
1291 // add size of "Cues"-element
1292 segment.totalDataSize += segment.cuesUpdater.totalSize();
1293 }
1294 }
1295
1296 if (newTagPos == ElementPosition::AfterData && segmentIndex == lastSegmentIndex) {
1297 // pretend writing "Tags"-element
1298 if (tagsSize) {
1299 // update offsets in "SeekHead"-element
1300 if (segment.seekInfo.push(0, MatroskaIds::Tags, currentPosition + segment.totalDataSize)) {
1301 goto calculateSegmentSize;
1302 } else {
1303 // add size of "Tags"-element
1304 segment.totalDataSize += tagsSize;
1305 }
1306 }
1307 // pretend writing "Attachments"-element
1308 if (attachmentsSize) {
1309 // update offsets in "SeekHead"-element
1310 if (segment.seekInfo.push(0, MatroskaIds::Attachments, currentPosition + segment.totalDataSize)) {
1311 goto calculateSegmentSize;
1312 } else {
1313 // add size of "Attachments"-element
1314 segment.totalDataSize += attachmentsSize;
1315 }
1316 }
1317 }
1318
1319 // calculate total offset again (taking everything into account)
1320 // -> check whether assumed size denotation was correct
1321 if (segment.sizeDenotationLength != (sizeLength = EbmlElement::calculateSizeDenotationLength(segment.totalDataSize))) {
1322 // assumption was wrong -> recalculate with new length
1323 segment.sizeDenotationLength = sizeLength;
1324 level1Element = segment.firstClusterElement;
1325 goto nonRewriteCalculations;
1326 }
1327
1328 totalOffset = currentOffset + 4 + sizeLength + offset;
1329 // offset does not include size of "Cues"-element
1330 if (newCuesPos == ElementPosition::BeforeData) {
1331 totalOffset += segment.cuesUpdater.totalSize();
1332 }
1333 if (totalOffset <= segment.firstClusterElement->startOffset()) {
1334 // calculate new padding
1335 if (segment.newPadding != 1) {
1336 // "Void"-element is at least 2 byte long -> can't add 1 byte padding
1337 newPadding += (segment.newPadding = segment.firstClusterElement->startOffset() - totalOffset);
1338 } else {
1339 rewriteRequired = true;
1340 }
1341 } else {
1342 rewriteRequired = true;
1343 }
1344 } else {
1345 rewriteRequired = true;
1346 }
1347 } else {
1348 diag.emplace_back(DiagLevel::Warning, argsToString("There are no clusters in segment ", segmentIndex, "."), context);
1349 }
1350
1351 if (rewriteRequired) {
1352 if (newTagPos != ElementPosition::AfterData
1353 && (!fileInfo().forceTagPosition()
1354 || (fileInfo().tagPosition() == ElementPosition::Keep && currentTagPos == ElementPosition::Keep))) {
1355 // rewriting might be avoided by writing the tags at the end
1356 newTagPos = ElementPosition::AfterData;
1357 rewriteRequired = false;
1358 } else if (newCuesPos != ElementPosition::AfterData
1360 || (fileInfo().indexPosition() == ElementPosition::Keep && currentCuesPos == ElementPosition::Keep))) {
1361 // rewriting might be avoided by writing the cues at the end
1362 newCuesPos = ElementPosition::AfterData;
1363 rewriteRequired = false;
1364 }
1365 // do calculations again for rewriting / changed element order
1366 goto calculateSegmentData;
1367 }
1368 } else {
1369 // if rewrite is required, pretend writing the remaining elements to compute total segment size
1370
1371 // pretend writing "Void"-element (only if there is at least one "Cluster"-element in the segment)
1372 if (!segmentIndex && rewriteRequired && (level1Element = level0Element->childById(MatroskaIds::Cluster, diag))) {
1373 // simply use the preferred padding
1374 segment.totalDataSize += (segment.newPadding = newPadding = fileInfo().preferredPadding());
1375 }
1376
1377 // pretend writing "Cluster"-element
1378 segment.clusterSizes.clear();
1379 bool cuesInvalidated = false;
1380 for (index = 0; level1Element; level1Element = level1Element->siblingById(MatroskaIds::Cluster, diag), ++index) {
1381 // update offset of "Cluster"-element in "Cues"-element
1382 clusterReadOffset = level1Element->startOffset() - level0Element->dataOffset() + readOffset;
1383 if (segment.cuesElement && segment.cuesUpdater.updateOffsets(clusterReadOffset, currentPosition + segment.totalDataSize)
1384 && newCuesPos == ElementPosition::BeforeData) {
1385 cuesInvalidated = true;
1386 } else {
1387 if (index == 0 && segment.seekInfo.push(index, MatroskaIds::Cluster, currentPosition + segment.totalDataSize)) {
1388 goto calculateSegmentSize;
1389 } else {
1390 // add size of "Cluster"-element
1391 clusterSize = clusterReadSize = 0;
1392 for (level2Element = level1Element->firstChild(); level2Element; level2Element = level2Element->nextSibling()) {
1393 level2Element->parse(diag);
1394 if (segment.cuesElement
1395 && segment.cuesUpdater.updateRelativeOffsets(clusterReadOffset, clusterReadSize, clusterSize)
1396 && newCuesPos == ElementPosition::BeforeData) {
1397 cuesInvalidated = true;
1398 }
1399 switch (level2Element->id()) {
1400 case EbmlIds::Void:
1401 case EbmlIds::Crc32:
1402 break;
1404 clusterSize += 1u + 1u + EbmlElement::calculateUIntegerLength(currentPosition + segment.totalDataSize);
1405 break;
1406 default:
1407 clusterSize += level2Element->totalSize();
1408 }
1409 clusterReadSize += level2Element->totalSize();
1410 }
1411 segment.clusterSizes.push_back(clusterSize);
1412 segment.totalDataSize += 4u + EbmlElement::calculateSizeDenotationLength(clusterSize) + clusterSize;
1413 }
1414 }
1415 // check whether aborted (because this loop might take some seconds to process)
1416 progress.stopIfAborted();
1417 // update the progress percentage (using offset / file size should be accurate enough)
1418 if ((index % 50 == 0) && fileInfo().size()) {
1419 progress.updateStepPercentage(static_cast<std::uint8_t>(level1Element->dataOffset() * 100 / fileInfo().size()));
1420 }
1421 // TODO: reduce code duplication for aborting and progress updates
1422 }
1423 // check whether the total size of the "Cues"-element has been invalidated and recompute cluster if required
1424 if (cuesInvalidated) {
1425 // reset element size to previously saved offset of "Cues"-element
1426 segment.totalDataSize = offset;
1427 goto addCuesElementSize;
1428 }
1429
1430 // pretend writing "Cues"-element
1431 progress.updateStep("Calculating offsets of elements after cluster ...");
1432 if (newCuesPos == ElementPosition::AfterData && segment.cuesElement) {
1433 // update offset of "Cues"-element in "SeekHead"-element
1434 if (segment.seekInfo.push(0, MatroskaIds::Cues, currentPosition + segment.totalDataSize)) {
1435 goto calculateSegmentSize;
1436 } else {
1437 // add size of "Cues"-element
1438 segment.totalDataSize += segment.cuesUpdater.totalSize();
1439 }
1440 }
1441
1442 // "Tags"- and "Attachments"-element are written in either the first or the last segment
1443 // and either before "Cues"- and "Cluster"-elements or after these elements
1444 // depending on the desired tag position (at the front/at the end)
1445 if (newTagPos == ElementPosition::AfterData && segmentIndex == lastSegmentIndex) {
1446 // pretend writing "Tags"-element
1447 if (tagsSize) {
1448 // update offsets in "SeekHead"-element
1449 if (segment.seekInfo.push(0, MatroskaIds::Tags, currentPosition + segment.totalDataSize)) {
1450 goto calculateSegmentSize;
1451 } else {
1452 // add size of "Tags"-element
1453 segment.totalDataSize += tagsSize;
1454 }
1455 }
1456 // pretend writing "Attachments"-element
1457 if (attachmentsSize) {
1458 // update offsets in "SeekHead"-element
1459 if (segment.seekInfo.push(0, MatroskaIds::Attachments, currentPosition + segment.totalDataSize)) {
1460 goto calculateSegmentSize;
1461 } else {
1462 // add size of "Attachments"-element
1463 segment.totalDataSize += attachmentsSize;
1464 }
1465 }
1466 }
1467 }
1468
1469 // increase the current segment index
1470 ++segmentIndex;
1471
1472 // increase write offsets by the size of the segment which size has just been computed
1474 currentPosition += segment.totalSize;
1475 currentOffset += segment.totalSize;
1476
1477 // increase the read offset by the size of the segment read from the original file
1478 readOffset += level0Element->totalSize();
1479
1480 break;
1481 }
1482 default:
1483 // just copy any unknown top-level elements
1484 diag.emplace_back(DiagLevel::Warning,
1485 "The top-level element \"" % level0Element->idToString() + "\" of the original file is unknown and will just be copied.",
1486 context);
1487 currentOffset += level0Element->totalSize();
1488 readOffset += level0Element->totalSize();
1489 }
1490 }
1491
1492 if (!rewriteRequired) {
1493 // check whether the new padding is ok according to specifications
1494 if ((rewriteRequired = (newPadding > fileInfo().maxPadding() || newPadding < fileInfo().minPadding()))) {
1495 // need to recalculate segment data for rewrite
1496 goto calculateSegmentData;
1497 }
1498 }
1499
1500 } catch (const OperationAbortedException &) {
1501 diag.emplace_back(DiagLevel::Information, "Applying new tag information has been aborted.", context);
1502 throw;
1503 } catch (const Failure &) {
1504 diag.emplace_back(DiagLevel::Critical, "Parsing the original file failed.", context);
1505 throw;
1506 } catch (const std::ios_base::failure &failure) {
1507 diag.emplace_back(DiagLevel::Critical, argsToString("An IO error occurred when parsing the original file: ", failure.what()), context);
1508 throw;
1509 }
1510
1511 // setup stream(s) for writing
1512 // -> update status
1513 progress.nextStepOrStop("Preparing streams ...");
1514
1515 // -> define variables needed to handle output stream and backup stream (required when rewriting the file)
1516 string originalPath = fileInfo().path(), backupPath;
1517 NativeFileStream &outputStream = fileInfo().stream();
1518 NativeFileStream backupStream; // create a stream to open the backup/original file for the case rewriting the file is required
1519 BinaryWriter outputWriter(&outputStream);
1520 char buff[8]; // buffer used to make size denotations
1521
1522 if (rewriteRequired) {
1523 if (fileInfo().saveFilePath().empty()) {
1524 // move current file to temp dir and reopen it as backupStream, recreate original file
1525 try {
1526 BackupHelper::createBackupFileCanonical(fileInfo().backupDirectory(), originalPath, backupPath, outputStream, backupStream);
1527 // recreate original file, define buffer variables
1528 outputStream.open(originalPath, ios_base::out | ios_base::binary | ios_base::trunc);
1529 } catch (const std::ios_base::failure &failure) {
1530 diag.emplace_back(
1531 DiagLevel::Critical, argsToString("Creation of temporary file (to rewrite the original file) failed: ", failure.what()), context);
1532 throw;
1533 }
1534 } else {
1535 // open the current file as backupStream and create a new outputStream at the specified "save file path"
1536 try {
1537 backupStream.exceptions(ios_base::badbit | ios_base::failbit);
1538 backupStream.open(BasicFileInfo::pathForOpen(fileInfo().path()).data(), ios_base::in | ios_base::binary);
1539 fileInfo().close();
1540 outputStream.open(BasicFileInfo::pathForOpen(fileInfo().saveFilePath()).data(), ios_base::out | ios_base::binary | ios_base::trunc);
1541 } catch (const std::ios_base::failure &failure) {
1542 diag.emplace_back(DiagLevel::Critical, argsToString("Opening streams to write output file failed: ", failure.what()), context);
1543 throw;
1544 }
1545 }
1546
1547 // set backup stream as associated input stream since we need the original elements to write the new file
1548 setStream(backupStream);
1549
1550 // TODO: reduce code duplication
1551
1552 } else { // !rewriteRequired
1553 // buffer currently assigned attachments
1554 for (auto &maker : attachmentMaker) {
1555 maker.bufferCurrentAttachments(diag);
1556 }
1557
1558 // reopen original file to ensure it is opened for writing
1559 try {
1560 fileInfo().close();
1561 outputStream.open(fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary);
1562 } catch (const std::ios_base::failure &failure) {
1563 diag.emplace_back(DiagLevel::Critical, argsToString("Opening the file with write permissions failed: ", failure.what()), context);
1564 throw;
1565 }
1566 }
1567
1568 // start actual writing
1569 try {
1570 // write EBML header
1571 progress.nextStepOrStop("Writing EBML header ...");
1572 outputWriter.writeUInt32BE(EbmlIds::Header);
1573 sizeLength = EbmlElement::makeSizeDenotation(ebmlHeaderDataSize, buff);
1574 outputStream.write(buff, sizeLength);
1577 EbmlElement::makeSimpleElement(outputStream, EbmlIds::MaxIdLength, m_maxIdLength);
1578 EbmlElement::makeSimpleElement(outputStream, EbmlIds::MaxSizeLength, m_maxSizeLength);
1582
1583 // iterates through all level 0 elements of the original file
1584 for (level0Element = firstElement(), segmentIndex = 0, currentPosition = 0; level0Element; level0Element = level0Element->nextSibling()) {
1585
1586 // write all level 0 elements of the original file
1587 switch (level0Element->id()) {
1588 case EbmlIds::Header:
1589 // header has already been written -> skip it here
1590 break;
1591
1592 case EbmlIds::Void:
1593 case EbmlIds::Crc32:
1594 // level 0 "Void"- and "Checksum"-elements are omitted
1595 break;
1596
1597 case MatroskaIds::Segment: {
1598 // get reference to the current segment data instance
1599 SegmentData &segment = segmentData[segmentIndex];
1600
1601 // write "Segment"-element actually
1602 progress.updateStep("Writing segment header ...");
1603 outputWriter.writeUInt32BE(MatroskaIds::Segment);
1604 sizeLength = EbmlElement::makeSizeDenotation(segment.totalDataSize, buff);
1605 outputStream.write(buff, sizeLength);
1606 segment.newDataOffset = offset = static_cast<std::uint64_t>(outputStream.tellp()); // store segment data offset here
1607
1608 // write CRC-32 element ...
1609 if (segment.hasCrc32) {
1610 // ... if the original element had a CRC-32 element
1611 *buff = static_cast<char>(EbmlIds::Crc32);
1612 *(buff + 1) = static_cast<char>(0x84); // length denotation: 4 byte
1613 // set the value after writing the element
1614 crc32Offsets.emplace_back(outputStream.tellp(), segment.totalDataSize);
1615 outputStream.write(buff, 6);
1616 }
1617
1618 // write "SeekHead"-element (except there is no seek information for the current segment)
1619 segment.seekInfo.make(outputStream, diag);
1620
1621 // write "SegmentInfo"-element
1622 for (level1Element = level0Element->childById(MatroskaIds::SegmentInfo, diag); level1Element;
1623 level1Element = level1Element->siblingById(MatroskaIds::SegmentInfo, diag)) {
1624 // -> write ID and size
1625 outputWriter.writeUInt32BE(MatroskaIds::SegmentInfo);
1626 sizeLength = EbmlElement::makeSizeDenotation(segment.infoDataSize, buff);
1627 outputStream.write(buff, sizeLength);
1628 // -> write children
1629 for (level2Element = level1Element->firstChild(); level2Element; level2Element = level2Element->nextSibling()) {
1630 switch (level2Element->id()) {
1631 case EbmlIds::Void: // skipped
1632 case EbmlIds::Crc32: // skipped
1633 case MatroskaIds::Title: // written separately
1634 case MatroskaIds::MuxingApp: // written separately
1635 case MatroskaIds::WrittingApp: // written separately
1636 break;
1637 default:
1638 level2Element->copyBuffer(outputStream);
1639 level2Element->discardBuffer();
1640 }
1641 }
1642 // -> write "Title"-element
1643 if (segmentIndex < m_titles.size()) {
1644 const auto &title = m_titles[segmentIndex];
1645 if (!title.empty()) {
1647 }
1648 }
1649 // -> write "MuxingApp"- and "WritingApp"-element
1650 EbmlElement::makeSimpleElement(outputStream, MatroskaIds::MuxingApp, muxingAppName);
1651 EbmlElement::makeSimpleElement(outputStream, MatroskaIds::WrittingApp, writingAppName);
1652 }
1653
1654 // write "Tracks"-element
1655 if (trackHeaderElementsSize) {
1656 outputWriter.writeUInt32BE(MatroskaIds::Tracks);
1657 sizeLength = EbmlElement::makeSizeDenotation(trackHeaderElementsSize, buff);
1658 outputStream.write(buff, sizeLength);
1659 for (auto &maker : trackHeaderMaker) {
1660 maker.make(outputStream);
1661 }
1662 }
1663
1664 // write "Chapters"-element
1665 for (level1Element = level0Element->childById(MatroskaIds::Chapters, diag); level1Element;
1666 level1Element = level1Element->siblingById(MatroskaIds::Chapters, diag)) {
1667 level1Element->copyBuffer(outputStream);
1668 level1Element->discardBuffer();
1669 }
1670
1671 if (newTagPos == ElementPosition::BeforeData && segmentIndex == 0) {
1672 // write "Tags"-element
1673 if (tagsSize) {
1674 outputWriter.writeUInt32BE(MatroskaIds::Tags);
1675 sizeLength = EbmlElement::makeSizeDenotation(tagElementsSize, buff);
1676 outputStream.write(buff, sizeLength);
1677 for (auto &maker : tagMaker) {
1678 maker.make(outputStream);
1679 }
1680 }
1681 // write "Attachments"-element
1682 if (attachmentsSize) {
1683 outputWriter.writeUInt32BE(MatroskaIds::Attachments);
1684 sizeLength = EbmlElement::makeSizeDenotation(attachedFileElementsSize, buff);
1685 outputStream.write(buff, sizeLength);
1686 for (auto &maker : attachmentMaker) {
1687 maker.make(outputStream, diag);
1688 }
1689 }
1690 }
1691
1692 // write "Cues"-element
1693 if (newCuesPos == ElementPosition::BeforeData && segment.cuesElement) {
1694 segment.cuesUpdater.make(outputStream, diag);
1695 }
1696
1697 // write padding / "Void"-element
1698 if (segment.newPadding) {
1699 // calculate length
1700 std::uint64_t voidLength;
1701 if (segment.newPadding < 64) {
1702 sizeLength = 1;
1703 *buff = static_cast<char>(voidLength = segment.newPadding - 2) | static_cast<char>(0x80);
1704 } else {
1705 sizeLength = 8;
1706 BE::getBytes(static_cast<std::uint64_t>((voidLength = segment.newPadding - 9) | 0x100000000000000), buff);
1707 }
1708 // write header
1709 outputWriter.writeByte(EbmlIds::Void);
1710 outputStream.write(buff, sizeLength);
1711 // write zeroes
1712 for (; voidLength; --voidLength) {
1713 outputStream.put(0);
1714 }
1715 }
1716
1717 // write media data / "Cluster"-elements
1718 level1Element = level0Element->childById(MatroskaIds::Cluster, diag);
1719 if (rewriteRequired) {
1720 // update status, check whether the operation has been aborted
1721 progress.nextStepOrStop("Writing cluster ...",
1722 static_cast<std::uint8_t>((static_cast<std::uint64_t>(outputStream.tellp()) - offset) * 100 / segment.totalDataSize));
1723 // write "Cluster"-element
1724 auto clusterSizesIterator = segment.clusterSizes.cbegin();
1725 unsigned int index = 0;
1726 for (; level1Element; level1Element = level1Element->siblingById(MatroskaIds::Cluster, diag), ++clusterSizesIterator, ++index) {
1727 // calculate position of cluster in segment
1728 clusterSize = currentPosition + (static_cast<std::uint64_t>(outputStream.tellp()) - offset);
1729 // write header; checking whether clusterSizesIterator is valid shouldn't be necessary
1730 outputWriter.writeUInt32BE(MatroskaIds::Cluster);
1731 sizeLength = EbmlElement::makeSizeDenotation(*clusterSizesIterator, buff);
1732 outputStream.write(buff, sizeLength);
1733 // write children
1734 for (level2Element = level1Element->firstChild(); level2Element; level2Element = level2Element->nextSibling()) {
1735 switch (level2Element->id()) {
1736 case EbmlIds::Void:
1737 case EbmlIds::Crc32:
1738 break;
1740 EbmlElement::makeSimpleElement(outputStream, MatroskaIds::Position, clusterSize);
1741 break;
1742 default:
1743 level2Element->copyEntirely(outputStream, diag, nullptr);
1744 }
1745 }
1746 // update percentage, check whether the operation has been aborted
1747 progress.stopIfAborted();
1748 if (index % 50 == 0) {
1749 progress.updateStepPercentage(
1750 static_cast<std::uint8_t>((static_cast<std::uint64_t>(outputStream.tellp()) - offset) * 100 / segment.totalDataSize));
1751 }
1752 }
1753 } else {
1754 // can't just skip existing "Cluster"-elements: "Position"-elements must be updated
1755 progress.nextStepOrStop("Updating cluster ...",
1756 static_cast<std::uint8_t>((static_cast<std::uint64_t>(outputStream.tellp()) - offset) * 100 / segment.totalDataSize));
1757 for (; level1Element; level1Element = level1Element->nextSibling()) {
1758 for (level2Element = level1Element->firstChild(); level2Element; level2Element = level2Element->nextSibling()) {
1759 switch (level2Element->id()) {
1761 // calculate new position
1762 sizeLength = EbmlElement::makeUInteger(level1Element->startOffset() - segmentData.front().newDataOffset, buff,
1763 level2Element->dataSize() > 8 ? 8 : static_cast<std::uint8_t>(level2Element->dataSize()));
1764 // new position can only applied if it doesn't need more bytes than the previous position
1765 if (level2Element->dataSize() < sizeLength) {
1766 // can't update position -> void position elements ("Position"-elements seem a bit useless anyways)
1767 outputStream.seekp(static_cast<streamoff>(level2Element->startOffset()));
1768 outputStream.put(static_cast<char>(EbmlIds::Void));
1769 } else {
1770 // update position
1771 outputStream.seekp(static_cast<streamoff>(level2Element->dataOffset()));
1772 outputStream.write(buff, sizeLength);
1773 }
1774 break;
1775 default:;
1776 }
1777 }
1778 }
1779 // skip existing "Cluster"-elements
1780 outputStream.seekp(static_cast<streamoff>(segment.clusterEndOffset));
1781 }
1782
1783 progress.updateStep("Writing segment tail ...");
1784
1785 // write "Cues"-element
1786 if (newCuesPos == ElementPosition::AfterData && segment.cuesElement) {
1787 segment.cuesUpdater.make(outputStream, diag);
1788 }
1789
1790 if (newTagPos == ElementPosition::AfterData && segmentIndex == lastSegmentIndex) {
1791 // write "Tags"-element
1792 if (tagsSize) {
1793 outputWriter.writeUInt32BE(MatroskaIds::Tags);
1794 sizeLength = EbmlElement::makeSizeDenotation(tagElementsSize, buff);
1795 outputStream.write(buff, sizeLength);
1796 for (auto &maker : tagMaker) {
1797 maker.make(outputStream);
1798 }
1799 }
1800 // write "Attachments"-element
1801 if (attachmentsSize) {
1802 outputWriter.writeUInt32BE(MatroskaIds::Attachments);
1803 sizeLength = EbmlElement::makeSizeDenotation(attachedFileElementsSize, buff);
1804 outputStream.write(buff, sizeLength);
1805 for (auto &maker : attachmentMaker) {
1806 maker.make(outputStream, diag);
1807 }
1808 }
1809 }
1810
1811 // increase the current segment index
1812 ++segmentIndex;
1813
1814 // increase write offsets by the size of the segment which has just been written
1815 currentPosition += segment.totalSize;
1816
1817 break;
1818 }
1819 default:
1820 // just copy any unknown top-level elements
1821 level0Element->copyEntirely(outputStream, diag, nullptr);
1822 currentPosition += level0Element->totalSize();
1823 }
1824 }
1825
1826 // reparse what is written so far
1827 progress.updateStep("Reparsing output file ...");
1828 if (rewriteRequired) {
1829 // report new size
1830 fileInfo().reportSizeChanged(static_cast<std::uint64_t>(outputStream.tellp()));
1831
1832 // "save as path" is now the regular path
1833 if (!fileInfo().saveFilePath().empty()) {
1834 fileInfo().reportPathChanged(fileInfo().saveFilePath());
1835 fileInfo().setSaveFilePath(string());
1836 }
1837
1838 // the outputStream needs to be reopened to be able to read again
1839 outputStream.close();
1840 outputStream.open(fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary);
1841 setStream(outputStream);
1842 } else {
1843 const auto newSize = static_cast<std::uint64_t>(outputStream.tellp());
1844 if (newSize < fileInfo().size()) {
1845 // file is smaller after the modification -> truncate
1846 // -> close stream before truncating
1847 outputStream.close();
1848 // -> truncate file
1849 auto ec = std::error_code();
1850 std::filesystem::resize_file(makeNativePath(fileInfo().path()), newSize, ec);
1851 if (!ec) {
1852 fileInfo().reportSizeChanged(newSize);
1853 } else {
1854 diag.emplace_back(DiagLevel::Critical, "Unable to truncate the file: " + ec.message(), context);
1855 }
1856 // -> reopen the stream again
1857 outputStream.open(fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary);
1858 } else {
1859 // file is longer after the modification -> just report new size
1860 fileInfo().reportSizeChanged(newSize);
1861 }
1862 }
1863 reset();
1864 try {
1865 parseHeader(diag, progress);
1866 } catch (const OperationAbortedException &) {
1867 throw;
1868 } catch (const Failure &) {
1869 diag.emplace_back(DiagLevel::Critical, "Unable to reparse the header of the new file.", context);
1870 throw;
1871 }
1872
1873 // update CRC-32 checksums
1874 if (!crc32Offsets.empty()) {
1875 progress.updateStep("Updating CRC-32 checksums ...");
1876 for (const auto &crc32Offset : crc32Offsets) {
1877 outputStream.seekg(static_cast<streamoff>(get<0>(crc32Offset) + 6));
1878 outputStream.seekp(static_cast<streamoff>(get<0>(crc32Offset) + 2));
1879 writer().writeUInt32LE(reader().readCrc32(get<1>(crc32Offset) - 6));
1880 }
1881 }
1882
1883 // prevent deferring final write operations (to catch and handle possible errors here)
1884 outputStream.flush();
1885
1886 // handle errors (which might have been occurred after renaming/creating backup file)
1887 } catch (...) {
1888 BackupHelper::handleFailureAfterFileModifiedCanonical(fileInfo(), originalPath, backupPath, outputStream, backupStream, diag, context);
1889 }
1890}
1891
1892} // 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.
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.
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.
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.
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.
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)