Tag Parser: matroska/matroskacontainer.cpp Source File

Tag Parser 12.5.2 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.
1#include "./matroskacontainer.h"
2#include "./ebmlid.h"
3#include "./matroskacues.h"
4#include "./matroskaeditionentry.h"
5#include "./matroskaid.h"
6#include "./matroskaseekinfo.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
37
41MatroskaContainer::MatroskaContainer(MediaFileInfo &fileInfo, std::uint64_t startOffset)
42 : GenericContainer<MediaFileInfo, MatroskaTag, MatroskaTrack, EbmlElement>(fileInfo, 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";
50 m_doctypeVersion = 1;
51 m_doctypeReadVersion = 1;
52}
53
54MatroskaContainer::~MatroskaContainer()
55{
56}
57
58 void MatroskaContainer::reset()
59{
60 GenericContainer<MediaFileInfo, MatroskaTag, MatroskaTrack, EbmlElement>::reset();
61 m_maxIdLength = 4;
62 m_maxSizeLength = 8;
63 m_version = 1;
64 m_readVersion = 1;
65 m_doctype = "matroska";
66 m_doctypeVersion = 1;
67 m_doctypeReadVersion = 1;
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
83 void MatroskaContainer::validateIndex(Diagnostics &diag, AbortableProgressFeedback &progress)
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;
105 case MatroskaIds::Cues:
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;
117 case MatroskaIds::CuePoint:
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()) {
123 case MatroskaIds::CueTime:
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;
132 case MatroskaIds::CueTrackPositions:
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()) {
140 case MatroskaIds::CueTrack:
141 case MatroskaIds::CueClusterPosition:
142 case MatroskaIds::CueRelativePosition:
143 case MatroskaIds::CueDuration:
144 case MatroskaIds::CueBlockNumber:
145 case MatroskaIds::CueCodecState:
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:
157 case MatroskaIds::CueReference:
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:
167 case MatroskaIds::CueTrack:
168 break;
169 case MatroskaIds::CueClusterPosition:
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;
185 case MatroskaIds::CueRelativePosition:
186 // read "Block" position denoted by "CueRelativePosition"-element (validate later since the "Cluster"-element is needed to validate)
187 pos = subElement->readUInteger();
188 break;
189 case MatroskaIds::CueDuration:
190 break;
191 case MatroskaIds::CueBlockNumber:
192 break;
193 case MatroskaIds::CueCodecState:
194 break;
195 case MatroskaIds::CueReference:
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()) {
214 case MatroskaIds::SimpleBlock:
215 case MatroskaIds::Block:
216 case MatroskaIds::BlockGroup:
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;
252 case MatroskaIds::Cluster:
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;
261 case MatroskaIds::Position:
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;
271 case MatroskaIds::PrevSize:
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
300 bool sameOffset(std::uint64_t offset, const EbmlElement *element)
301{
302 return element->startOffset() == offset;
303}
304
309 inline 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
314 MatroskaChapter *MatroskaContainer::chapter(std::size_t index)
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
327 size_t MatroskaContainer::chapterCount() const
328{
329 size_t count = 0;
330 for (const auto &entry : m_editionEntries) {
331 count += entry->chapters().size();
332 }
333 return count;
334}
335
336 MatroskaAttachment *MatroskaContainer::createAttachment()
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
365 ElementPosition MatroskaContainer::determineElementPosition(std::uint64_t elementId, Diagnostics &diag) const
366{
367 if (!m_firstElement || m_segmentCount != 1) {
368 return ElementPosition::Keep;
369 }
370 const auto *const segmentElement = m_firstElement->siblingByIdIncludingThis(MatroskaIds::Segment, diag);
371 if (!segmentElement) {
372 return ElementPosition::Keep;
373 }
374 for (const EbmlElement *childElement = segmentElement->firstChild(); childElement; childElement = childElement->nextSibling()) {
375 if (childElement->id() == elementId) {
376 return ElementPosition::BeforeData;
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) {
381 return ElementPosition::AfterData;
382 }
383 }
384 }
385 return ElementPosition::Keep;
386 }
387 }
388 return ElementPosition::Keep;
389}
390
391 ElementPosition MatroskaContainer::determineTagPosition(Diagnostics &diag) const
392{
393 return determineElementPosition(MatroskaIds::Tags, diag);
394}
395
396 ElementPosition MatroskaContainer::determineIndexPosition(Diagnostics &diag) const
397{
398 return determineElementPosition(MatroskaIds::Cues, diag);
399}
400
401 void MatroskaContainer::internalParseHeader(Diagnostics &diag, AbortableProgressFeedback &progress)
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;
430 case EbmlIds::ReadVersion:
431 m_readVersion = subElement->readUInteger();
432 break;
433 case EbmlIds::DocType:
434 m_doctype = subElement->readString();
435 break;
436 case EbmlIds::DocTypeVersion:
437 m_doctypeVersion = subElement->readUInteger();
438 break;
439 case EbmlIds::DocTypeReadVersion:
440 m_doctypeReadVersion = subElement->readUInteger();
441 break;
442 case EbmlIds::MaxIdLength:
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;
452 case EbmlIds::MaxSizeLength:
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;
469 case MatroskaIds::Segment:
470 ++m_segmentCount;
471 for (EbmlElement *subElement = topLevelElement->firstChild(); subElement; subElement = subElement->nextSibling()) {
472 try {
473 subElement->parse(diag);
474 switch (subElement->id()) {
475 case MatroskaIds::SeekHead:
476 m_seekInfos.emplace_back(make_unique<MatroskaSeekInfo>());
477 m_seekInfos.back()->parse(subElement, diag);
478 break;
479 case MatroskaIds::Tracks:
480 if (excludesOffset(m_tracksElements, subElement->startOffset())) {
481 m_tracksElements.push_back(subElement);
482 }
483 break;
484 case MatroskaIds::SegmentInfo:
485 if (excludesOffset(m_segmentInfoElements, subElement->startOffset())) {
486 m_segmentInfoElements.push_back(subElement);
487 }
488 break;
489 case MatroskaIds::Tags:
490 if (excludesOffset(m_tagsElements, subElement->startOffset())) {
491 m_tagsElements.push_back(subElement);
492 }
493 break;
494 case MatroskaIds::Chapters:
495 if (excludesOffset(m_chaptersElements, subElement->startOffset())) {
496 m_chaptersElements.push_back(subElement);
497 }
498 break;
499 case MatroskaIds::Attachments:
500 if (excludesOffset(m_attachmentsElements, subElement->startOffset())) {
501 m_attachmentsElements.push_back(subElement);
502 }
503 break;
504 case MatroskaIds::Cluster:
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()) {
525 case MatroskaIds::SegmentInfo:
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;
531 case MatroskaIds::Tracks:
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;
537 case MatroskaIds::Tags:
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;
543 case MatroskaIds::Chapters:
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;
549 case MatroskaIds::Attachments:
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 }
610 m_duration = TimeSpan();
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()) {
620 case MatroskaIds::Title:
621 m_titles.emplace_back(subElement->readString());
622 hasTitle = true;
623 break;
624 case MatroskaIds::Duration:
625 rawDuration = subElement->readFloat();
626 break;
627 case MatroskaIds::TimeCodeScale:
628 timeScale = subElement->readUInteger();
629 break;
630 case MatroskaIds::MuxingApp:
631 muxingApplications().emplace_back(subElement->readString());
632 break;
633 case MatroskaIds::WrittingApp:
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()) {
661 track->readStatisticsFromTags(tags(), diag);
662 }
663}
664
665 void MatroskaContainer::internalParseTags(Diagnostics &diag, AbortableProgressFeedback &progress)
666{
667 CPP_UTILITIES_UNUSED(progress)
668
669 static const string context("parsing tags of Matroska container");
670 auto flags = MatroskaTagFlags::None;
671 if (fileInfo().fileHandlingFlags() & MediaFileHandlingFlags::NormalizeKnownTagFieldIds) {
672 flags += MatroskaTagFlags::NormalizeKnownFieldIds;
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
706 void MatroskaContainer::internalParseTracks(Diagnostics &diag, AbortableProgressFeedback &progress)
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()) {
715 case MatroskaIds::TrackEntry:
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
742 void MatroskaContainer::internalParseChapters(Diagnostics &diag, AbortableProgressFeedback &progress)
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()) {
751 case MatroskaIds::EditionEntry:
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
776 void MatroskaContainer::internalParseAttachments(Diagnostics &diag, AbortableProgressFeedback &progress)
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()) {
787 case MatroskaIds::AttachedFile:
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
813 struct SegmentData {
815 SegmentData()
816 : hasCrc32(false)
817 , cuesElement(nullptr)
818 , infoDataSize(0)
819 , firstClusterElement(nullptr)
820 , clusterEndOffset(0)
821 , startOffset(0)
822 , newPadding(0)
823 , totalDataSize(0)
824 , totalSize(0)
825 , newDataOffset(0)
826 , sizeDenotationLength(0)
827 {
828 }
829 SegmentData(SegmentData &&) = default;
830
832 bool hasCrc32;
834 MatroskaSeekInfo seekInfo;
836 EbmlElement *cuesElement;
838 MatroskaCuePositionUpdater cuesUpdater;
840 std::uint64_t infoDataSize;
842 std::vector<std::uint64_t> clusterSizes;
844 EbmlElement *firstClusterElement;
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;
858 std::uint8_t sizeDenotationLength;
859};
860
861 void MatroskaContainer::internalMakeFile(Diagnostics &diag, AbortableProgressFeedback &progress)
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()) {
872 case ParsingStatus::Ok:
873 case ParsingStatus::NotSupported:
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)
924 ElementPosition currentTagPos = ElementPosition::Keep;
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()) {
1016 case MatroskaIds::Segment:
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:
1023 case MatroskaIds::Attachments:
1024 firstTagFound = true;
1025 break;
1026 case MatroskaIds::Cluster:
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
1105 : ElementPosition::AfterData;
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
1161 segment.totalDataSize += 4 + EbmlElement::calculateSizeDenotationLength(segment.infoDataSize) + segment.infoDataSize;
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;
1404 case MatroskaIds::Position:
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
1474 segment.totalSize = 4 + EbmlElement::calculateSizeDenotationLength(segment.totalDataSize) + segment.totalDataSize;
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);
1576 EbmlElement::makeSimpleElement(outputStream, EbmlIds::Version, m_version);
1577 EbmlElement::makeSimpleElement(outputStream, EbmlIds::ReadVersion, m_readVersion);
1578 EbmlElement::makeSimpleElement(outputStream, EbmlIds::MaxIdLength, m_maxIdLength);
1579 EbmlElement::makeSimpleElement(outputStream, EbmlIds::MaxSizeLength, m_maxSizeLength);
1580 EbmlElement::makeSimpleElement(outputStream, EbmlIds::DocType, m_doctype);
1581 EbmlElement::makeSimpleElement(outputStream, EbmlIds::DocTypeVersion, m_doctypeVersion);
1582 EbmlElement::makeSimpleElement(outputStream, EbmlIds::DocTypeReadVersion, m_doctypeReadVersion);
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()) {
1647 EbmlElement::makeSimpleElement(outputStream, MatroskaIds::Title, title);
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;
1738 case MatroskaIds::Position:
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()) {
1759 case MatroskaIds::Position:
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
backuphelper.h
TagParser::AbortableProgressFeedback
The AbortableProgressFeedback class provides feedback about an ongoing operation via callbacks.
Definition progressfeedback.h:186
TagParser::AbortableProgressFeedback::stopIfAborted
void stopIfAborted() const
Throws an OperationAbortedException if aborted.
Definition progressfeedback.h:255
TagParser::AbortableProgressFeedback::nextStepOrStop
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.
Definition progressfeedback.h:266
TagParser::AbstractContainer::startOffset
std::uint64_t startOffset() const
Returns the start offset in the related stream.
Definition abstractcontainer.h:154
TagParser::AbstractContainer::m_titles
std::vector< std::string > m_titles
Definition abstractcontainer.h:110
TagParser::AbstractContainer::writingApplications
const std::vector< std::string > & writingApplications() const
Returns the writing applications specified as meta-data.
Definition abstractcontainer.cpp:500
TagParser::AbstractContainer::muxingApplications
const std::vector< std::string > & muxingApplications() const
Returns the muxing applications specified as meta-data.
Definition abstractcontainer.cpp:483
TagParser::AbstractContainer::isHeaderParsed
bool isHeaderParsed() const
Returns an indication whether the header has been parsed yet.
Definition abstractcontainer.h:178
TagParser::AbstractContainer::setStream
void setStream(std::iostream &stream)
Sets the related stream.
Definition abstractcontainer.h:144
TagParser::AbstractContainer::m_doctype
std::string m_doctype
Definition abstractcontainer.h:107
TagParser::AbstractContainer::timeScale
std::uint32_t timeScale() const
Returns the time scale of the file if known; otherwise returns 0.
Definition abstractcontainer.h:309
TagParser::AbstractContainer::writer
CppUtilities::BinaryWriter & writer()
Returns the related BinaryWriter.
Definition abstractcontainer.h:170
TagParser::AbstractContainer::parseHeader
void parseHeader(Diagnostics &diag, AbortableProgressFeedback &progress)
Parses the header if not parsed yet.
Definition abstractcontainer.cpp:60
TagParser::AbstractContainer::m_doctypeReadVersion
std::uint64_t m_doctypeReadVersion
Definition abstractcontainer.h:109
TagParser::AbstractContainer::track
virtual AbstractTrack * track(std::size_t index)
Returns the track with the specified index.
Definition abstractcontainer.cpp:373
TagParser::AbstractContainer::m_readVersion
std::uint64_t m_readVersion
Definition abstractcontainer.h:106
TagParser::AbstractContainer::reader
CppUtilities::BinaryReader & reader()
Returns the related BinaryReader.
Definition abstractcontainer.h:162
TagParser::AbstractContainer::m_doctypeVersion
std::uint64_t m_doctypeVersion
Definition abstractcontainer.h:108
TagParser::AbstractContainer::m_version
std::uint64_t m_version
Definition abstractcontainer.h:105
TagParser::AbstractContainer::m_duration
CppUtilities::TimeSpan m_duration
Definition abstractcontainer.h:111
TagParser::BasicFileInfo::reportPathChanged
void reportPathChanged(std::string_view newPath)
Call this function to report that the path changed.
Definition basicfileinfo.h:133
TagParser::BasicFileInfo::path
const std::string & path() const
Returns the path of the current file.
Definition basicfileinfo.h:103
TagParser::BasicFileInfo::stream
CppUtilities::NativeFileStream & stream()
Returns the std::fstream for the current instance.
Definition basicfileinfo.h:85
TagParser::BasicFileInfo::close
void close()
A possibly opened std::fstream will be closed.
Definition basicfileinfo.cpp:97
TagParser::BasicFileInfo::pathForOpen
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(),...
Definition basicfileinfo.h:153
TagParser::BasicFileInfo::reportSizeChanged
void reportSizeChanged(std::uint64_t newSize)
Call this function to report that the size changed.
Definition basicfileinfo.h:124
TagParser::BasicProgressFeedback::updateStep
void updateStep(const std::string &step, std::uint8_t stepPercentage=0)
Updates the current step and invokes the first callback specified on construction.
Definition progressfeedback.h:96
TagParser::BasicProgressFeedback::updateStepPercentage
void updateStepPercentage(std::uint8_t stepPercentage)
Updates the current step percentage and invokes the second callback specified on construction (or the...
Definition progressfeedback.h:124
TagParser::Diagnostics
The Diagnostics class is a container for DiagMessage.
Definition diagnostics.h:156
TagParser::EbmlElement
The EbmlElement class helps to parse EBML files such as Matroska files.
Definition ebmlelement.h:32
TagParser::EbmlElement::makeUInteger
static std::uint8_t makeUInteger(std::uint64_t value, char *buff)
Writes value to buff.
Definition ebmlelement.cpp:441
TagParser::EbmlElement::makeSimpleElement
static void makeSimpleElement(std::ostream &stream, IdentifierType id, std::uint64_t content)
Makes a simple EBML element.
Definition ebmlelement.cpp:514
TagParser::EbmlElement::calculateSizeDenotationLength
static std::uint8_t calculateSizeDenotationLength(std::uint64_t size)
Returns the length of the size denotation for the specified size in byte.
Definition ebmlelement.cpp:288
TagParser::EbmlElement::idToString
std::string idToString() const
Converts the specified EBML ID to a printable string.
Definition ebmlelement.h:71
TagParser::EbmlElement::makeSizeDenotation
static std::uint8_t makeSizeDenotation(std::uint64_t size, char *buff)
Makes the size denotation for the specified size and stores it to buff.
Definition ebmlelement.cpp:343
TagParser::EbmlElement::calculateUIntegerLength
static std::uint8_t calculateUIntegerLength(std::uint64_t integer)
Returns the length of the specified unsigned integer in byte.
Definition ebmlelement.cpp:416
TagParser::Failure
The class inherits from std::exception and serves as base class for exceptions thrown by the elements...
Definition exceptions.h:11
TagParser::GenericContainer< MediaFileInfo, MatroskaTag, MatroskaTrack, EbmlElement >::tracks
const std::vector< std::unique_ptr< MatroskaTrack > > & tracks() const
Definition genericcontainer.h:238
TagParser::GenericContainer< MediaFileInfo, MatroskaTag, MatroskaTrack, EbmlElement >::tag
MatroskaTag * tag(std::size_t index) override
Definition genericcontainer.h:164
TagParser::GenericContainer< MediaFileInfo, MatroskaTag, MatroskaTrack, EbmlElement >::track
MatroskaTrack * track(std::size_t index) override
Definition genericcontainer.h:176
TagParser::GenericContainer< MediaFileInfo, MatroskaTag, MatroskaTrack, EbmlElement >::m_tracks
std::vector< std::unique_ptr< MatroskaTrack > > m_tracks
Definition genericcontainer.h:61
TagParser::GenericContainer< MediaFileInfo, MatroskaTag, MatroskaTrack, EbmlElement >::tags
const std::vector< std::unique_ptr< MatroskaTag > > & tags() const
Definition genericcontainer.h:208
TagParser::GenericContainer< MediaFileInfo, MatroskaTag, MatroskaTrack, EbmlElement >::GenericContainer
GenericContainer(MediaFileInfo &fileInfo, std::uint64_t startOffset)
Definition genericcontainer.h:71
TagParser::GenericContainer< MediaFileInfo, MatroskaTag, MatroskaTrack, EbmlElement >::m_firstElement
std::unique_ptr< EbmlElement > m_firstElement
Definition genericcontainer.h:58
TagParser::GenericContainer< MediaFileInfo, MatroskaTag, MatroskaTrack, EbmlElement >::fileInfo
MediaFileInfo & fileInfo() const
Definition genericcontainer.h:114
TagParser::GenericContainer< MediaFileInfo, MatroskaTag, MatroskaTrack, EbmlElement >::m_additionalElements
std::vector< std::unique_ptr< EbmlElement > > m_additionalElements
Definition genericcontainer.h:59
TagParser::GenericContainer< MediaFileInfo, MatroskaTag, MatroskaTrack, EbmlElement >::firstElement
EbmlElement * firstElement() const
Definition genericcontainer.h:132
TagParser::GenericContainer< MediaFileInfo, MatroskaTag, MatroskaTrack, EbmlElement >::m_tags
std::vector< std::unique_ptr< MatroskaTag > > m_tags
Definition genericcontainer.h:60
TagParser::GenericContainer::reset
void reset() override
Discards all parsing results.
Definition genericcontainer.h:368
TagParser::GenericFileElement::copyEntirely
void copyEntirely(TargetStream &targetStream, Diagnostics &diag, AbortableProgressFeedback *progress)
Writes the entire element including all children to the specified targetStream.
Definition genericfileelement.h:877
TagParser::GenericFileElement::copyBuffer
void copyBuffer(TargetStream &targetStream)
Copies buffered data to targetStream.
Definition genericfileelement.h:907
TagParser::GenericFileElement::startOffset
std::uint64_t startOffset() const
Returns the start offset in the related stream.
Definition genericfileelement.h:266
TagParser::GenericFileElement::discardBuffer
void discardBuffer()
Discards buffered data.
Definition genericfileelement.h:896
TagParser::GenericFileElement::headerSize
std::uint32_t headerSize() const
Returns the header size of the element in byte.
Definition genericfileelement.h:309
TagParser::GenericFileElement::endOffset
std::uint64_t endOffset() const
Returns the offset of the first byte which doesn't belong to this element anymore.
Definition genericfileelement.h:356
TagParser::GenericFileElement::id
const IdentifierType & id() const
Returns the element ID.
Definition genericfileelement.h:283
TagParser::GenericFileElement::childById
ImplementationType * childById(const IdentifierType &id, Diagnostics &diag)
Returns the first child with the specified id.
Definition genericfileelement.h:610
TagParser::GenericFileElement::nextSibling
ImplementationType * nextSibling()
Returns the next sibling of the element.
Definition genericfileelement.h:439
TagParser::GenericFileElement::firstChild
ImplementationType * firstChild()
Returns the first child of the element.
Definition genericfileelement.h:465
TagParser::GenericFileElement< EbmlElement >::maximumIdLengthSupported
static constexpr std::uint32_t maximumIdLengthSupported()
Definition genericfileelement.h:980
TagParser::GenericFileElement::dataSize
DataSizeType dataSize() const
Returns the data size of the element in byte.
Definition genericfileelement.h:320
TagParser::GenericFileElement::totalSize
std::uint64_t totalSize() const
Returns the total size of the element.
Definition genericfileelement.h:348
TagParser::GenericFileElement::parse
void parse(Diagnostics &diag)
Parses the header information of the element which is read from the related stream at the start offse...
Definition genericfileelement.h:779
TagParser::GenericFileElement< EbmlElement >::maximumSizeLengthSupported
static constexpr std::uint32_t maximumSizeLengthSupported()
Definition genericfileelement.h:988
TagParser::GenericFileElement::dataOffset
std::uint64_t dataOffset() const
Returns the data offset of the element in the related stream.
Definition genericfileelement.h:338
TagParser::GenericFileElement::makeBuffer
void makeBuffer()
Buffers the element (header and data).
Definition genericfileelement.h:886
TagParser::GenericFileElement::siblingById
ImplementationType * siblingById(const IdentifierType &id, Diagnostics &diag)
Returns the first sibling with the specified id.
Definition genericfileelement.h:648
TagParser::InvalidDataException
The exception that is thrown when the data to be parsed or to be made seems invalid and therefore can...
Definition exceptions.h:25
TagParser::MatroskaAttachment
Implementation of TagParser::AbstractAttachment for the Matroska container.
Definition matroskaattachment.h:44
TagParser::MatroskaChapter
The MatroskaChapter class provides an implementation of AbstractAttachment for Matroska files.
Definition matroskachapter.h:12
TagParser::MatroskaContainer::internalParseChapters
void internalParseChapters(Diagnostics &diag, AbortableProgressFeedback &progress) override
Internally called to parse the chapters.
Definition matroskacontainer.cpp:742
TagParser::MatroskaContainer::internalParseHeader
void internalParseHeader(Diagnostics &diag, AbortableProgressFeedback &progress) override
Internally called to parse the header.
Definition matroskacontainer.cpp:401
TagParser::MatroskaContainer::chapterCount
std::size_t chapterCount() const override
Returns the number of chapters the container holds.
Definition matroskacontainer.cpp:327
TagParser::MatroskaContainer::internalParseTracks
void internalParseTracks(Diagnostics &diag, AbortableProgressFeedback &progress) override
Internally called to parse the tracks.
Definition matroskacontainer.cpp:706
TagParser::MatroskaContainer::determineTagPosition
ElementPosition determineTagPosition(Diagnostics &diag) const override
Determines the position of the tags inside the file.
Definition matroskacontainer.cpp:391
TagParser::MatroskaContainer::createAttachment
MatroskaAttachment * createAttachment() override
Creates and returns a new attachment.
Definition matroskacontainer.cpp:336
TagParser::MatroskaContainer::reset
void reset() override
Discards all parsing results.
Definition matroskacontainer.cpp:58
TagParser::MatroskaContainer::~MatroskaContainer
~MatroskaContainer() override
Definition matroskacontainer.cpp:54
TagParser::MatroskaContainer::MatroskaContainer
MatroskaContainer(MediaFileInfo &stream, std::uint64_t startOffset)
Constructs a new container for the specified fileInfo at the specified startOffset.
Definition matroskacontainer.cpp:41
TagParser::MatroskaContainer::attachment
MatroskaAttachment * attachment(std::size_t index) override
Returns the attachment with the specified index.
Definition matroskacontainer.h:107
TagParser::MatroskaContainer::internalMakeFile
void internalMakeFile(Diagnostics &diag, AbortableProgressFeedback &progress) override
Internally called to make the file.
Definition matroskacontainer.cpp:861
TagParser::MatroskaContainer::chapter
MatroskaChapter * chapter(std::size_t index) override
Returns the chapter with the specified index.
Definition matroskacontainer.cpp:314
TagParser::MatroskaContainer::determineIndexPosition
ElementPosition determineIndexPosition(Diagnostics &diag) const override
Determines the position of the index.
Definition matroskacontainer.cpp:396
TagParser::MatroskaContainer::internalParseAttachments
void internalParseAttachments(Diagnostics &diag, AbortableProgressFeedback &progress) override
Internally called to parse the attachments.
Definition matroskacontainer.cpp:776
TagParser::MatroskaContainer::determineElementPosition
ElementPosition determineElementPosition(std::uint64_t elementId, Diagnostics &diag) const
Determines the position of the element with the specified elementId.
Definition matroskacontainer.cpp:365
TagParser::MatroskaContainer::validateIndex
void validateIndex(Diagnostics &diag, AbortableProgressFeedback &progress)
Validates the file index (cue entries).
Definition matroskacontainer.cpp:83
TagParser::MatroskaContainer::internalParseTags
void internalParseTags(Diagnostics &diag, AbortableProgressFeedback &progress) override
Internally called to parse the tags.
Definition matroskacontainer.cpp:665
TagParser::MatroskaCuePositionUpdater
The MatroskaCuePositionUpdater class helps to rewrite the "Cues"-element with shifted positions.
Definition matroskacues.h:64
TagParser::MatroskaCuePositionUpdater::totalSize
std::uint64_t totalSize() const
Returns how many bytes will be written when calling the make() method.
Definition matroskacues.cpp:40
TagParser::MatroskaCuePositionUpdater::updateOffsets
bool updateOffsets(std::uint64_t originalOffset, std::uint64_t newOffset)
Sets the offset of the entries with the specified originalOffset to newOffset.
Definition matroskacues.cpp:179
TagParser::MatroskaCuePositionUpdater::updateRelativeOffsets
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...
Definition matroskacues.cpp:205
TagParser::MatroskaCuePositionUpdater::make
void make(std::ostream &stream, Diagnostics &diag)
Writes the previously parsed "Cues"-element with updated positions to the specified stream.
Definition matroskacues.cpp:262
TagParser::MatroskaCuePositionUpdater::parse
void parse(EbmlElement *cuesElement, Diagnostics &diag)
Parses the specified cuesElement.
Definition matroskacues.cpp:54
TagParser::MatroskaSeekInfo
The MatroskaSeekInfo class helps parsing and making "SeekHead"-elements.
Definition matroskaseekinfo.h:12
TagParser::MatroskaSeekInfo::push
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.
Definition matroskaseekinfo.cpp:231
TagParser::MatroskaSeekInfo::make
void make(std::ostream &stream, Diagnostics &diag)
Writes a "SeekHead" element for the current instance to the specified stream.
Definition matroskaseekinfo.cpp:147
TagParser::MatroskaSeekInfo::actualSize
std::uint64_t actualSize() const
Returns the number of bytes which will be written when calling the make() method.
Definition matroskaseekinfo.cpp:212
TagParser::MatroskaTag
Implementation of TagParser::Tag for the Matroska container.
Definition matroskatag.h:72
TagParser::MatroskaTrack
Implementation of TagParser::AbstractTrack for the Matroska container.
Definition matroskatrack.h:48
TagParser::MediaFileInfo
The MediaFileInfo class allows to read and write tag information providing a container/tag format ind...
Definition mediafileinfo.h:83
TagParser::MediaFileInfo::isForcingRewrite
bool isForcingRewrite() const
Returns whether forcing rewriting (when applying changes) is enabled.
Definition mediafileinfo.h:558
TagParser::MediaFileInfo::setSaveFilePath
void setSaveFilePath(std::string_view saveFilePath)
Sets the "save file path".
Definition mediafileinfo.h:467
TagParser::MediaFileInfo::fileHandlingFlags
MediaFileHandlingFlags fileHandlingFlags()
Returns the currently configured file handling flags.
Definition mediafileinfo.h:519
TagParser::MediaFileInfo::saveFilePath
const std::string & saveFilePath() const
Returns the "save file path" which has been set using setSaveFilePath().
Definition mediafileinfo.h:448
TagParser::MediaFileInfo::preferredPadding
std::size_t preferredPadding() const
Returns the padding to be written before the data block when applying changes and the file needs to b...
Definition mediafileinfo.h:632
TagParser::MediaFileInfo::tagPosition
ElementPosition tagPosition() const
Returns the position (in the output file) where the tag information is written when applying changes.
Definition mediafileinfo.h:652
TagParser::MediaFileInfo::writePadding
static void writePadding(std::ostream &outputStream, uint64_t size)
Writes the specified number of zeroes to outputStream.
Definition mediafileinfo.cpp:1399
TagParser::MediaFileInfo::indexPosition
ElementPosition indexPosition() const
Returns the position (in the output file) where the index is written when applying changes.
Definition mediafileinfo.h:697
TagParser::NoDataFoundException
The exception that is thrown when the data to be parsed holds no parsable information (e....
Definition exceptions.h:18
TagParser::OperationAbortedException
The exception that is thrown when an operation has been stopped and thus not successfully completed b...
Definition exceptions.h:46
ebmlid.h
exceptions.h
matroskacontainer.h
matroskacues.h
matroskaeditionentry.h
matroskaid.h
matroskaseekinfo.h
mediafileinfo.h
CppUtilities
Definition abstractcontainer.h:16
TagParser::BackupHelper::handleFailureAfterFileModifiedCanonical
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.
Definition backuphelper.cpp:241
TagParser::BackupHelper::createBackupFileCanonical
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.
Definition backuphelper.cpp:199
TagParser::EbmlIds::MaxIdLength
@ MaxIdLength
Definition ebmlid.h:18
TagParser::EbmlIds::DocTypeVersion
@ DocTypeVersion
Definition ebmlid.h:21
TagParser::EbmlIds::Header
@ Header
Definition ebmlid.h:15
TagParser::EbmlIds::ReadVersion
@ ReadVersion
Definition ebmlid.h:17
TagParser::EbmlIds::DocType
@ DocType
Definition ebmlid.h:20
TagParser::EbmlIds::MaxSizeLength
@ MaxSizeLength
Definition ebmlid.h:19
TagParser::EbmlIds::Version
@ Version
Definition ebmlid.h:16
TagParser::EbmlIds::DocTypeReadVersion
@ DocTypeReadVersion
Definition ebmlid.h:22
TagParser::EbmlIds::Void
@ Void
Definition ebmlid.h:28
TagParser::EbmlIds::Crc32
@ Crc32
Definition ebmlid.h:28
TagParser::MatroskaIds::EditionEntry
@ EditionEntry
Definition matroskaid.h:296
TagParser::MatroskaIds::CuePoint
@ CuePoint
Definition matroskaid.h:249
TagParser::MatroskaIds::CueTime
@ CueTime
Definition matroskaid.h:254
TagParser::MatroskaIds::CueTrackPositions
@ CueTrackPositions
Definition matroskaid.h:254
TagParser::MatroskaIds::Cluster
@ Cluster
Definition matroskaid.h:23
TagParser::MatroskaIds::Tracks
@ Tracks
Definition matroskaid.h:19
TagParser::MatroskaIds::Tags
@ Tags
Definition matroskaid.h:21
TagParser::MatroskaIds::SeekHead
@ SeekHead
Definition matroskaid.h:22
TagParser::MatroskaIds::Segment
@ Segment
Definition matroskaid.h:17
TagParser::MatroskaIds::SegmentInfo
@ SegmentInfo
Definition matroskaid.h:18
TagParser::MatroskaIds::Attachments
@ Attachments
Definition matroskaid.h:24
TagParser::MatroskaIds::Chapters
@ Chapters
Definition matroskaid.h:25
TagParser::MatroskaIds::Cues
@ Cues
Definition matroskaid.h:20
TagParser::MatroskaIds::TrackEntry
@ TrackEntry
Definition matroskaid.h:69
TagParser::MatroskaIds::TimeCodeScale
@ TimeCodeScale
Definition matroskaid.h:44
TagParser::MatroskaIds::WrittingApp
@ WrittingApp
Definition matroskaid.h:46
TagParser::MatroskaIds::Title
@ Title
Definition matroskaid.h:55
TagParser::MatroskaIds::Duration
@ Duration
Definition matroskaid.h:45
TagParser::MatroskaIds::MuxingApp
@ MuxingApp
Definition matroskaid.h:47
TagParser::MatroskaIds::Tag
@ Tag
Definition matroskaid.h:211
TagParser::MatroskaIds::CueTrack
@ CueTrack
Definition matroskaid.h:260
TagParser::MatroskaIds::CueBlockNumber
@ CueBlockNumber
Definition matroskaid.h:264
TagParser::MatroskaIds::CueDuration
@ CueDuration
Definition matroskaid.h:263
TagParser::MatroskaIds::CueCodecState
@ CueCodecState
Definition matroskaid.h:265
TagParser::MatroskaIds::CueRelativePosition
@ CueRelativePosition
Definition matroskaid.h:262
TagParser::MatroskaIds::CueReference
@ CueReference
Definition matroskaid.h:266
TagParser::MatroskaIds::CueClusterPosition
@ CueClusterPosition
Definition matroskaid.h:261
TagParser::MatroskaIds::Position
@ Position
Definition matroskaid.h:353
TagParser::MatroskaIds::BlockGroup
@ BlockGroup
Definition matroskaid.h:356
TagParser::MatroskaIds::SimpleBlock
@ SimpleBlock
Definition matroskaid.h:355
TagParser::MatroskaIds::PrevSize
@ PrevSize
Definition matroskaid.h:354
TagParser::MatroskaIds::Block
@ Block
Definition matroskaid.h:369
TagParser::MatroskaIds::AttachedFile
@ AttachedFile
Definition matroskaid.h:277
TagParser
Contains all classes and functions of the TagInfo library.
Definition aaccodebook.h:10
TagParser::ElementPosition
ElementPosition
Definition settings.h:13
TagParser::ElementPosition::Keep
@ Keep
Definition settings.h:16
TagParser::ElementPosition::BeforeData
@ BeforeData
Definition settings.h:14
TagParser::ElementPosition::AfterData
@ AfterData
Definition settings.h:15
TagParser::MatroskaTagFlags::NormalizeKnownFieldIds
@ NormalizeKnownFieldIds
Definition matroskatag.h:19
TagParser::MatroskaTagFlags::None
@ None
Definition matroskatag.h:18
TagParser::sameOffset
bool sameOffset(std::uint64_t offset, const EbmlElement *element)
Returns an indication whether offset equals the start offset of element.
Definition matroskacontainer.cpp:300
TagParser::DiagLevel::Warning
@ Warning
Definition diagnostics.h:20
TagParser::DiagLevel::Critical
@ Critical
Definition diagnostics.h:21
TagParser::DiagLevel::Information
@ Information
Definition diagnostics.h:19
TagParser::ParsingStatus::NotSupported
@ NotSupported
Definition mediafileinfo.h:43
TagParser::ParsingStatus::Ok
@ Ok
Definition mediafileinfo.h:42
TagParser::excludesOffset
bool excludesOffset(const vector< EbmlElement * > &elements, std::uint64_t offset)
Returns whether none of the specified elements have the specified offset.
Definition matroskacontainer.cpp:309
TagParser::TagDataType::TimeSpan
@ TimeSpan
Definition tagvalue.h:124
TagParser::MediaFileHandlingFlags::PreserveWritingApplication
@ PreserveWritingApplication
Definition mediafileinfo.h:68
TagParser::MediaFileHandlingFlags::NormalizeKnownTagFieldIds
@ NormalizeKnownTagFieldIds
Definition mediafileinfo.h:65
TagParser::MediaFileHandlingFlags::PreserveMuxingApplication
@ PreserveMuxingApplication
Definition mediafileinfo.h:67
TagParser::SegmentData
The private SegmentData struct is used in MatroskaContainer::internalMakeFile() to store segment spec...
Definition matroskacontainer.cpp:813
TagParser::SegmentData::SegmentData
SegmentData()
Constructs a new segment data object.
Definition matroskacontainer.cpp:815
TagParser::SegmentData::hasCrc32
bool hasCrc32
whether CRC-32 checksum is present
Definition matroskacontainer.cpp:832
TagParser::SegmentData::newPadding
std::uint64_t newPadding
padding (in the new file)
Definition matroskacontainer.cpp:850
TagParser::SegmentData::totalDataSize
std::uint64_t totalDataSize
total size of the segment data (in the new file, excluding header)
Definition matroskacontainer.cpp:852
TagParser::SegmentData::sizeDenotationLength
std::uint8_t sizeDenotationLength
header size (in the new file)
Definition matroskacontainer.cpp:858
TagParser::SegmentData::clusterSizes
std::vector< std::uint64_t > clusterSizes
cluster sizes, needed because cluster elements are not necessarily copied as-is so they're size might...
Definition matroskacontainer.cpp:842
TagParser::SegmentData::newDataOffset
std::uint64_t newDataOffset
data offset of the segment in the new file
Definition matroskacontainer.cpp:856
TagParser::SegmentData::infoDataSize
std::uint64_t infoDataSize
size of the "SegmentInfo"-element
Definition matroskacontainer.cpp:840
TagParser::SegmentData::startOffset
std::uint64_t startOffset
start offset (in the new file)
Definition matroskacontainer.cpp:848
TagParser::SegmentData::seekInfo
MatroskaSeekInfo seekInfo
used to make "SeekHead"-element
Definition matroskacontainer.cpp:834
TagParser::SegmentData::SegmentData
SegmentData(SegmentData &&)=default
TagParser::SegmentData::firstClusterElement
EbmlElement * firstClusterElement
first "Cluster"-element (original file)
Definition matroskacontainer.cpp:844
TagParser::SegmentData::clusterEndOffset
std::uint64_t clusterEndOffset
end offset of last "Cluster"-element (original file)
Definition matroskacontainer.cpp:846
TagParser::SegmentData::cuesUpdater
MatroskaCuePositionUpdater cuesUpdater
used to make "Cues"-element
Definition matroskacontainer.cpp:838
TagParser::SegmentData::cuesElement
EbmlElement * cuesElement
"Cues"-element (original file)
Definition matroskacontainer.cpp:836
TagParser::SegmentData::totalSize
std::uint64_t totalSize
total size of the segment data (in the new file, including header)
Definition matroskacontainer.cpp:854