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>
16#include <c++utilities/io/path.h>
21#include <initializer_list>
25#include <unordered_set>
28using namespace std::placeholders;
68 m_tracksElements.clear();
69 m_segmentInfoElements.clear();
70 m_tagsElements.clear();
71 m_chaptersElements.clear();
72 m_attachmentsElements.clear();
74 m_editionEntries.clear();
75 m_attachments.clear();
85 static const string context(
"validating Matroska file index (cues)");
86 bool cuesElementsFound =
false;
88 unordered_set<EbmlElement::IdentifierType> ids;
89 bool cueTimeFound =
false, cueTrackPositionsFound =
false;
90 unique_ptr<EbmlElement> clusterElement;
91 std::uint64_t pos, prevClusterSize = 0, currentOffset = 0;
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) {
309inline bool excludesOffset(
const vector<EbmlElement *> &elements, std::uint64_t offset)
311 return find_if(elements.cbegin(), elements.cend(), std::bind(
sameOffset, offset, _1)) == elements.cend();
316 for (
const auto &entry : m_editionEntries) {
317 const auto &chapters = entry->chapters();
318 if (index < chapters.size()) {
319 return chapters[index].get();
321 index -= chapters.size();
330 for (
const auto &entry : m_editionEntries) {
331 count += entry->chapters().size();
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;
345 attachmentId = dice();
347 for (
const auto &
attachment : m_attachments) {
350 goto generateRandomId;
355 m_attachments.emplace_back(make_unique<MatroskaAttachment>());
371 if (!segmentElement) {
374 for (
const EbmlElement *childElement = segmentElement->
firstChild(); childElement; childElement = childElement->nextSibling()) {
375 if (childElement->id() == elementId) {
378 for (
const auto &seekInfo : m_seekInfos) {
379 for (
const auto &info : seekInfo->info()) {
380 if (info.first == elementId) {
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);
471 for (
EbmlElement *subElement = topLevelElement->
firstChild(); subElement; subElement = subElement->nextSibling()) {
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);
605void MatroskaContainer::parseSegmentInfo(
Diagnostics &diag)
607 if (m_segmentInfoElements.empty()) {
611 for (EbmlElement *element : m_segmentInfoElements) {
612 element->parse(diag);
613 EbmlElement *subElement = element->firstChild();
614 double rawDuration = 0.0;
616 bool hasTitle =
false;
618 subElement->parse(diag);
619 switch (subElement->id()) {
621 m_titles.emplace_back(subElement->readString());
625 rawDuration = subElement->readFloat();
631 subElement = subElement->nextSibling();
638 if (rawDuration > 0.0) {
639 m_duration += TimeSpan::fromSeconds(rawDuration *
static_cast<double>(
timeScale) / 1000000000.0);
649void MatroskaContainer::readTrackStatisticsFromTags(Diagnostics &diag)
661 CPP_UTILITIES_UNUSED(progress)
663 static const string context(
"parsing tags of Matroska container");
668 for (
EbmlElement *
const element : m_tagsElements) {
670 element->parse(diag);
672 subElement->parse(diag);
673 switch (subElement->id()) {
675 m_tags.emplace_back(make_unique<MatroskaTag>());
677 m_tags.back()->parse2(*subElement, flags, diag);
688 diag.emplace_back(
DiagLevel::Warning,
"\"Tags\"-element contains unknown child. It will be ignored.", context);
693 readTrackStatisticsFromTags(diag);
697 readTrackStatisticsFromTags(diag);
702 static const string context(
"parsing tracks of Matroska container");
705 element->parse(diag);
707 subElement->parse(diag);
708 switch (subElement->id()) {
710 m_tracks.emplace_back(make_unique<MatroskaTrack>(*subElement));
712 m_tracks.back()->parseHeader(diag, progress);
724 "\"Tracks\"-element contains unknown child element \"" % subElement->idToString() +
"\". It will be ignored.", context);
729 readTrackStatisticsFromTags(diag);
733 readTrackStatisticsFromTags(diag);
738 static const string context(
"parsing editions/chapters of Matroska container");
741 element->parse(diag);
743 subElement->parse(diag);
744 switch (subElement->id()) {
746 m_editionEntries.emplace_back(make_unique<MatroskaEditionEntry>(subElement));
748 m_editionEntries.back()->parseNested(diag, progress);
750 m_editionEntries.pop_back();
752 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Unable to parse edition entry ", m_editionEntries.size(),
'.'), context);
760 "\"Chapters\"-element contains unknown child element \"" % subElement->idToString() +
"\". It will be ignored.", context);
772 CPP_UTILITIES_UNUSED(progress)
774 static const string context(
"parsing attachments of Matroska container");
775 for (
EbmlElement *element : m_attachmentsElements) {
777 element->parse(diag);
779 subElement->parse(diag);
780 switch (subElement->id()) {
782 m_attachments.emplace_back(make_unique<MatroskaAttachment>());
784 m_attachments.back()->parse(subElement, diag);
786 m_attachments.pop_back();
788 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Unable to parse attached file ", m_attachments.size(),
'.'), context);
796 "\"Attachments\"-element contains unknown child element \"" % subElement->idToString() +
"\". It will be ignored.", context);
857 static const string context(
"making Matroska container");
858 progress.
updateStep(
"Calculating element sizes ...");
865 switch (
fileInfo().attachmentsParsingStatus()) {
870 diag.emplace_back(
DiagLevel::Critical,
"Attachments have to be parsed without critical errors before changes can be applied.", context);
876 if (!level0Element) {
883 std::vector<MatroskaTagMaker> tagMaker;
884 tagMaker.reserve(
tags().size());
885 std::uint64_t tagElementsSize = 0;
886 std::uint64_t tagsSize;
887 std::vector<MatroskaAttachmentMaker> attachmentMaker;
888 attachmentMaker.reserve(m_attachments.size());
889 std::uint64_t attachedFileElementsSize = 0;
890 std::uint64_t attachmentsSize;
891 std::vector<MatroskaTrackHeaderMaker> trackHeaderMaker;
892 trackHeaderMaker.reserve(
tracks().size());
893 std::uint64_t trackHeaderElementsSize = 0;
894 std::uint64_t trackHeaderSize;
898 unsigned int segmentIndex = 0;
900 std::vector<SegmentData> segmentData;
902 std::uint64_t offset;
904 std::uint64_t totalOffset;
906 std::uint64_t currentPosition = 0;
908 std::vector<std::tuple<std::uint64_t, std::uint64_t>> crc32Offsets;
910 std::uint8_t sizeLength;
912 std::uint64_t clusterSize, clusterReadSize, clusterReadOffset;
924 unsigned int lastSegmentIndex = numeric_limits<unsigned int>::max();
926 std::uint64_t newPadding;
932 std::uint64_t ebmlHeaderDataSize = 2 * 7;
934 for (
auto headerValue :
945 constexpr std::string_view muxingAppName = APP_NAME
" v" APP_VERSION;
946 constexpr std::uint64_t muxingAppElementTotalSize = 2 + 1 + muxingAppName.size();
949 const std::uint64_t writingAppElementDataSize
951 const std::uint64_t writingAppElementTotalSize = 2 + 1 + writingAppElementDataSize;
958 if (maker.requiredSize() > 3) {
960 tagElementsSize += maker.requiredSize();
972 if (maker.requiredSize() > 3) {
974 attachedFileElementsSize += maker.requiredSize();
987 if (maker.requiredSize() > 3) {
989 trackHeaderElementsSize += maker.requiredSize();
1001 for (
bool firstClusterFound =
false, firstTagFound =
false; level0Element; level0Element = level0Element->
nextSibling()) {
1002 level0Element->
parse(diag);
1003 switch (level0Element->
id()) {
1006 for (level1Element = level0Element->
firstChild(); level1Element && !firstClusterFound && !firstTagFound;
1008 level1Element->
parse(diag);
1009 switch (level1Element->
id()) {
1012 firstTagFound =
true;
1015 firstClusterFound =
true;
1018 if (firstTagFound) {
1020 }
else if (firstClusterFound) {
1027 segmentData.resize(lastSegmentIndex + 1);
1038 "Unable to parse content in top-level element at " % numberToString(level0Element->
startOffset()) +
" of original file.", context);
1042 progress.
nextStepOrStop(
"Calculating offsets of elements before cluster ...");
1043 calculateSegmentData:
1046 std::uint64_t currentOffset = ebmlHeaderSize;
1048 std::uint64_t readOffset = 0;
1053 if (rewriteRequired) {
1064 for (level0Element =
firstElement(), currentPosition = newPadding = segmentIndex = 0; level0Element;
1066 switch (level0Element->
id()) {
1095 newCuesPos = currentCuesPos;
1108 calculateSegmentSize:
1121 goto calculateSegmentSize;
1125 segment.
infoDataSize = muxingAppElementTotalSize + writingAppElementTotalSize;
1127 if (segmentIndex <
m_titles.size()) {
1128 const auto &title =
m_titles[segmentIndex];
1129 if (!title.empty()) {
1134 for (level2Element = level1Element->
firstChild(); level2Element; level2Element = level2Element->
nextSibling()) {
1135 level2Element->
parse(diag);
1136 switch (level2Element->
id()) {
1154 if (trackHeaderSize) {
1157 goto calculateSegmentSize;
1169 goto calculateSegmentSize;
1185 goto calculateSegmentSize;
1192 if (attachmentsSize) {
1195 goto calculateSegmentSize;
1209 goto calculateSegmentSize;
1212 progress.
updateStep(
"Calculating cluster offsets and index size ...");
1217 progress.
updateStep(
"Calculating cluster offsets ...");
1221 if (!rewriteRequired) {
1228 if (totalOffset <= segment.firstClusterElement->
startOffset()) {
1237 diag.emplace_back(
DiagLevel::Critical,
"Header size of \"Segment\"-element from original file is invalid.", context);
1242 nonRewriteCalculations:
1247 goto calculateSegmentSize;
1250 bool cuesInvalidated =
false;
1258 cuesInvalidated =
true;
1263 if (index % 50 == 0) {
1267 if (cuesInvalidated) {
1269 goto addCuesElementSize;
1274 progress.
updateStep(
"Calculating offsets of elements after cluster ...");
1278 goto calculateSegmentSize;
1290 goto calculateSegmentSize;
1297 if (attachmentsSize) {
1300 goto calculateSegmentSize;
1314 goto nonRewriteCalculations;
1317 totalOffset = currentOffset + 4 + sizeLength + offset;
1322 if (totalOffset <= segment.firstClusterElement->
startOffset()) {
1328 rewriteRequired =
true;
1331 rewriteRequired =
true;
1334 rewriteRequired =
true;
1337 diag.emplace_back(
DiagLevel::Warning, argsToString(
"There are no clusters in segment ", segmentIndex,
"."), context);
1340 if (rewriteRequired) {
1346 rewriteRequired =
false;
1352 rewriteRequired =
false;
1355 goto calculateSegmentData;
1368 bool cuesInvalidated =
false;
1374 cuesInvalidated =
true;
1377 goto calculateSegmentSize;
1380 clusterSize = clusterReadSize = 0;
1381 for (level2Element = level1Element->
firstChild(); level2Element; level2Element = level2Element->
nextSibling()) {
1382 level2Element->
parse(diag);
1386 cuesInvalidated =
true;
1388 switch (level2Element->
id()) {
1396 clusterSize += level2Element->
totalSize();
1398 clusterReadSize += level2Element->
totalSize();
1407 if ((index % 50 == 0) &&
fileInfo().size()) {
1413 if (cuesInvalidated) {
1416 goto addCuesElementSize;
1420 progress.
updateStep(
"Calculating offsets of elements after cluster ...");
1424 goto calculateSegmentSize;
1439 goto calculateSegmentSize;
1446 if (attachmentsSize) {
1449 goto calculateSegmentSize;
1467 readOffset += level0Element->
totalSize();
1474 "The top-level element \"" % level0Element->
idToString() +
"\" of the original file is unknown and will just be copied.",
1476 currentOffset += level0Element->
totalSize();
1477 readOffset += level0Element->
totalSize();
1481 if (!rewriteRequired) {
1483 if ((rewriteRequired = (newPadding >
fileInfo().maxPadding() || newPadding <
fileInfo().minPadding()))) {
1485 goto calculateSegmentData;
1495 }
catch (
const std::ios_base::failure &failure) {
1496 diag.emplace_back(
DiagLevel::Critical, argsToString(
"An IO error occurred when parsing the original file: ", failure.what()), context);
1507 NativeFileStream backupStream;
1508 BinaryWriter outputWriter(&outputStream);
1511 if (rewriteRequired) {
1512 if (
fileInfo().saveFilePath().empty()) {
1517 outputStream.open(originalPath, ios_base::out | ios_base::binary | ios_base::trunc);
1518 }
catch (
const std::ios_base::failure &failure) {
1520 DiagLevel::Critical, argsToString(
"Creation of temporary file (to rewrite the original file) failed: ", failure.what()), context);
1526 backupStream.exceptions(ios_base::badbit | ios_base::failbit);
1530 }
catch (
const std::ios_base::failure &failure) {
1531 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Opening streams to write output file failed: ", failure.what()), context);
1543 for (
auto &maker : attachmentMaker) {
1544 maker.bufferCurrentAttachments(diag);
1550 outputStream.open(
fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary);
1551 }
catch (
const std::ios_base::failure &failure) {
1552 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Opening the file with write permissions failed: ", failure.what()), context);
1563 outputStream.write(buff, sizeLength);
1573 for (level0Element =
firstElement(), segmentIndex = 0, currentPosition = 0; level0Element; level0Element = level0Element->
nextSibling()) {
1576 switch (level0Element->
id()) {
1591 progress.
updateStep(
"Writing segment header ...");
1594 outputStream.write(buff, sizeLength);
1595 segment.
newDataOffset = offset =
static_cast<std::uint64_t
>(outputStream.tellp());
1601 *(buff + 1) =
static_cast<char>(0x84);
1603 crc32Offsets.emplace_back(outputStream.tellp(), segment.
totalDataSize);
1604 outputStream.write(buff, 6);
1616 outputStream.write(buff, sizeLength);
1618 for (level2Element = level1Element->
firstChild(); level2Element; level2Element = level2Element->
nextSibling()) {
1619 switch (level2Element->
id()) {
1632 if (segmentIndex <
m_titles.size()) {
1633 const auto &title =
m_titles[segmentIndex];
1634 if (!title.empty()) {
1645 if (trackHeaderElementsSize) {
1648 outputStream.write(buff, sizeLength);
1649 for (
auto &maker : trackHeaderMaker) {
1650 maker.make(outputStream);
1666 outputStream.write(buff, sizeLength);
1667 for (
auto &maker : tagMaker) {
1668 maker.make(outputStream);
1672 if (attachmentsSize) {
1675 outputStream.write(buff, sizeLength);
1676 for (
auto &maker : attachmentMaker) {
1677 maker.make(outputStream, diag);
1690 std::uint64_t voidLength;
1693 *buff =
static_cast<char>(voidLength = segment.
newPadding - 2) |
static_cast<char>(0x80);
1696 BE::getBytes(
static_cast<std::uint64_t
>((voidLength = segment.
newPadding - 9) | 0x100000000000000), buff);
1700 outputStream.write(buff, sizeLength);
1702 for (; voidLength; --voidLength) {
1703 outputStream.put(0);
1709 if (rewriteRequired) {
1712 static_cast<std::uint8_t
>((
static_cast<std::uint64_t
>(outputStream.tellp()) - offset) * 100 / segment.
totalDataSize));
1714 auto clusterSizesIterator = segment.
clusterSizes.cbegin();
1715 unsigned int index = 0;
1718 clusterSize = currentPosition + (
static_cast<std::uint64_t
>(outputStream.tellp()) - offset);
1722 outputStream.write(buff, sizeLength);
1724 for (level2Element = level1Element->
firstChild(); level2Element; level2Element = level2Element->
nextSibling()) {
1725 switch (level2Element->
id()) {
1733 level2Element->
copyEntirely(outputStream, diag,
nullptr);
1738 if (index % 50 == 0) {
1740 static_cast<std::uint8_t
>((
static_cast<std::uint64_t
>(outputStream.tellp()) - offset) * 100 / segment.
totalDataSize));
1746 static_cast<std::uint8_t
>((
static_cast<std::uint64_t
>(outputStream.tellp()) - offset) * 100 / segment.
totalDataSize));
1747 for (; level1Element; level1Element = level1Element->
nextSibling()) {
1748 for (level2Element = level1Element->
firstChild(); level2Element; level2Element = level2Element->
nextSibling()) {
1749 switch (level2Element->
id()) {
1753 level2Element->
dataSize() > 8 ? 8 :
static_cast<std::uint8_t
>(level2Element->
dataSize()));
1755 if (level2Element->
dataSize() < sizeLength) {
1757 outputStream.seekp(
static_cast<streamoff
>(level2Element->
startOffset()));
1761 outputStream.seekp(
static_cast<streamoff
>(level2Element->
dataOffset()));
1762 outputStream.write(buff, sizeLength);
1773 progress.
updateStep(
"Writing segment tail ...");
1785 outputStream.write(buff, sizeLength);
1786 for (
auto &maker : tagMaker) {
1787 maker.make(outputStream);
1791 if (attachmentsSize) {
1794 outputStream.write(buff, sizeLength);
1795 for (
auto &maker : attachmentMaker) {
1796 maker.make(outputStream, diag);
1811 level0Element->
copyEntirely(outputStream, diag,
nullptr);
1812 currentPosition += level0Element->
totalSize();
1817 progress.
updateStep(
"Reparsing output file ...");
1818 if (rewriteRequired) {
1823 if (!
fileInfo().saveFilePath().empty()) {
1829 outputStream.close();
1830 outputStream.open(
fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary);
1833 const auto newSize =
static_cast<std::uint64_t
>(outputStream.tellp());
1837 outputStream.close();
1839 auto ec = std::error_code();
1840 std::filesystem::resize_file(makeNativePath(
fileInfo().path()), newSize, ec);
1844 diag.emplace_back(
DiagLevel::Critical,
"Unable to truncate the file: " + ec.message(), context);
1847 outputStream.open(
fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary);
1859 diag.emplace_back(
DiagLevel::Critical,
"Unable to reparse the header of the new file.", context);
1864 if (!crc32Offsets.empty()) {
1865 progress.
updateStep(
"Updating CRC-32 checksums ...");
1866 for (
const auto &crc32Offset : crc32Offsets) {
1867 outputStream.seekg(
static_cast<streamoff
>(get<0>(crc32Offset) + 6));
1868 outputStream.seekp(
static_cast<streamoff
>(get<0>(crc32Offset) + 2));
1869 writer().writeUInt32LE(
reader().readCrc32(get<1>(crc32Offset) - 6));
1874 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.
void copyEntirely(TargetStream &targetStream, Diagnostics &diag, AbortableProgressFeedback *progress)
Writes the entire element including all children to the specified targetStream.
void copyBuffer(TargetStream &targetStream)
Copies buffered data to targetStream.
std::uint64_t startOffset() const
Returns the start offset in the related stream.
void discardBuffer()
Discards buffered data.
std::uint32_t headerSize() const
Returns the header size of the element in byte.
std::uint64_t endOffset() const
Returns the offset of the first byte which doesn't belong to this element anymore.
const IdentifierType & id() const
Returns the element ID.
ImplementationType * childById(const IdentifierType &id, Diagnostics &diag)
Returns the first child with the specified id.
ImplementationType * nextSibling()
Returns the next sibling of the element.
ImplementationType * firstChild()
Returns the first child of the element.
static constexpr std::uint32_t maximumIdLengthSupported()
Returns the maximum id length supported by the class in byte.
DataSizeType dataSize() const
Returns the data size of the element in byte.
std::uint64_t totalSize() const
Returns the total size of the element.
void parse(Diagnostics &diag)
Parses the header information of the element which is read from the related stream at the start offse...
static constexpr std::uint32_t maximumSizeLengthSupported()
Returns the maximum size length supported by the class in byte.
std::uint64_t dataOffset() const
Returns the data offset of the element in the related stream.
void makeBuffer()
Buffers the element (header and data).
ImplementationType * siblingById(const IdentifierType &id, Diagnostics &diag)
Returns the first sibling with the specified id.
The exception that is thrown when the data to be parsed or to be made seems invalid and therefore can...
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.
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)
std::uint8_t sizeDenotationLength
header size (in the new file)
std::vector< std::uint64_t > clusterSizes
cluster sizes
std::uint64_t newDataOffset
data offset of the segment in the new file
std::uint64_t infoDataSize
size of the "SegmentInfo"-element
std::uint64_t startOffset
start offset (in the new file)
MatroskaSeekInfo seekInfo
used to make "SeekHead"-element
SegmentData(SegmentData &&)=default
EbmlElement * firstClusterElement
first "Cluster"-element (original file)
std::uint64_t clusterEndOffset
end offset of last "Cluster"-element (original file)
MatroskaCuePositionUpdater cuesUpdater
used to make "Cues"-element
EbmlElement * cuesElement
"Cues"-element (original file)
std::uint64_t totalSize
total size of the segment data (in the new file, including header)