85 static const auto context = std::string(
"validating Matroska file index (cues)");
86 auto cuesElementsFound =
false;
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();
95 segmentElement->parse(diag);
98 segmentChildElement = segmentChildElement->
nextSibling()) {
100 segmentChildElement->parse(diag);
101 switch (segmentChildElement->id()) {
106 cuesElementsFound =
true;
109 cuePointElement = cuePointElement->
nextSibling()) {
111 cuePointElement->parse(diag);
112 cueTimeFound = cueTrackPositionsFound =
false;
113 switch (cuePointElement->id()) {
120 cuePointChildElement = cuePointChildElement->
nextSibling()) {
121 cuePointChildElement->parse(diag);
122 switch (cuePointChildElement->id()) {
127 DiagLevel::Warning,
"\"CuePoint\"-element contains multiple \"CueTime\" elements.", context);
133 cueTrackPositionsFound =
true;
135 clusterElement.reset();
138 subElement->parse(diag);
139 switch (subElement->id()) {
147 if (ids.count(subElement->id())) {
149 "\"CueTrackPositions\"-element contains multiple \"" % subElement->idToString() +
"\" elements.",
152 ids.insert(subElement->id());
161 "\"CueTrackPositions\"-element contains unknown element \"" % subElement->idToString() +
"\".",
164 switch (subElement->id()) {
171 clusterElement = make_unique<EbmlElement>(
172 *
this, segmentElement->dataOffset() + subElement->readUInteger() - currentOffset);
174 clusterElement->parse(diag);
177 "\"CueClusterPosition\" element at " % numberToString(subElement->startOffset())
178 +
" does not point to \"Cluster\"-element (points to "
179 + numberToString(clusterElement->startOffset()) +
").",
187 pos = subElement->readUInteger();
203 "\"CueTrackPositions\"-element does not contain mandatory element \"CueTrack\".", context);
205 if (!clusterElement) {
207 "\"CueTrackPositions\"-element does not contain mandatory element \"CueClusterPosition\".", context);
210 EbmlElement referenceElement(*
this, clusterElement->dataOffset() + pos);
212 referenceElement.
parse(diag);
213 switch (referenceElement.
id()) {
220 "\"CueRelativePosition\" element does not point to \"Block\"-, \"BlockGroup\", or "
221 "\"SimpleBlock\"-element (points to "
235 "\"CuePoint\"-element contains unknown element \"" % cuePointElement->idToString() +
"\".", context);
241 DiagLevel::Warning,
"\"CuePoint\"-element does not contain mandatory element \"CueTime\".", context);
243 if (!cueTrackPositionsFound) {
245 DiagLevel::Warning,
"\"CuePoint\"-element does not contain mandatory element \"CueClusterPosition\".", context);
255 clusterElementChild = clusterElementChild->
nextSibling()) {
256 clusterElementChild->parse(diag);
257 switch (clusterElementChild->id()) {
263 if ((pos = clusterElementChild->readUInteger()) > 0
264 && (segmentChildElement->startOffset() - segmentElement->dataOffset() + currentOffset) != pos) {
266 argsToString(
"\"Position\"-element at ", clusterElementChild->startOffset(),
" points to ", pos,
267 " which is not the offset of the containing \"Cluster\"-element."),
273 if ((pos = clusterElementChild->readUInteger()) != prevClusterSize) {
275 argsToString(
"\"PrevSize\"-element at ", clusterElementChild->startOffset(),
" should be ", prevClusterSize,
276 " but is ", pos,
"."),
283 prevClusterSize = segmentChildElement->totalSize();
288 currentOffset += segmentElement->totalSize();
292 if (!cuesElementsFound) {
403 CPP_UTILITIES_UNUSED(progress)
405 static const string context(
"parsing header of Matroska container");
409 m_tracksElements.clear();
410 m_segmentInfoElements.clear();
411 m_tagsElements.clear();
414 std::uint64_t currentOffset = 0;
415 vector<MatroskaSeekInfo>::difference_type seekInfosIndex = 0;
418 for (
EbmlElement *topLevelElement =
m_firstElement.get(); topLevelElement; topLevelElement = topLevelElement->nextSibling()) {
420 topLevelElement->parse(diag);
421 switch (topLevelElement->id()) {
425 subElement->parse(diag);
426 switch (subElement->id()) {
443 m_maxIdLength = subElement->readUInteger();
447 " bytes is not supported."),
453 m_maxSizeLength = subElement->readUInteger();
457 " bytes is not supported."),
464 diag.emplace_back(
DiagLevel::Critical,
"Unable to parse all children of EBML header.", context);
473 subElement->parse(diag);
474 switch (subElement->id()) {
476 m_seekInfos.emplace_back(make_unique<MatroskaSeekInfo>());
477 m_seekInfos.back()->parse(subElement, diag);
480 if (
excludesOffset(m_tracksElements, subElement->startOffset())) {
481 m_tracksElements.push_back(subElement);
485 if (
excludesOffset(m_segmentInfoElements, subElement->startOffset())) {
486 m_segmentInfoElements.push_back(subElement);
491 m_tagsElements.push_back(subElement);
495 if (
excludesOffset(m_chaptersElements, subElement->startOffset())) {
496 m_chaptersElements.push_back(subElement);
500 if (
excludesOffset(m_attachmentsElements, subElement->startOffset())) {
501 m_attachmentsElements.push_back(subElement);
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;
512 argsToString(
"Offset (", offset,
") denoted by \"SeekHead\" element is invalid."), context);
514 auto element = make_unique<EbmlElement>(*
this, offset);
516 element->parse(diag);
517 if (element->id() != infoPair.first) {
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),
")."),
524 switch (element->id()) {
559 argsToString(
"Can not parse element at ", offset,
" (denoted using \"SeekHead\" element)."), context);
565 if (((!m_tracksElements.empty() && !m_tagsElements.empty()) ||
fileInfo().size() >
fileInfo().maxFullParseSize())
566 && !m_segmentInfoElements.empty()) {
572 diag.emplace_back(
DiagLevel::Critical,
"Unable to parse all children of \"Segment\"-element.", context);
576 currentOffset += topLevelElement->totalSize();
582 DiagLevel::Critical, argsToString(
"Unable to parse top-level element at ", topLevelElement->startOffset(),
'.'), context);
590 parseSegmentInfo(diag);
592 diag.emplace_back(
DiagLevel::Critical,
"Unable to parse EBML (segment) \"Info\"-element.", context);
863 static const string context(
"making Matroska container");
864 progress.
updateStep(
"Calculating element sizes ...");
871 switch (
fileInfo().attachmentsParsingStatus()) {
876 diag.emplace_back(
DiagLevel::Critical,
"Attachments have to be parsed without critical errors before changes can be applied.", context);
882 if (!level0Element) {
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;
904 unsigned int segmentIndex = 0;
906 std::vector<SegmentData> segmentData;
908 std::uint64_t offset;
910 std::uint64_t totalOffset;
912 std::uint64_t currentPosition = 0;
914 std::vector<std::tuple<std::uint64_t, std::uint64_t>> crc32Offsets;
916 std::uint8_t sizeLength;
918 std::uint64_t clusterSize, clusterReadSize, clusterReadOffset;
930 unsigned int lastSegmentIndex = numeric_limits<unsigned int>::max();
932 std::uint64_t newPadding;
938 std::uint64_t ebmlHeaderDataSize = 2 * 7;
940 for (
auto headerValue :
953 ? std::string_view(muxingApps.front())
954 : std::string_view(APP_NAME
" v" APP_VERSION);
960 ? std::string_view(writingApps.front())
961 : std::string_view(
fileInfo().writingApplication().empty() ? muxingAppName : std::string_view(
fileInfo().writingApplication()));
962 const auto writingAppElementTotalSize
969 const auto &maker = tagMaker.emplace_back(
tag->prepareMaking(diag));
970 if (maker.requiredSize() > 3) {
972 tagElementsSize += maker.requiredSize();
983 const auto &maker = attachmentMaker.emplace_back(
attachment->prepareMaking(diag));
984 if (maker.requiredSize() > 3) {
986 attachedFileElementsSize += maker.requiredSize();
998 const auto &maker = trackHeaderMaker.emplace_back(
track->prepareMakingHeader(diag));
999 if (maker.requiredSize() > 3) {
1001 trackHeaderElementsSize += maker.requiredSize();
1013 for (
bool firstClusterFound =
false, firstTagFound =
false; level0Element; level0Element = level0Element->
nextSibling()) {
1014 level0Element->
parse(diag);
1015 switch (level0Element->
id()) {
1018 for (level1Element = level0Element->
firstChild(); level1Element && !firstClusterFound && !firstTagFound;
1020 level1Element->
parse(diag);
1021 switch (level1Element->
id()) {
1024 firstTagFound =
true;
1027 firstClusterFound =
true;
1030 if (firstTagFound) {
1032 }
else if (firstClusterFound) {
1039 segmentData.resize(lastSegmentIndex + 1);
1050 "Unable to parse content in top-level element at " % numberToString(level0Element->
startOffset()) +
" of original file.", context);
1054 progress.
nextStepOrStop(
"Calculating offsets of elements before cluster ...");
1055 calculateSegmentData:
1058 std::uint64_t currentOffset = ebmlHeaderSize;
1060 std::uint64_t readOffset = 0;
1065 if (rewriteRequired) {
1076 for (level0Element =
firstElement(), currentPosition = newPadding = segmentIndex = 0; level0Element;
1078 switch (level0Element->
id()) {
1107 newCuesPos = currentCuesPos;
1120 calculateSegmentSize:
1133 goto calculateSegmentSize;
1137 segment.
infoDataSize = muxingAppElementTotalSize + writingAppElementTotalSize;
1139 if (segmentIndex <
m_titles.size()) {
1140 const auto &title =
m_titles[segmentIndex];
1141 if (!title.empty()) {
1146 for (level2Element = level1Element->
firstChild(); level2Element; level2Element = level2Element->
nextSibling()) {
1147 level2Element->
parse(diag);
1148 switch (level2Element->
id()) {
1166 if (trackHeaderSize) {
1169 goto calculateSegmentSize;
1181 goto calculateSegmentSize;
1197 goto calculateSegmentSize;
1204 if (attachmentsSize) {
1207 goto calculateSegmentSize;
1221 goto calculateSegmentSize;
1224 progress.
updateStep(
"Calculating cluster offsets and index size ...");
1229 progress.
updateStep(
"Calculating cluster offsets ...");
1233 if (!rewriteRequired) {
1240 if (totalOffset <= segment.firstClusterElement->
startOffset()) {
1249 diag.emplace_back(
DiagLevel::Critical,
"Header size of \"Segment\"-element from original file is invalid.", context);
1254 nonRewriteCalculations:
1259 goto calculateSegmentSize;
1262 bool cuesInvalidated =
false;
1270 cuesInvalidated =
true;
1275 if (index % 50 == 0) {
1279 if (cuesInvalidated) {
1281 goto addCuesElementSize;
1286 progress.
updateStep(
"Calculating offsets of elements after cluster ...");
1290 goto calculateSegmentSize;
1302 goto calculateSegmentSize;
1309 if (attachmentsSize) {
1312 goto calculateSegmentSize;
1326 goto nonRewriteCalculations;
1329 totalOffset = currentOffset + 4 + sizeLength + offset;
1334 if (totalOffset <= segment.firstClusterElement->
startOffset()) {
1340 rewriteRequired =
true;
1343 rewriteRequired =
true;
1346 rewriteRequired =
true;
1349 diag.emplace_back(
DiagLevel::Warning, argsToString(
"There are no clusters in segment ", segmentIndex,
"."), context);
1352 if (rewriteRequired) {
1358 rewriteRequired =
false;
1360 && (!
fileInfo().forceIndexPosition()
1364 rewriteRequired =
false;
1367 goto calculateSegmentData;
1380 bool cuesInvalidated =
false;
1386 cuesInvalidated =
true;
1389 goto calculateSegmentSize;
1392 clusterSize = clusterReadSize = 0;
1393 for (level2Element = level1Element->
firstChild(); level2Element; level2Element = level2Element->
nextSibling()) {
1394 level2Element->
parse(diag);
1398 cuesInvalidated =
true;
1400 switch (level2Element->
id()) {
1408 clusterSize += level2Element->
totalSize();
1410 clusterReadSize += level2Element->
totalSize();
1419 if ((index % 50 == 0) &&
fileInfo().size()) {
1425 if (cuesInvalidated) {
1428 goto addCuesElementSize;
1432 progress.
updateStep(
"Calculating offsets of elements after cluster ...");
1436 goto calculateSegmentSize;
1451 goto calculateSegmentSize;
1458 if (attachmentsSize) {
1461 goto calculateSegmentSize;
1479 readOffset += level0Element->
totalSize();
1486 "The top-level element \"" % level0Element->
idToString() +
"\" of the original file is unknown and will just be copied.",
1488 currentOffset += level0Element->
totalSize();
1489 readOffset += level0Element->
totalSize();
1493 if (!rewriteRequired) {
1495 if ((rewriteRequired = (newPadding >
fileInfo().maxPadding() || newPadding <
fileInfo().minPadding()))) {
1497 goto calculateSegmentData;
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);
1519 NativeFileStream backupStream;
1520 BinaryWriter outputWriter(&outputStream);
1523 if (rewriteRequired) {
1524 if (
fileInfo().saveFilePath().empty()) {
1529 outputStream.open(originalPath, ios_base::out | ios_base::binary | ios_base::trunc);
1530 }
catch (
const std::ios_base::failure &failure) {
1532 DiagLevel::Critical, argsToString(
"Creation of temporary file (to rewrite the original file) failed: ", failure.what()), context);
1538 backupStream.exceptions(ios_base::badbit | ios_base::failbit);
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);
1555 for (
auto &maker : attachmentMaker) {
1556 maker.bufferCurrentAttachments(diag);
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);
1575 outputStream.write(buff, sizeLength);
1585 for (level0Element =
firstElement(), segmentIndex = 0, currentPosition = 0; level0Element; level0Element = level0Element->
nextSibling()) {
1588 switch (level0Element->
id()) {
1603 progress.
updateStep(
"Writing segment header ...");
1606 outputStream.write(buff, sizeLength);
1607 segment.
newDataOffset = offset =
static_cast<std::uint64_t
>(outputStream.tellp());
1613 *(buff + 1) =
static_cast<char>(0x84);
1615 crc32Offsets.emplace_back(outputStream.tellp(), segment.
totalDataSize);
1616 outputStream.write(buff, 6);
1628 outputStream.write(buff, sizeLength);
1630 for (level2Element = level1Element->
firstChild(); level2Element; level2Element = level2Element->
nextSibling()) {
1631 switch (level2Element->
id()) {
1644 if (segmentIndex <
m_titles.size()) {
1645 const auto &title =
m_titles[segmentIndex];
1646 if (!title.empty()) {
1656 if (trackHeaderElementsSize) {
1659 outputStream.write(buff, sizeLength);
1660 for (
auto &maker : trackHeaderMaker) {
1661 maker.make(outputStream);
1677 outputStream.write(buff, sizeLength);
1678 for (
auto &maker : tagMaker) {
1679 maker.make(outputStream);
1683 if (attachmentsSize) {
1686 outputStream.write(buff, sizeLength);
1687 for (
auto &maker : attachmentMaker) {
1688 maker.make(outputStream, diag);
1701 std::uint64_t voidLength;
1704 *buff =
static_cast<char>(voidLength = segment.
newPadding - 2) |
static_cast<char>(0x80);
1707 BE::getBytes(
static_cast<std::uint64_t
>((voidLength = segment.
newPadding - 9) | 0x100000000000000), buff);
1711 outputStream.write(buff, sizeLength);
1718 if (rewriteRequired) {
1721 static_cast<std::uint8_t
>((
static_cast<std::uint64_t
>(outputStream.tellp()) - offset) * 100 / segment.
totalDataSize));
1723 auto clusterSizesIterator = segment.
clusterSizes.cbegin();
1724 unsigned int index = 0;
1727 clusterSize = currentPosition + (
static_cast<std::uint64_t
>(outputStream.tellp()) - offset);
1731 outputStream.write(buff, sizeLength);
1733 for (level2Element = level1Element->
firstChild(); level2Element; level2Element = level2Element->
nextSibling()) {
1734 switch (level2Element->
id()) {
1742 level2Element->
copyEntirely(outputStream, diag,
nullptr);
1747 if (index % 50 == 0) {
1749 static_cast<std::uint8_t
>((
static_cast<std::uint64_t
>(outputStream.tellp()) - offset) * 100 / segment.
totalDataSize));
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()) {
1762 level2Element->
dataSize() > 8 ? 8 :
static_cast<std::uint8_t
>(level2Element->
dataSize()));
1764 if (level2Element->
dataSize() < sizeLength) {
1766 outputStream.seekp(
static_cast<streamoff
>(level2Element->
startOffset()));
1770 outputStream.seekp(
static_cast<streamoff
>(level2Element->
dataOffset()));
1771 outputStream.write(buff, sizeLength);
1782 progress.
updateStep(
"Writing segment tail ...");
1794 outputStream.write(buff, sizeLength);
1795 for (
auto &maker : tagMaker) {
1796 maker.make(outputStream);
1800 if (attachmentsSize) {
1803 outputStream.write(buff, sizeLength);
1804 for (
auto &maker : attachmentMaker) {
1805 maker.make(outputStream, diag);
1820 level0Element->
copyEntirely(outputStream, diag,
nullptr);
1821 currentPosition += level0Element->
totalSize();
1826 progress.
updateStep(
"Reparsing output file ...");
1827 if (rewriteRequired) {
1832 if (!
fileInfo().saveFilePath().empty()) {
1838 outputStream.close();
1839 outputStream.open(
fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary);
1842 const auto newSize =
static_cast<std::uint64_t
>(outputStream.tellp());
1846 outputStream.close();
1848 auto ec = std::error_code();
1849 std::filesystem::resize_file(makeNativePath(
fileInfo().path()), newSize, ec);
1853 diag.emplace_back(
DiagLevel::Critical,
"Unable to truncate the file: " + ec.message(), context);
1856 outputStream.open(
fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary);
1868 diag.emplace_back(
DiagLevel::Critical,
"Unable to reparse the header of the new file.", context);
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));
1883 outputStream.flush();