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();
637 subElement = subElement->nextSibling();
644 if (rawDuration > 0.0) {
645 m_duration += TimeSpan::fromSeconds(rawDuration *
static_cast<double>(
timeScale) / 1000000000.0);
655void MatroskaContainer::readTrackStatisticsFromTags(Diagnostics &diag)
667 CPP_UTILITIES_UNUSED(progress)
669 static const string context(
"parsing tags of Matroska container");
674 for (
EbmlElement *
const element : m_tagsElements) {
676 element->parse(diag);
678 subElement->parse(diag);
679 switch (subElement->id()) {
681 m_tags.emplace_back(make_unique<MatroskaTag>());
683 m_tags.back()->parse2(*subElement, flags, diag);
694 diag.emplace_back(
DiagLevel::Warning,
"\"Tags\"-element contains unknown child. It will be ignored.", context);
699 readTrackStatisticsFromTags(diag);
703 readTrackStatisticsFromTags(diag);
708 static const string context(
"parsing tracks of Matroska container");
711 element->parse(diag);
713 subElement->parse(diag);
714 switch (subElement->id()) {
716 m_tracks.emplace_back(make_unique<MatroskaTrack>(*subElement));
718 m_tracks.back()->parseHeader(diag, progress);
730 "\"Tracks\"-element contains unknown child element \"" % subElement->idToString() +
"\". It will be ignored.", context);
735 readTrackStatisticsFromTags(diag);
739 readTrackStatisticsFromTags(diag);
744 static const string context(
"parsing editions/chapters of Matroska container");
747 element->parse(diag);
749 subElement->parse(diag);
750 switch (subElement->id()) {
752 m_editionEntries.emplace_back(make_unique<MatroskaEditionEntry>(subElement));
754 m_editionEntries.back()->parseNested(diag, progress);
756 m_editionEntries.pop_back();
758 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Unable to parse edition entry ", m_editionEntries.size(),
'.'), context);
766 "\"Chapters\"-element contains unknown child element \"" % subElement->idToString() +
"\". It will be ignored.", context);
778 CPP_UTILITIES_UNUSED(progress)
780 static const string context(
"parsing attachments of Matroska container");
781 for (
EbmlElement *element : m_attachmentsElements) {
783 element->parse(diag);
785 subElement->parse(diag);
786 switch (subElement->id()) {
788 m_attachments.emplace_back(make_unique<MatroskaAttachment>());
790 m_attachments.back()->parse(subElement, diag);
792 m_attachments.pop_back();
794 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Unable to parse attached file ", m_attachments.size(),
'.'), context);
802 "\"Attachments\"-element contains unknown child element \"" % subElement->idToString() +
"\". It will be ignored.", 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);
955 const auto muxingAppElementTotalSize = std::uint64_t(2 + 1 + muxingAppName.size());
960 ? std::string_view(writingApps.front())
961 : std::string_view(
fileInfo().writingApplication().empty() ? muxingAppName : std::string_view(
fileInfo().writingApplication()));
962 const auto writingAppElementTotalSize = std::uint64_t(2 + 1 + writingAppName.size());
969 if (maker.requiredSize() > 3) {
971 tagElementsSize += maker.requiredSize();
983 if (maker.requiredSize() > 3) {
985 attachedFileElementsSize += maker.requiredSize();
998 if (maker.requiredSize() > 3) {
1000 trackHeaderElementsSize += maker.requiredSize();
1012 for (
bool firstClusterFound =
false, firstTagFound =
false; level0Element; level0Element = level0Element->
nextSibling()) {
1013 level0Element->
parse(diag);
1014 switch (level0Element->
id()) {
1017 for (level1Element = level0Element->
firstChild(); level1Element && !firstClusterFound && !firstTagFound;
1019 level1Element->
parse(diag);
1020 switch (level1Element->
id()) {
1023 firstTagFound =
true;
1026 firstClusterFound =
true;
1029 if (firstTagFound) {
1031 }
else if (firstClusterFound) {
1038 segmentData.resize(lastSegmentIndex + 1);
1049 "Unable to parse content in top-level element at " % numberToString(level0Element->
startOffset()) +
" of original file.", context);
1053 progress.
nextStepOrStop(
"Calculating offsets of elements before cluster ...");
1054 calculateSegmentData:
1057 std::uint64_t currentOffset = ebmlHeaderSize;
1059 std::uint64_t readOffset = 0;
1064 if (rewriteRequired) {
1075 for (level0Element =
firstElement(), currentPosition = newPadding = segmentIndex = 0; level0Element;
1077 switch (level0Element->
id()) {
1106 newCuesPos = currentCuesPos;
1119 calculateSegmentSize:
1132 goto calculateSegmentSize;
1136 segment.
infoDataSize = muxingAppElementTotalSize + writingAppElementTotalSize;
1138 if (segmentIndex <
m_titles.size()) {
1139 const auto &title =
m_titles[segmentIndex];
1140 if (!title.empty()) {
1145 for (level2Element = level1Element->
firstChild(); level2Element; level2Element = level2Element->
nextSibling()) {
1146 level2Element->
parse(diag);
1147 switch (level2Element->
id()) {
1165 if (trackHeaderSize) {
1168 goto calculateSegmentSize;
1180 goto calculateSegmentSize;
1196 goto calculateSegmentSize;
1203 if (attachmentsSize) {
1206 goto calculateSegmentSize;
1220 goto calculateSegmentSize;
1223 progress.
updateStep(
"Calculating cluster offsets and index size ...");
1228 progress.
updateStep(
"Calculating cluster offsets ...");
1232 if (!rewriteRequired) {
1239 if (totalOffset <= segment.firstClusterElement->
startOffset()) {
1248 diag.emplace_back(
DiagLevel::Critical,
"Header size of \"Segment\"-element from original file is invalid.", context);
1253 nonRewriteCalculations:
1258 goto calculateSegmentSize;
1261 bool cuesInvalidated =
false;
1269 cuesInvalidated =
true;
1274 if (index % 50 == 0) {
1278 if (cuesInvalidated) {
1280 goto addCuesElementSize;
1285 progress.
updateStep(
"Calculating offsets of elements after cluster ...");
1289 goto calculateSegmentSize;
1301 goto calculateSegmentSize;
1308 if (attachmentsSize) {
1311 goto calculateSegmentSize;
1325 goto nonRewriteCalculations;
1328 totalOffset = currentOffset + 4 + sizeLength + offset;
1333 if (totalOffset <= segment.firstClusterElement->
startOffset()) {
1339 rewriteRequired =
true;
1342 rewriteRequired =
true;
1345 rewriteRequired =
true;
1348 diag.emplace_back(
DiagLevel::Warning, argsToString(
"There are no clusters in segment ", segmentIndex,
"."), context);
1351 if (rewriteRequired) {
1357 rewriteRequired =
false;
1363 rewriteRequired =
false;
1366 goto calculateSegmentData;
1379 bool cuesInvalidated =
false;
1385 cuesInvalidated =
true;
1388 goto calculateSegmentSize;
1391 clusterSize = clusterReadSize = 0;
1392 for (level2Element = level1Element->
firstChild(); level2Element; level2Element = level2Element->
nextSibling()) {
1393 level2Element->
parse(diag);
1397 cuesInvalidated =
true;
1399 switch (level2Element->
id()) {
1407 clusterSize += level2Element->
totalSize();
1409 clusterReadSize += level2Element->
totalSize();
1418 if ((index % 50 == 0) &&
fileInfo().size()) {
1424 if (cuesInvalidated) {
1427 goto addCuesElementSize;
1431 progress.
updateStep(
"Calculating offsets of elements after cluster ...");
1435 goto calculateSegmentSize;
1450 goto calculateSegmentSize;
1457 if (attachmentsSize) {
1460 goto calculateSegmentSize;
1478 readOffset += level0Element->
totalSize();
1485 "The top-level element \"" % level0Element->
idToString() +
"\" of the original file is unknown and will just be copied.",
1487 currentOffset += level0Element->
totalSize();
1488 readOffset += level0Element->
totalSize();
1492 if (!rewriteRequired) {
1494 if ((rewriteRequired = (newPadding >
fileInfo().maxPadding() || newPadding <
fileInfo().minPadding()))) {
1496 goto calculateSegmentData;
1506 }
catch (
const std::ios_base::failure &failure) {
1507 diag.emplace_back(
DiagLevel::Critical, argsToString(
"An IO error occurred when parsing the original file: ", failure.what()), context);
1518 NativeFileStream backupStream;
1519 BinaryWriter outputWriter(&outputStream);
1522 if (rewriteRequired) {
1523 if (
fileInfo().saveFilePath().empty()) {
1528 outputStream.open(originalPath, ios_base::out | ios_base::binary | ios_base::trunc);
1529 }
catch (
const std::ios_base::failure &failure) {
1531 DiagLevel::Critical, argsToString(
"Creation of temporary file (to rewrite the original file) failed: ", failure.what()), context);
1537 backupStream.exceptions(ios_base::badbit | ios_base::failbit);
1541 }
catch (
const std::ios_base::failure &failure) {
1542 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Opening streams to write output file failed: ", failure.what()), context);
1554 for (
auto &maker : attachmentMaker) {
1555 maker.bufferCurrentAttachments(diag);
1561 outputStream.open(
fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary);
1562 }
catch (
const std::ios_base::failure &failure) {
1563 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Opening the file with write permissions failed: ", failure.what()), context);
1574 outputStream.write(buff, sizeLength);
1584 for (level0Element =
firstElement(), segmentIndex = 0, currentPosition = 0; level0Element; level0Element = level0Element->
nextSibling()) {
1587 switch (level0Element->
id()) {
1602 progress.
updateStep(
"Writing segment header ...");
1605 outputStream.write(buff, sizeLength);
1606 segment.
newDataOffset = offset =
static_cast<std::uint64_t
>(outputStream.tellp());
1612 *(buff + 1) =
static_cast<char>(0x84);
1614 crc32Offsets.emplace_back(outputStream.tellp(), segment.
totalDataSize);
1615 outputStream.write(buff, 6);
1627 outputStream.write(buff, sizeLength);
1629 for (level2Element = level1Element->
firstChild(); level2Element; level2Element = level2Element->
nextSibling()) {
1630 switch (level2Element->
id()) {
1643 if (segmentIndex <
m_titles.size()) {
1644 const auto &title =
m_titles[segmentIndex];
1645 if (!title.empty()) {
1655 if (trackHeaderElementsSize) {
1658 outputStream.write(buff, sizeLength);
1659 for (
auto &maker : trackHeaderMaker) {
1660 maker.make(outputStream);
1676 outputStream.write(buff, sizeLength);
1677 for (
auto &maker : tagMaker) {
1678 maker.make(outputStream);
1682 if (attachmentsSize) {
1685 outputStream.write(buff, sizeLength);
1686 for (
auto &maker : attachmentMaker) {
1687 maker.make(outputStream, diag);
1700 std::uint64_t voidLength;
1703 *buff =
static_cast<char>(voidLength = segment.
newPadding - 2) |
static_cast<char>(0x80);
1706 BE::getBytes(
static_cast<std::uint64_t
>((voidLength = segment.
newPadding - 9) | 0x100000000000000), buff);
1710 outputStream.write(buff, sizeLength);
1712 for (; voidLength; --voidLength) {
1713 outputStream.put(0);
1719 if (rewriteRequired) {
1722 static_cast<std::uint8_t
>((
static_cast<std::uint64_t
>(outputStream.tellp()) - offset) * 100 / segment.
totalDataSize));
1724 auto clusterSizesIterator = segment.
clusterSizes.cbegin();
1725 unsigned int index = 0;
1728 clusterSize = currentPosition + (
static_cast<std::uint64_t
>(outputStream.tellp()) - offset);
1732 outputStream.write(buff, sizeLength);
1734 for (level2Element = level1Element->
firstChild(); level2Element; level2Element = level2Element->
nextSibling()) {
1735 switch (level2Element->
id()) {
1743 level2Element->
copyEntirely(outputStream, diag,
nullptr);
1748 if (index % 50 == 0) {
1750 static_cast<std::uint8_t
>((
static_cast<std::uint64_t
>(outputStream.tellp()) - offset) * 100 / segment.
totalDataSize));
1756 static_cast<std::uint8_t
>((
static_cast<std::uint64_t
>(outputStream.tellp()) - offset) * 100 / segment.
totalDataSize));
1757 for (; level1Element; level1Element = level1Element->
nextSibling()) {
1758 for (level2Element = level1Element->
firstChild(); level2Element; level2Element = level2Element->
nextSibling()) {
1759 switch (level2Element->
id()) {
1763 level2Element->
dataSize() > 8 ? 8 :
static_cast<std::uint8_t
>(level2Element->
dataSize()));
1765 if (level2Element->
dataSize() < sizeLength) {
1767 outputStream.seekp(
static_cast<streamoff
>(level2Element->
startOffset()));
1771 outputStream.seekp(
static_cast<streamoff
>(level2Element->
dataOffset()));
1772 outputStream.write(buff, sizeLength);
1783 progress.
updateStep(
"Writing segment tail ...");
1795 outputStream.write(buff, sizeLength);
1796 for (
auto &maker : tagMaker) {
1797 maker.make(outputStream);
1801 if (attachmentsSize) {
1804 outputStream.write(buff, sizeLength);
1805 for (
auto &maker : attachmentMaker) {
1806 maker.make(outputStream, diag);
1821 level0Element->
copyEntirely(outputStream, diag,
nullptr);
1822 currentPosition += level0Element->
totalSize();
1827 progress.
updateStep(
"Reparsing output file ...");
1828 if (rewriteRequired) {
1833 if (!
fileInfo().saveFilePath().empty()) {
1839 outputStream.close();
1840 outputStream.open(
fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary);
1843 const auto newSize =
static_cast<std::uint64_t
>(outputStream.tellp());
1847 outputStream.close();
1849 auto ec = std::error_code();
1850 std::filesystem::resize_file(makeNativePath(
fileInfo().path()), newSize, ec);
1854 diag.emplace_back(
DiagLevel::Critical,
"Unable to truncate the file: " + ec.message(), context);
1857 outputStream.open(
fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary);
1869 diag.emplace_back(
DiagLevel::Critical,
"Unable to reparse the header of the new file.", context);
1874 if (!crc32Offsets.empty()) {
1875 progress.
updateStep(
"Updating CRC-32 checksums ...");
1876 for (
const auto &crc32Offset : crc32Offsets) {
1877 outputStream.seekg(
static_cast<streamoff
>(get<0>(crc32Offset) + 6));
1878 outputStream.seekp(
static_cast<streamoff
>(get<0>(crc32Offset) + 2));
1879 writer().writeUInt32LE(
reader().readCrc32(get<1>(crc32Offset) - 6));
1884 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
const std::vector< std::string > & writingApplications() const
Returns the writing applications specified as meta-data.
const std::vector< std::string > & muxingApplications() const
Returns the muxing applications specified as meta-data.
bool isHeaderParsed() const
Returns an indication whether the header has been parsed yet.
void setStream(std::iostream &stream)
Sets the related stream.
std::uint32_t timeScale() const
Returns the time scale of the file if known; otherwise returns 0.
CppUtilities::BinaryWriter & writer()
Returns the related BinaryWriter.
void parseHeader(Diagnostics &diag, AbortableProgressFeedback &progress)
Parses the header if not parsed yet.
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.
Implementation of GenericContainer<MediaFileInfo, MatroskaTag, MatroskaTrack, EbmlElement>.
void internalParseChapters(Diagnostics &diag, AbortableProgressFeedback &progress) override
Internally called to parse the chapters.
void internalParseHeader(Diagnostics &diag, AbortableProgressFeedback &progress) override
Internally called to parse the header.
std::size_t chapterCount() const override
Returns the number of chapters the container holds.
void internalParseTracks(Diagnostics &diag, AbortableProgressFeedback &progress) override
Internally called to parse the tracks.
ElementPosition determineTagPosition(Diagnostics &diag) const override
Determines the position of the tags inside the file.
MatroskaAttachment * createAttachment() override
Creates and returns a new attachment.
void reset() override
Discards all parsing results.
~MatroskaContainer() 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.
@ PreserveWritingApplication
@ NormalizeKnownTagFieldIds
@ PreserveMuxingApplication
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)