8#include "../backuphelper.h"
9#include "../exceptions.h"
10#include "../mediafileinfo.h"
12#include "resources/config.h"
14#include <c++utilities/conversion/stringbuilder.h>
15#include <c++utilities/conversion/stringconversion.h>
21#include <initializer_list>
25#include <unordered_set>
28using namespace std::placeholders;
38std::uint64_t MatroskaContainer::m_maxFullParseSize = 0x3200000;
70 m_tracksElements.clear();
71 m_segmentInfoElements.clear();
72 m_tagsElements.clear();
73 m_chaptersElements.clear();
74 m_attachmentsElements.clear();
76 m_editionEntries.clear();
77 m_attachments.clear();
87 static const string context(
"validating Matroska file index (cues)");
88 bool cuesElementsFound =
false;
90 unordered_set<EbmlElement::IdentifierType> ids;
91 bool cueTimeFound =
false, cueTrackPositionsFound =
false;
92 unique_ptr<EbmlElement> clusterElement;
93 std::uint64_t pos, prevClusterSize = 0, currentOffset = 0;
97 segmentElement->parse(diag);
100 segmentChildElement = segmentChildElement->
nextSibling()) {
102 segmentChildElement->parse(diag);
103 switch (segmentChildElement->id()) {
108 cuesElementsFound =
true;
111 cuePointElement = cuePointElement->
nextSibling()) {
113 cuePointElement->parse(diag);
114 cueTimeFound = cueTrackPositionsFound =
false;
115 switch (cuePointElement->id()) {
122 cuePointChildElement = cuePointChildElement->
nextSibling()) {
123 cuePointChildElement->parse(diag);
124 switch (cuePointChildElement->id()) {
129 DiagLevel::Warning,
"\"CuePoint\"-element contains multiple \"CueTime\" elements.", context);
135 cueTrackPositionsFound =
true;
137 clusterElement.reset();
140 subElement->parse(diag);
141 switch (subElement->id()) {
149 if (ids.count(subElement->id())) {
151 "\"CueTrackPositions\"-element contains multiple \"" % subElement->idToString() +
"\" elements.",
154 ids.insert(subElement->id());
163 "\"CueTrackPositions\"-element contains unknown element \"" % subElement->idToString() +
"\".",
166 switch (subElement->id()) {
173 clusterElement = make_unique<EbmlElement>(
174 *
this, segmentElement->dataOffset() + subElement->readUInteger() - currentOffset);
176 clusterElement->parse(diag);
179 "\"CueClusterPosition\" element at " % numberToString(subElement->startOffset())
180 +
" does not point to \"Cluster\"-element (points to "
181 + numberToString(clusterElement->startOffset()) +
").",
189 pos = subElement->readUInteger();
205 "\"CueTrackPositions\"-element does not contain mandatory element \"CueTrack\".", context);
207 if (!clusterElement) {
209 "\"CueTrackPositions\"-element does not contain mandatory element \"CueClusterPosition\".", context);
212 EbmlElement referenceElement(*
this, clusterElement->dataOffset() + pos);
214 referenceElement.
parse(diag);
215 switch (referenceElement.
id()) {
222 "\"CueRelativePosition\" element does not point to \"Block\"-, \"BlockGroup\", or "
223 "\"SimpleBlock\"-element (points to "
237 "\"CuePoint\"-element contains unknown element \"" % cuePointElement->idToString() +
"\".", context);
243 DiagLevel::Warning,
"\"CuePoint\"-element does not contain mandatory element \"CueTime\".", context);
245 if (!cueTrackPositionsFound) {
247 DiagLevel::Warning,
"\"CuePoint\"-element does not contain mandatory element \"CueClusterPosition\".", context);
257 clusterElementChild = clusterElementChild->
nextSibling()) {
258 clusterElementChild->parse(diag);
259 switch (clusterElementChild->id()) {
265 if ((pos = clusterElementChild->readUInteger()) > 0
266 && (segmentChildElement->startOffset() - segmentElement->dataOffset() + currentOffset) != pos) {
268 argsToString(
"\"Position\"-element at ", clusterElementChild->startOffset(),
" points to ", pos,
269 " which is not the offset of the containing \"Cluster\"-element."),
275 if ((pos = clusterElementChild->readUInteger()) != prevClusterSize) {
277 argsToString(
"\"PrevSize\"-element at ", clusterElementChild->startOffset(),
" should be ", prevClusterSize,
278 " but is ", pos,
"."),
285 prevClusterSize = segmentChildElement->totalSize();
290 currentOffset += segmentElement->totalSize();
294 if (!cuesElementsFound) {
311inline bool excludesOffset(
const vector<EbmlElement *> &elements, std::uint64_t offset)
313 return find_if(elements.cbegin(), elements.cend(), std::bind(
sameOffset, offset, _1)) == elements.cend();
318 for (
const auto &entry : m_editionEntries) {
319 const auto &chapters = entry->chapters();
320 if (index < chapters.size()) {
321 return chapters[index].get();
323 index -= chapters.size();
332 for (
const auto &entry : m_editionEntries) {
333 count += entry->chapters().size();
341 static const auto randomEngine(
342 default_random_engine(
static_cast<default_random_engine::result_type
>(chrono::system_clock::now().time_since_epoch().count())));
343 std::uint64_t attachmentId;
344 auto dice(bind(uniform_int_distribution<
decltype(attachmentId)>(), randomEngine));
345 std::uint8_t tries = 0;
347 attachmentId = dice();
349 for (
const auto &
attachment : m_attachments) {
352 goto generateRandomId;
357 m_attachments.emplace_back(make_unique<MatroskaAttachment>());
373 if (!segmentElement) {
377 if (childElement->id() == elementId) {
380 for (
const auto &seekInfo : m_seekInfos) {
381 for (
const auto &info : seekInfo->info()) {
382 if (info.first == elementId) {
405 CPP_UTILITIES_UNUSED(progress)
407 static const string context(
"parsing header of Matroska container");
411 m_tracksElements.clear();
412 m_segmentInfoElements.clear();
413 m_tagsElements.clear();
416 std::uint64_t currentOffset = 0;
417 vector<MatroskaSeekInfo>::difference_type seekInfosIndex = 0;
420 for (
EbmlElement *topLevelElement =
m_firstElement.get(); topLevelElement; topLevelElement = topLevelElement->nextSibling()) {
422 topLevelElement->parse(diag);
423 switch (topLevelElement->id()) {
427 subElement->parse(diag);
428 switch (subElement->id()) {
445 m_maxIdLength = subElement->readUInteger();
449 " bytes is not supported."),
455 m_maxSizeLength = subElement->readUInteger();
459 " bytes is not supported."),
466 diag.emplace_back(
DiagLevel::Critical,
"Unable to parse all children of EBML header.", context);
475 subElement->parse(diag);
476 switch (subElement->id()) {
478 m_seekInfos.emplace_back(make_unique<MatroskaSeekInfo>());
479 m_seekInfos.back()->parse(subElement, diag);
482 if (
excludesOffset(m_tracksElements, subElement->startOffset())) {
483 m_tracksElements.push_back(subElement);
487 if (
excludesOffset(m_segmentInfoElements, subElement->startOffset())) {
488 m_segmentInfoElements.push_back(subElement);
493 m_tagsElements.push_back(subElement);
497 if (
excludesOffset(m_chaptersElements, subElement->startOffset())) {
498 m_chaptersElements.push_back(subElement);
502 if (
excludesOffset(m_attachmentsElements, subElement->startOffset())) {
503 m_attachmentsElements.push_back(subElement);
509 for (
auto i = m_seekInfos.cbegin() + seekInfosIndex, end = m_seekInfos.cend(); i != end; ++i, ++seekInfosIndex) {
510 for (
const auto &infoPair : (*i)->info()) {
511 std::uint64_t offset = currentOffset + topLevelElement->dataOffset() + infoPair.second;
514 argsToString(
"Offset (", offset,
") denoted by \"SeekHead\" element is invalid."), context);
516 auto element = make_unique<EbmlElement>(*
this, offset);
518 element->parse(diag);
519 if (element->id() != infoPair.first) {
521 argsToString(
"ID of element ", element->idToString(),
" at ", offset,
522 " does not match the ID denoted in the \"SeekHead\" element (0x",
523 numberToString(infoPair.first, 16u),
")."),
526 switch (element->id()) {
561 argsToString(
"Can not parse element at ", offset,
" (denoted using \"SeekHead\" element)."), context);
567 if (((!m_tracksElements.empty() && !m_tagsElements.empty()) ||
fileInfo().size() > m_maxFullParseSize)
568 && !m_segmentInfoElements.empty()) {
574 diag.emplace_back(
DiagLevel::Critical,
"Unable to parse all children of \"Segment\"-element.", context);
578 currentOffset += topLevelElement->totalSize();
584 DiagLevel::Critical, argsToString(
"Unable to parse top-level element at ", topLevelElement->startOffset(),
'.'), context);
592 parseSegmentInfo(diag);
594 diag.emplace_back(
DiagLevel::Critical,
"Unable to parse EBML (segment) \"Info\"-element.", context);
607void MatroskaContainer::parseSegmentInfo(
Diagnostics &diag)
609 if (m_segmentInfoElements.empty()) {
613 for (EbmlElement *element : m_segmentInfoElements) {
614 element->parse(diag);
615 EbmlElement *subElement = element->firstChild();
616 double rawDuration = 0.0;
618 bool hasTitle =
false;
620 subElement->parse(diag);
621 switch (subElement->id()) {
623 m_titles.emplace_back(subElement->readString());
627 rawDuration = subElement->readFloat();
633 subElement = subElement->nextSibling();
640 if (rawDuration > 0.0) {
641 m_duration += TimeSpan::fromSeconds(rawDuration *
static_cast<double>(
timeScale) / 1000000000.0);
651void MatroskaContainer::readTrackStatisticsFromTags(Diagnostics &diag)
663 CPP_UTILITIES_UNUSED(progress)
665 static const string context(
"parsing tags of Matroska container");
670 for (
EbmlElement *
const element : m_tagsElements) {
672 element->parse(diag);
674 subElement->parse(diag);
675 switch (subElement->id()) {
677 m_tags.emplace_back(make_unique<MatroskaTag>());
679 m_tags.back()->parse2(*subElement, flags, diag);
690 diag.emplace_back(
DiagLevel::Warning,
"\"Tags\"-element contains unknown child. It will be ignored.", context);
695 readTrackStatisticsFromTags(diag);
699 readTrackStatisticsFromTags(diag);
704 static const string context(
"parsing tracks of Matroska container");
707 element->parse(diag);
709 subElement->parse(diag);
710 switch (subElement->id()) {
712 m_tracks.emplace_back(make_unique<MatroskaTrack>(*subElement));
714 m_tracks.back()->parseHeader(diag, progress);
726 "\"Tracks\"-element contains unknown child element \"" % subElement->idToString() +
"\". It will be ignored.", context);
731 readTrackStatisticsFromTags(diag);
735 readTrackStatisticsFromTags(diag);
740 static const string context(
"parsing editions/chapters of Matroska container");
743 element->parse(diag);
745 subElement->parse(diag);
746 switch (subElement->id()) {
748 m_editionEntries.emplace_back(make_unique<MatroskaEditionEntry>(subElement));
750 m_editionEntries.back()->parseNested(diag, progress);
752 m_editionEntries.pop_back();
754 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Unable to parse edition entry ", m_editionEntries.size(),
'.'), context);
762 "\"Chapters\"-element contains unknown child element \"" % subElement->idToString() +
"\". It will be ignored.", context);
774 CPP_UTILITIES_UNUSED(progress)
776 static const string context(
"parsing attachments of Matroska container");
777 for (
EbmlElement *element : m_attachmentsElements) {
779 element->parse(diag);
781 subElement->parse(diag);
782 switch (subElement->id()) {
784 m_attachments.emplace_back(make_unique<MatroskaAttachment>());
786 m_attachments.back()->parse(subElement, diag);
788 m_attachments.pop_back();
790 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Unable to parse attached file ", m_attachments.size(),
'.'), context);
798 "\"Attachments\"-element contains unknown child element \"" % subElement->idToString() +
"\". It will be ignored.", context);
858 static const string context(
"making Matroska container");
859 progress.
updateStep(
"Calculating element sizes ...");
866 switch (
fileInfo().attachmentsParsingStatus()) {
871 diag.emplace_back(
DiagLevel::Critical,
"Attachments have to be parsed without critical errors before changes can be applied.", context);
877 if (!level0Element) {
884 vector<MatroskaTagMaker> tagMaker;
885 tagMaker.reserve(
tags().size());
886 std::uint64_t tagElementsSize = 0;
887 std::uint64_t tagsSize;
888 vector<MatroskaAttachmentMaker> attachmentMaker;
889 attachmentMaker.reserve(m_attachments.size());
890 std::uint64_t attachedFileElementsSize = 0;
891 std::uint64_t attachmentsSize;
892 vector<MatroskaTrackHeaderMaker> trackHeaderMaker;
893 trackHeaderMaker.reserve(
tracks().size());
894 std::uint64_t trackHeaderElementsSize = 0;
895 std::uint64_t trackHeaderSize;
899 unsigned int segmentIndex = 0;
901 vector<SegmentData> segmentData;
903 std::uint64_t offset;
905 std::uint64_t totalOffset;
907 std::uint64_t currentPosition = 0;
909 vector<tuple<std::uint64_t, std::uint64_t>> crc32Offsets;
911 std::uint8_t sizeLength;
913 std::uint64_t clusterSize, clusterReadSize, clusterReadOffset;
925 unsigned int lastSegmentIndex = numeric_limits<unsigned int>::max();
927 std::uint64_t newPadding;
933 std::uint64_t ebmlHeaderDataSize = 2 * 7;
935 for (
auto headerValue :
946 constexpr std::string_view muxingAppName = APP_NAME
" v" APP_VERSION;
947 constexpr std::uint64_t muxingAppElementTotalSize = 2 + 1 + muxingAppName.size();
950 const std::uint64_t writingAppElementDataSize
952 const std::uint64_t writingAppElementTotalSize = 2 + 1 + writingAppElementDataSize;
959 if (tagMaker.back().requiredSize() > 3) {
961 tagElementsSize += tagMaker.back().requiredSize();
973 if (attachmentMaker.back().requiredSize() > 3) {
975 attachedFileElementsSize += attachmentMaker.back().requiredSize();
988 if (trackHeaderMaker.back().requiredSize() > 3) {
990 trackHeaderElementsSize += trackHeaderMaker.back().requiredSize();
1002 for (
bool firstClusterFound =
false, firstTagFound =
false; level0Element; level0Element = level0Element->
nextSibling()) {
1003 level0Element->
parse(diag);
1004 switch (level0Element->
id()) {
1007 for (level1Element = level0Element->
firstChild(); level1Element && !firstClusterFound && !firstTagFound;
1009 level1Element->
parse(diag);
1010 switch (level1Element->
id()) {
1013 firstTagFound =
true;
1016 firstClusterFound =
true;
1019 if (firstTagFound) {
1021 }
else if (firstClusterFound) {
1028 segmentData.resize(lastSegmentIndex + 1);
1039 "Unable to parse content in top-level element at " % numberToString(level0Element->
startOffset()) +
" of original file.", context);
1043 progress.
nextStepOrStop(
"Calculating offsets of elements before cluster ...");
1044 calculateSegmentData:
1047 std::uint64_t currentOffset = ebmlHeaderSize;
1049 std::uint64_t readOffset = 0;
1054 if (rewriteRequired) {
1065 for (level0Element =
firstElement(), currentPosition = newPadding = segmentIndex = 0; level0Element;
1067 switch (level0Element->
id()) {
1096 newCuesPos = currentCuesPos;
1109 calculateSegmentSize:
1122 goto calculateSegmentSize;
1126 segment.
infoDataSize = muxingAppElementTotalSize + writingAppElementTotalSize;
1128 if (segmentIndex <
m_titles.size()) {
1130 if (!
title.empty()) {
1135 for (level2Element = level1Element->
firstChild(); level2Element; level2Element = level2Element->
nextSibling()) {
1136 level2Element->
parse(diag);
1137 switch (level2Element->
id()) {
1155 if (trackHeaderSize) {
1158 goto calculateSegmentSize;
1170 goto calculateSegmentSize;
1186 goto calculateSegmentSize;
1193 if (attachmentsSize) {
1196 goto calculateSegmentSize;
1210 goto calculateSegmentSize;
1213 progress.
updateStep(
"Calculating cluster offsets and index size ...");
1218 progress.
updateStep(
"Calculating cluster offsets ...");
1222 if (!rewriteRequired) {
1229 if (totalOffset <= segment.firstClusterElement->
startOffset()) {
1238 diag.emplace_back(
DiagLevel::Critical,
"Header size of \"Segment\"-element from original file is invalid.", context);
1243 nonRewriteCalculations:
1248 goto calculateSegmentSize;
1251 bool cuesInvalidated =
false;
1259 cuesInvalidated =
true;
1264 if (index % 50 == 0) {
1268 if (cuesInvalidated) {
1270 goto addCuesElementSize;
1275 progress.
updateStep(
"Calculating offsets of elements after cluster ...");
1279 goto calculateSegmentSize;
1291 goto calculateSegmentSize;
1298 if (attachmentsSize) {
1301 goto calculateSegmentSize;
1315 goto nonRewriteCalculations;
1318 totalOffset = currentOffset + 4 + sizeLength + offset;
1323 if (totalOffset <= segment.firstClusterElement->
startOffset()) {
1329 rewriteRequired =
true;
1332 rewriteRequired =
true;
1335 rewriteRequired =
true;
1338 diag.emplace_back(
DiagLevel::Warning, argsToString(
"There are no clusters in segment ", segmentIndex,
"."), context);
1341 if (rewriteRequired) {
1347 rewriteRequired =
false;
1353 rewriteRequired =
false;
1356 goto calculateSegmentData;
1369 bool cuesInvalidated =
false;
1375 cuesInvalidated =
true;
1378 goto calculateSegmentSize;
1381 clusterSize = clusterReadSize = 0;
1382 for (level2Element = level1Element->
firstChild(); level2Element; level2Element = level2Element->
nextSibling()) {
1383 level2Element->
parse(diag);
1387 cuesInvalidated =
true;
1389 switch (level2Element->
id()) {
1397 clusterSize += level2Element->
totalSize();
1399 clusterReadSize += level2Element->
totalSize();
1408 if ((index % 50 == 0) &&
fileInfo().size()) {
1414 if (cuesInvalidated) {
1417 goto addCuesElementSize;
1421 progress.
updateStep(
"Calculating offsets of elements after cluster ...");
1425 goto calculateSegmentSize;
1440 goto calculateSegmentSize;
1447 if (attachmentsSize) {
1450 goto calculateSegmentSize;
1468 readOffset += level0Element->
totalSize();
1475 "The top-level element \"" % level0Element->
idToString() +
"\" of the original file is unknown and will just be copied.",
1477 currentOffset += level0Element->
totalSize();
1478 readOffset += level0Element->
totalSize();
1482 if (!rewriteRequired) {
1484 if ((rewriteRequired = (newPadding >
fileInfo().maxPadding() || newPadding <
fileInfo().minPadding()))) {
1486 goto calculateSegmentData;
1496 }
catch (
const std::ios_base::failure &failure) {
1497 diag.emplace_back(
DiagLevel::Critical, argsToString(
"An IO error occurred when parsing the original file: ", failure.what()), context);
1508 NativeFileStream backupStream;
1509 BinaryWriter outputWriter(&outputStream);
1512 if (rewriteRequired) {
1513 if (
fileInfo().saveFilePath().empty()) {
1518 outputStream.open(originalPath, ios_base::out | ios_base::binary | ios_base::trunc);
1519 }
catch (
const std::ios_base::failure &failure) {
1521 DiagLevel::Critical, argsToString(
"Creation of temporary file (to rewrite the original file) failed: ", failure.what()), context);
1527 backupStream.exceptions(ios_base::badbit | ios_base::failbit);
1531 }
catch (
const std::ios_base::failure &failure) {
1532 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Opening streams to write output file failed: ", failure.what()), context);
1544 for (
auto &maker : attachmentMaker) {
1545 maker.bufferCurrentAttachments(diag);
1551 outputStream.open(
fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary);
1552 }
catch (
const std::ios_base::failure &failure) {
1553 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Opening the file with write permissions failed: ", failure.what()), context);
1564 outputStream.write(buff, sizeLength);
1574 for (level0Element =
firstElement(), segmentIndex = 0, currentPosition = 0; level0Element; level0Element = level0Element->
nextSibling()) {
1577 switch (level0Element->
id()) {
1592 progress.
updateStep(
"Writing segment header ...");
1595 outputStream.write(buff, sizeLength);
1596 segment.
newDataOffset = offset =
static_cast<std::uint64_t
>(outputStream.tellp());
1602 *(buff + 1) =
static_cast<char>(0x84);
1604 crc32Offsets.emplace_back(outputStream.tellp(), segment.
totalDataSize);
1605 outputStream.write(buff, 6);
1617 outputStream.write(buff, sizeLength);
1619 for (level2Element = level1Element->
firstChild(); level2Element; level2Element = level2Element->
nextSibling()) {
1620 switch (level2Element->
id()) {
1633 if (segmentIndex <
m_titles.size()) {
1635 if (!
title.empty()) {
1642 fileInfo().writingApplication().empty() ? muxingAppName :
fileInfo().writingApplication());
1646 if (trackHeaderElementsSize) {
1649 outputStream.write(buff, sizeLength);
1650 for (
auto &maker : trackHeaderMaker) {
1651 maker.make(outputStream);
1667 outputStream.write(buff, sizeLength);
1668 for (
auto &maker : tagMaker) {
1669 maker.make(outputStream);
1673 if (attachmentsSize) {
1676 outputStream.write(buff, sizeLength);
1677 for (
auto &maker : attachmentMaker) {
1678 maker.make(outputStream, diag);
1691 std::uint64_t voidLength;
1694 *buff =
static_cast<char>(voidLength = segment.
newPadding - 2) |
static_cast<char>(0x80);
1697 BE::getBytes(
static_cast<std::uint64_t
>((voidLength = segment.
newPadding - 9) | 0x100000000000000), buff);
1701 outputStream.write(buff, sizeLength);
1703 for (; voidLength; --voidLength) {
1704 outputStream.put(0);
1710 if (rewriteRequired) {
1713 static_cast<std::uint8_t
>((
static_cast<std::uint64_t
>(outputStream.tellp()) - offset) * 100 / segment.
totalDataSize));
1715 auto clusterSizesIterator = segment.
clusterSizes.cbegin();
1716 unsigned int index = 0;
1719 clusterSize = currentPosition + (
static_cast<std::uint64_t
>(outputStream.tellp()) - offset);
1723 outputStream.write(buff, sizeLength);
1725 for (level2Element = level1Element->
firstChild(); level2Element; level2Element = level2Element->
nextSibling()) {
1726 switch (level2Element->
id()) {
1734 level2Element->
copyEntirely(outputStream, diag,
nullptr);
1739 if (index % 50 == 0) {
1741 static_cast<std::uint8_t
>((
static_cast<std::uint64_t
>(outputStream.tellp()) - offset) * 100 / segment.
totalDataSize));
1747 static_cast<std::uint8_t
>((
static_cast<std::uint64_t
>(outputStream.tellp()) - offset) * 100 / segment.
totalDataSize));
1748 for (; level1Element; level1Element = level1Element->
nextSibling()) {
1749 for (level2Element = level1Element->
firstChild(); level2Element; level2Element = level2Element->
nextSibling()) {
1750 switch (level2Element->
id()) {
1754 level2Element->
dataSize() > 8 ? 8 :
static_cast<std::uint8_t
>(level2Element->
dataSize()));
1756 if (level2Element->
dataSize() < sizeLength) {
1758 outputStream.seekp(
static_cast<streamoff
>(level2Element->
startOffset()));
1762 outputStream.seekp(
static_cast<streamoff
>(level2Element->
dataOffset()));
1763 outputStream.write(buff, sizeLength);
1774 progress.
updateStep(
"Writing segment tail ...");
1786 outputStream.write(buff, sizeLength);
1787 for (
auto &maker : tagMaker) {
1788 maker.make(outputStream);
1792 if (attachmentsSize) {
1795 outputStream.write(buff, sizeLength);
1796 for (
auto &maker : attachmentMaker) {
1797 maker.make(outputStream, diag);
1812 level0Element->
copyEntirely(outputStream, diag,
nullptr);
1813 currentPosition += level0Element->
totalSize();
1818 progress.
updateStep(
"Reparsing output file ...");
1819 if (rewriteRequired) {
1824 if (!
fileInfo().saveFilePath().empty()) {
1830 outputStream.close();
1831 outputStream.open(
fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary);
1834 const auto newSize =
static_cast<std::uint64_t
>(outputStream.tellp());
1838 outputStream.close();
1840 if (truncate(
fileInfo().path().c_str(),
static_cast<iostream::off_type
>(newSize)) == 0) {
1846 outputStream.open(
fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary);
1858 diag.emplace_back(
DiagLevel::Critical,
"Unable to reparse the header of the new file.", context);
1863 if (!crc32Offsets.empty()) {
1864 progress.
updateStep(
"Updating CRC-32 checksums ...");
1865 for (
const auto &crc32Offset : crc32Offsets) {
1866 outputStream.seekg(
static_cast<streamoff
>(get<0>(crc32Offset) + 6));
1867 outputStream.seekp(
static_cast<streamoff
>(get<0>(crc32Offset) + 2));
1868 writer().writeUInt32LE(
reader().readCrc32(get<1>(crc32Offset) - 6));
1873 outputStream.flush();
The AbortableProgressFeedback class provides feedback about an ongoing operation via callbacks.
void stopIfAborted() const
Throws an OperationAbortedException if aborted.
void nextStepOrStop(const std::string &step, std::uint8_t stepPercentage=0)
Throws an OperationAbortedException if aborted; otherwise the data for the next step is set.
std::uint64_t id() const
Returns the ID of the attachment.
bool isIgnored() const
Returns whether the attachment is ignored/omitted when rewriting the container.
void setId(std::uint64_t id)
Sets the ID of the attachment.
std::uint64_t startOffset() const
Returns the start offset in the related stream.
std::vector< std::string > m_titles
bool isHeaderParsed() const
Returns an indication whether the header has been parsed yet.
void setStream(std::iostream &stream)
Sets the related stream.
std::uint32_t timeScale() const
Returns the time scale of the file if known; otherwise returns 0.
CppUtilities::BinaryWriter & writer()
Returns the related BinaryWriter.
void parseHeader(Diagnostics &diag, AbortableProgressFeedback &progress)
Parses the header if not parsed yet.
std::uint64_t m_doctypeReadVersion
std::uint64_t m_readVersion
CppUtilities::BinaryReader & reader()
Returns the related BinaryReader.
std::uint64_t m_doctypeVersion
CppUtilities::TimeSpan m_duration
void reportPathChanged(std::string_view newPath)
Call this function to report that the path changed.
const std::string & path() const
Returns the path of the current file.
std::uint64_t size() const
Returns size of the current file in bytes.
CppUtilities::NativeFileStream & stream()
Returns the std::fstream for the current instance.
void close()
A possibly opened std::fstream will be closed.
static std::string_view pathForOpen(std::string_view url)
Returns removes the "file:/" prefix from url to be able to pass it to functions like open(),...
void reportSizeChanged(std::uint64_t newSize)
Call this function to report that the size changed.
void updateStep(const std::string &step, std::uint8_t stepPercentage=0)
Updates the current step and invokes the first callback specified on construction.
void updateStepPercentage(std::uint8_t stepPercentage)
Updates the current step percentage and invokes the second callback specified on construction (or the...
The Diagnostics class is a container for DiagMessage.
The EbmlElement class helps to parse EBML files such as Matroska files.
static std::uint8_t makeUInteger(std::uint64_t value, char *buff)
Writes value to buff.
static void makeSimpleElement(std::ostream &stream, IdentifierType id, std::uint64_t content)
Makes a simple EBML element.
static std::uint8_t calculateSizeDenotationLength(std::uint64_t size)
Returns the length of the size denotation for the specified size in byte.
std::string idToString() const
Converts the specified EBML ID to a printable string.
static std::uint8_t makeSizeDenotation(std::uint64_t size, char *buff)
Makes the size denotation for the specified size and stores it to buff.
static std::uint8_t calculateUIntegerLength(std::uint64_t integer)
Returns the length of the specified unsigned integer in byte.
The class inherits from std::exception and serves as base class for exceptions thrown by the elements...
The GenericContainer class helps parsing header, track, tag and chapter information of a file.
const std::vector< std::unique_ptr< MatroskaTrack > > & tracks() const
Returns the tracks of the file.
MatroskaTag * tag(std::size_t index) override
MatroskaTrack * track(std::size_t index) override
std::vector< std::unique_ptr< MatroskaTrack > > m_tracks
const std::vector< std::unique_ptr< MatroskaTag > > & tags() const
Returns the tags of the file.
std::unique_ptr< EbmlElement > m_firstElement
MediaFileInfo & fileInfo() const
Returns the related file info.
std::vector< std::unique_ptr< EbmlElement > > m_additionalElements
EbmlElement * firstElement() const
Returns the first element of the file if available; otherwiese returns nullptr.
std::vector< std::unique_ptr< MatroskaTag > > m_tags
void reset() override
Discards all parsing results.
std::uint64_t startOffset() const
Returns the start offset in the related stream.
void discardBuffer()
Discards buffered data.
void copyEntirely(std::ostream &targetStream, Diagnostics &diag, AbortableProgressFeedback *progress)
Writes the entire element including all children to the specified targetStream.
std::uint32_t headerSize() const
Returns the header size of the element in byte.
std::uint64_t endOffset() const
Returns the offset of the first byte which doesn't belong to this element anymore.
const IdentifierType & id() const
Returns the element ID.
void copyBuffer(std::ostream &targetStream)
Copies buffered data to targetStream.
ImplementationType * childById(const IdentifierType &id, Diagnostics &diag)
Returns the first child with the specified id.
ImplementationType * nextSibling()
Returns the next sibling of the element.
ImplementationType * firstChild()
Returns the first child of the element.
static constexpr std::uint32_t maximumIdLengthSupported()
Returns the maximum id length supported by the class in byte.
DataSizeType dataSize() const
Returns the data size of the element in byte.
std::uint64_t totalSize() const
Returns the total size of the element.
void parse(Diagnostics &diag)
Parses the header information of the element which is read from the related stream at the start offse...
static constexpr std::uint32_t maximumSizeLengthSupported()
Returns the maximum size length supported by the class in byte.
std::uint64_t dataOffset() const
Returns the data offset of the element in the related stream.
void makeBuffer()
Buffers the element (header and data).
ImplementationType * siblingById(const IdentifierType &id, Diagnostics &diag)
Returns the first sibling with the specified id.
The exception that is thrown when the data to be parsed or to be made seems invalid and therefore can...
Implementation of TagParser::AbstractAttachment for the Matroska container.
MatroskaAttachmentMaker prepareMaking(Diagnostics &diag)
Prepares making.
The MatroskaChapter class provides an implementation of AbstractAttachment for Matroska files.
void internalParseChapters(Diagnostics &diag, AbortableProgressFeedback &progress) override
Internally called to parse the chapters.
void internalParseHeader(Diagnostics &diag, AbortableProgressFeedback &progress) override
Internally called to parse the header.
std::size_t chapterCount() const override
Returns the number of chapters the container holds.
void internalParseTracks(Diagnostics &diag, AbortableProgressFeedback &progress) override
Internally called to parse the tracks.
ElementPosition determineTagPosition(Diagnostics &diag) const override
Determines the position of the tags inside the file.
MatroskaAttachment * createAttachment() override
Creates and returns a new attachment.
void reset() override
Discards all parsing results.
~MatroskaContainer() override
MatroskaContainer(MediaFileInfo &stream, std::uint64_t startOffset)
Constructs a new container for the specified fileInfo at the specified startOffset.
MatroskaAttachment * attachment(std::size_t index) override
Returns the attachment with the specified index.
void internalMakeFile(Diagnostics &diag, AbortableProgressFeedback &progress) override
Internally called to make the file.
MatroskaChapter * chapter(std::size_t index) override
Returns the chapter with the specified index.
ElementPosition determineIndexPosition(Diagnostics &diag) const override
Determines the position of the index.
void internalParseAttachments(Diagnostics &diag, AbortableProgressFeedback &progress) override
Internally called to parse the attachments.
ElementPosition determineElementPosition(std::uint64_t elementId, Diagnostics &diag) const
Determines the position of the element with the specified elementId.
void validateIndex(Diagnostics &diag, AbortableProgressFeedback &progress)
Validates the file index (cue entries).
void internalParseTags(Diagnostics &diag, AbortableProgressFeedback &progress) override
Internally called to parse the tags.
The MatroskaCuePositionUpdater class helps to rewrite the "Cues"-element with shifted positions.
std::uint64_t totalSize() const
Returns how many bytes will be written when calling the make() method.
bool updateOffsets(std::uint64_t originalOffset, std::uint64_t newOffset)
Sets the offset of the entries with the specified originalOffset to newOffset.
bool updateRelativeOffsets(std::uint64_t referenceOffset, std::uint64_t originalRelativeOffset, std::uint64_t newRelativeOffset)
Sets the relative offset of the entries with the specified originalRelativeOffset and the specified r...
void make(std::ostream &stream, Diagnostics &diag)
Writes the previously parsed "Cues"-element with updated positions to the specified stream.
void parse(EbmlElement *cuesElement, Diagnostics &diag)
Parses the specified cuesElement.
The MatroskaSeekInfo class helps parsing and making "SeekHead"-elements.
bool push(unsigned int index, EbmlElement::IdentifierType id, std::uint64_t offset)
Pushes the specified offset of an element with the specified id to the info.
void make(std::ostream &stream, Diagnostics &diag)
Writes a "SeekHead" element for the current instance to the specified stream.
std::uint64_t actualSize() const
Returns the number of bytes which will be written when calling the make() method.
Implementation of TagParser::Tag for the Matroska container.
MatroskaTagMaker prepareMaking(Diagnostics &diag)
Prepares making.
Implementation of TagParser::AbstractTrack for the Matroska container.
MatroskaTrackHeaderMaker prepareMakingHeader(Diagnostics &diag) const
Prepares making header.
void readStatisticsFromTags(const std::vector< std::unique_ptr< MatroskaTag > > &tags, Diagnostics &diag)
Reads track-specific statistics from the specified tags.
The exception that is thrown when the data to be parsed holds no parsable information (e....
The exception that is thrown when an operation has been stopped and thus not successfully completed b...
TAG_PARSER_EXPORT void handleFailureAfterFileModifiedCanonical(MediaFileInfo &fileInfo, const std::string &originalPath, const std::string &backupPath, CppUtilities::NativeFileStream &outputStream, CppUtilities::NativeFileStream &backupStream, Diagnostics &diag, const std::string &context="making file")
Handles a failure/abort which occurred after the file has been modified.
TAG_PARSER_EXPORT void createBackupFileCanonical(const std::string &backupDir, std::string &originalPath, std::string &backupPath, CppUtilities::NativeFileStream &originalStream, CppUtilities::NativeFileStream &backupStream)
Creates a backup file like createBackupFile() but canonicalizes originalPath before doing the backup.
constexpr TAG_PARSER_EXPORT std::string_view title()
Contains all classes and functions of the TagInfo library.
bool sameOffset(std::uint64_t offset, const EbmlElement *element)
Returns an indication whether offset equals the start offset of element.
bool excludesOffset(const vector< EbmlElement * > &elements, std::uint64_t offset)
Returns whether none of the specified elements have the specified offset.
@ NormalizeKnownTagFieldIds
The private SegmentData struct is used in MatroskaContainer::internalMakeFile() to store segment spec...
SegmentData()
Constructs a new segment data object.
bool hasCrc32
whether CRC-32 checksum is present
std::uint64_t newPadding
padding (in the new file)
std::uint64_t totalDataSize
total size of the segment data (in the new file, excluding header)
vector< std::uint64_t > clusterSizes
cluster sizes
std::uint8_t sizeDenotationLength
header size (in the new file)
std::uint64_t newDataOffset
data offset of the segment in the new file
std::uint64_t infoDataSize
size of the "SegmentInfo"-element
std::uint64_t startOffset
start offset (in the new file)
MatroskaSeekInfo seekInfo
used to make "SeekHead"-element
EbmlElement * firstClusterElement
first "Cluster"-element (original file)
std::uint64_t clusterEndOffset
end offset of last "Cluster"-element (original file)
MatroskaCuePositionUpdater cuesUpdater
used to make "Cues"-element
EbmlElement * cuesElement
"Cues"-element (original file)
std::uint64_t totalSize
total size of the segment data (in the new file, including header)