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>
28 using namespace std::placeholders;
38 std::uint64_t MatroskaContainer::m_maxFullParseSize = 0x3200000;
43 MatroskaContainer::MatroskaContainer(
MediaFileInfo &fileInfo, std::uint64_t startOffset)
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) {
311 inline 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);
607 void 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);
651 void MatroskaContainer::readTrackStatisticsFromTags(Diagnostics &diag)
663 CPP_UTILITIES_UNUSED(progress)
665 static const string context(
"parsing tags of Matroska container");
668 element->parse(diag);
670 subElement->parse(diag);
671 switch (subElement->id()) {
673 m_tags.emplace_back(make_unique<MatroskaTag>());
675 m_tags.back()->parse(*subElement, diag);
686 diag.emplace_back(
DiagLevel::Warning,
"\"Tags\"-element contains unknown child. It will be ignored.", context);
691 readTrackStatisticsFromTags(diag);
695 readTrackStatisticsFromTags(diag);
700 static const string context(
"parsing tracks of Matroska container");
703 element->parse(diag);
705 subElement->parse(diag);
706 switch (subElement->id()) {
708 m_tracks.emplace_back(make_unique<MatroskaTrack>(*subElement));
710 m_tracks.back()->parseHeader(diag, progress);
722 "\"Tracks\"-element contains unknown child element \"" % subElement->idToString() +
"\". It will be ignored.", context);
727 readTrackStatisticsFromTags(diag);
731 readTrackStatisticsFromTags(diag);
736 static const string context(
"parsing editions/chapters of Matroska container");
739 element->parse(diag);
741 subElement->parse(diag);
742 switch (subElement->id()) {
744 m_editionEntries.emplace_back(make_unique<MatroskaEditionEntry>(subElement));
746 m_editionEntries.back()->parseNested(diag, progress);
748 m_editionEntries.pop_back();
750 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Unable to parse edition entry ", m_editionEntries.size(),
'.'), context);
758 "\"Chapters\"-element contains unknown child element \"" % subElement->idToString() +
"\". It will be ignored.", context);
770 CPP_UTILITIES_UNUSED(progress)
772 static const string context(
"parsing attachments of Matroska container");
773 for (
EbmlElement *element : m_attachmentsElements) {
775 element->parse(diag);
777 subElement->parse(diag);
778 switch (subElement->id()) {
780 m_attachments.emplace_back(make_unique<MatroskaAttachment>());
782 m_attachments.back()->parse(subElement, diag);
784 m_attachments.pop_back();
786 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Unable to parse attached file ", m_attachments.size(),
'.'), context);
794 "\"Attachments\"-element contains unknown child element \"" % subElement->idToString() +
"\". It will be ignored.", context);
854 static const string context(
"making Matroska container");
855 progress.
updateStep(
"Calculating element sizes ...");
862 switch (
fileInfo().attachmentsParsingStatus()) {
867 diag.emplace_back(
DiagLevel::Critical,
"Attachments have to be parsed without critical errors before changes can be applied.", context);
873 if (!level0Element) {
880 vector<MatroskaTagMaker> tagMaker;
881 tagMaker.reserve(
tags().size());
882 std::uint64_t tagElementsSize = 0;
883 std::uint64_t tagsSize;
884 vector<MatroskaAttachmentMaker> attachmentMaker;
885 attachmentMaker.reserve(m_attachments.size());
886 std::uint64_t attachedFileElementsSize = 0;
887 std::uint64_t attachmentsSize;
888 vector<MatroskaTrackHeaderMaker> trackHeaderMaker;
889 trackHeaderMaker.reserve(
tracks().size());
890 std::uint64_t trackHeaderElementsSize = 0;
891 std::uint64_t trackHeaderSize;
895 unsigned int segmentIndex = 0;
897 vector<SegmentData> segmentData;
899 std::uint64_t offset;
901 std::uint64_t totalOffset;
903 std::uint64_t currentPosition = 0;
905 vector<tuple<std::uint64_t, std::uint64_t>> crc32Offsets;
907 std::uint8_t sizeLength;
909 std::uint64_t clusterSize, clusterReadSize, clusterReadOffset;
921 unsigned int lastSegmentIndex = numeric_limits<unsigned int>::max();
923 std::uint64_t newPadding;
929 std::uint64_t ebmlHeaderDataSize = 2 * 7;
931 for (
auto headerValue :
942 constexpr std::string_view muxingAppName = APP_NAME
" v" APP_VERSION;
943 constexpr std::uint64_t muxingAppElementTotalSize = 2 + 1 + muxingAppName.size();
946 const std::uint64_t writingAppElementDataSize
948 const std::uint64_t writingAppElementTotalSize = 2 + 1 + writingAppElementDataSize;
955 if (tagMaker.back().requiredSize() > 3) {
957 tagElementsSize += tagMaker.back().requiredSize();
969 if (attachmentMaker.back().requiredSize() > 3) {
971 attachedFileElementsSize += attachmentMaker.back().requiredSize();
984 if (trackHeaderMaker.back().requiredSize() > 3) {
986 trackHeaderElementsSize += trackHeaderMaker.back().requiredSize();
998 for (
bool firstClusterFound =
false, firstTagFound =
false; level0Element; level0Element = level0Element->
nextSibling()) {
999 level0Element->
parse(diag);
1000 switch (level0Element->
id()) {
1003 for (level1Element = level0Element->
firstChild(); level1Element && !firstClusterFound && !firstTagFound;
1005 level1Element->
parse(diag);
1006 switch (level1Element->
id()) {
1009 firstTagFound =
true;
1012 firstClusterFound =
true;
1015 if (firstTagFound) {
1017 }
else if (firstClusterFound) {
1024 segmentData.resize(lastSegmentIndex + 1);
1035 "Unable to parse content in top-level element at " % numberToString(level0Element->
startOffset()) +
" of original file.", context);
1039 progress.
nextStepOrStop(
"Calculating offsets of elements before cluster ...");
1040 calculateSegmentData:
1043 std::uint64_t currentOffset = ebmlHeaderSize;
1045 std::uint64_t readOffset = 0;
1050 if (rewriteRequired) {
1061 for (level0Element =
firstElement(), currentPosition = newPadding = segmentIndex = 0; level0Element;
1063 switch (level0Element->
id()) {
1092 newCuesPos = currentCuesPos;
1105 calculateSegmentSize:
1118 goto calculateSegmentSize;
1122 segment.
infoDataSize = muxingAppElementTotalSize + writingAppElementTotalSize;
1124 if (segmentIndex <
m_titles.size()) {
1126 if (!
title.empty()) {
1131 for (level2Element = level1Element->
firstChild(); level2Element; level2Element = level2Element->
nextSibling()) {
1132 level2Element->
parse(diag);
1133 switch (level2Element->
id()) {
1151 if (trackHeaderSize) {
1154 goto calculateSegmentSize;
1166 goto calculateSegmentSize;
1182 goto calculateSegmentSize;
1189 if (attachmentsSize) {
1192 goto calculateSegmentSize;
1206 goto calculateSegmentSize;
1209 progress.
updateStep(
"Calculating cluster offsets and index size ...");
1214 progress.
updateStep(
"Calculating cluster offsets ...");
1218 if (!rewriteRequired) {
1225 if (totalOffset <= segment.firstClusterElement->
startOffset()) {
1234 diag.emplace_back(
DiagLevel::Critical,
"Header size of \"Segment\"-element from original file is invalid.", context);
1239 nonRewriteCalculations:
1244 goto calculateSegmentSize;
1247 bool cuesInvalidated =
false;
1255 cuesInvalidated =
true;
1260 if (index % 50 == 0) {
1264 if (cuesInvalidated) {
1266 goto addCuesElementSize;
1271 progress.
updateStep(
"Calculating offsets of elements after cluster ...");
1275 goto calculateSegmentSize;
1287 goto calculateSegmentSize;
1294 if (attachmentsSize) {
1297 goto calculateSegmentSize;
1311 goto nonRewriteCalculations;
1314 totalOffset = currentOffset + 4 + sizeLength + offset;
1319 if (totalOffset <= segment.firstClusterElement->
startOffset()) {
1325 rewriteRequired =
true;
1328 rewriteRequired =
true;
1331 rewriteRequired =
true;
1334 diag.emplace_back(
DiagLevel::Warning, argsToString(
"There are no clusters in segment ", segmentIndex,
"."), context);
1337 if (rewriteRequired) {
1343 rewriteRequired =
false;
1349 rewriteRequired =
false;
1352 goto calculateSegmentData;
1365 bool cuesInvalidated =
false;
1371 cuesInvalidated =
true;
1374 goto calculateSegmentSize;
1377 clusterSize = clusterReadSize = 0;
1378 for (level2Element = level1Element->
firstChild(); level2Element; level2Element = level2Element->
nextSibling()) {
1379 level2Element->
parse(diag);
1383 cuesInvalidated =
true;
1385 switch (level2Element->
id()) {
1393 clusterSize += level2Element->
totalSize();
1395 clusterReadSize += level2Element->
totalSize();
1404 if ((index % 50 == 0) &&
fileInfo().size()) {
1410 if (cuesInvalidated) {
1413 goto addCuesElementSize;
1417 progress.
updateStep(
"Calculating offsets of elements after cluster ...");
1421 goto calculateSegmentSize;
1436 goto calculateSegmentSize;
1443 if (attachmentsSize) {
1446 goto calculateSegmentSize;
1464 readOffset += level0Element->
totalSize();
1471 "The top-level element \"" % level0Element->
idToString() +
"\" of the original file is unknown and will just be copied.",
1473 currentOffset += level0Element->
totalSize();
1474 readOffset += level0Element->
totalSize();
1478 if (!rewriteRequired) {
1480 if ((rewriteRequired = (newPadding >
fileInfo().maxPadding() || newPadding <
fileInfo().minPadding()))) {
1482 goto calculateSegmentData;
1492 }
catch (
const std::ios_base::failure &failure) {
1493 diag.emplace_back(
DiagLevel::Critical, argsToString(
"An IO error occurred when parsing the original file: ", failure.what()), context);
1504 NativeFileStream backupStream;
1505 BinaryWriter outputWriter(&outputStream);
1508 if (rewriteRequired) {
1509 if (
fileInfo().saveFilePath().empty()) {
1515 }
catch (
const std::ios_base::failure &failure) {
1517 DiagLevel::Critical, argsToString(
"Creation of temporary file (to rewrite the original file) failed: ", failure.what()), context);
1523 backupStream.exceptions(ios_base::badbit | ios_base::failbit);
1527 }
catch (
const std::ios_base::failure &failure) {
1528 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Opening streams to write output file failed: ", failure.what()), context);
1540 for (
auto &maker : attachmentMaker) {
1541 maker.bufferCurrentAttachments(diag);
1547 outputStream.open(
fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary);
1548 }
catch (
const std::ios_base::failure &failure) {
1549 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Opening the file with write permissions failed: ", failure.what()), context);
1560 outputStream.write(buff, sizeLength);
1570 for (level0Element =
firstElement(), segmentIndex = 0, currentPosition = 0; level0Element; level0Element = level0Element->
nextSibling()) {
1573 switch (level0Element->
id()) {
1588 progress.
updateStep(
"Writing segment header ...");
1591 outputStream.write(buff, sizeLength);
1592 segment.
newDataOffset = offset =
static_cast<std::uint64_t
>(outputStream.tellp());
1598 *(buff + 1) =
static_cast<char>(0x84);
1600 crc32Offsets.emplace_back(outputStream.tellp(), segment.
totalDataSize);
1601 outputStream.write(buff, 6);
1613 outputStream.write(buff, sizeLength);
1615 for (level2Element = level1Element->
firstChild(); level2Element; level2Element = level2Element->
nextSibling()) {
1616 switch (level2Element->
id()) {
1629 if (segmentIndex <
m_titles.size()) {
1631 if (!
title.empty()) {
1638 fileInfo().writingApplication().empty() ? muxingAppName :
fileInfo().writingApplication());
1642 if (trackHeaderElementsSize) {
1645 outputStream.write(buff, sizeLength);
1646 for (
auto &maker : trackHeaderMaker) {
1647 maker.make(outputStream);
1663 outputStream.write(buff, sizeLength);
1664 for (
auto &maker : tagMaker) {
1665 maker.make(outputStream);
1669 if (attachmentsSize) {
1672 outputStream.write(buff, sizeLength);
1673 for (
auto &maker : attachmentMaker) {
1674 maker.make(outputStream, diag);
1687 std::uint64_t voidLength;
1690 *buff =
static_cast<char>(voidLength = segment.
newPadding - 2) |
static_cast<char>(0x80);
1693 BE::getBytes(
static_cast<std::uint64_t
>((voidLength = segment.
newPadding - 9) | 0x100000000000000), buff);
1697 outputStream.write(buff, sizeLength);
1699 for (; voidLength; --voidLength) {
1700 outputStream.put(0);
1706 if (rewriteRequired) {
1709 static_cast<std::uint8_t
>((
static_cast<std::uint64_t
>(outputStream.tellp()) - offset) * 100 / segment.
totalDataSize));
1711 auto clusterSizesIterator = segment.
clusterSizes.cbegin();
1712 unsigned int index = 0;
1715 clusterSize = currentPosition + (
static_cast<std::uint64_t
>(outputStream.tellp()) - offset);
1719 outputStream.write(buff, sizeLength);
1721 for (level2Element = level1Element->
firstChild(); level2Element; level2Element = level2Element->
nextSibling()) {
1722 switch (level2Element->
id()) {
1730 level2Element->
copyEntirely(outputStream, diag,
nullptr);
1735 if (index % 50 == 0) {
1737 static_cast<std::uint8_t
>((
static_cast<std::uint64_t
>(outputStream.tellp()) - offset) * 100 / segment.
totalDataSize));
1743 static_cast<std::uint8_t
>((
static_cast<std::uint64_t
>(outputStream.tellp()) - offset) * 100 / segment.
totalDataSize));
1744 for (; level1Element; level1Element = level1Element->
nextSibling()) {
1745 for (level2Element = level1Element->
firstChild(); level2Element; level2Element = level2Element->
nextSibling()) {
1746 switch (level2Element->
id()) {
1750 level2Element->
dataSize() > 8 ? 8 :
static_cast<std::uint8_t
>(level2Element->
dataSize()));
1752 if (level2Element->
dataSize() < sizeLength) {
1754 outputStream.seekp(
static_cast<streamoff
>(level2Element->
startOffset()));
1758 outputStream.seekp(
static_cast<streamoff
>(level2Element->
dataOffset()));
1759 outputStream.write(buff, sizeLength);
1770 progress.
updateStep(
"Writing segment tail ...");
1782 outputStream.write(buff, sizeLength);
1783 for (
auto &maker : tagMaker) {
1784 maker.make(outputStream);
1788 if (attachmentsSize) {
1791 outputStream.write(buff, sizeLength);
1792 for (
auto &maker : attachmentMaker) {
1793 maker.make(outputStream, diag);
1808 level0Element->
copyEntirely(outputStream, diag,
nullptr);
1809 currentPosition += level0Element->
totalSize();
1814 progress.
updateStep(
"Reparsing output file ...");
1815 if (rewriteRequired) {
1820 if (!
fileInfo().saveFilePath().empty()) {
1826 outputStream.close();
1827 outputStream.open(
fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary);
1830 const auto newSize =
static_cast<std::uint64_t
>(outputStream.tellp());
1834 outputStream.close();
1836 if (truncate(
fileInfo().path().c_str(),
static_cast<iostream::off_type
>(newSize)) == 0) {
1842 outputStream.open(
fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary);
1854 diag.emplace_back(
DiagLevel::Critical,
"Unable to reparse the header of the new file.", context);
1859 if (!crc32Offsets.empty()) {
1860 progress.
updateStep(
"Updating CRC-32 checksums ...");
1861 for (
const auto &crc32Offset : crc32Offsets) {
1862 outputStream.seekg(
static_cast<streamoff
>(get<0>(crc32Offset) + 6));
1863 outputStream.seekp(
static_cast<streamoff
>(get<0>(crc32Offset) + 2));
1864 writer().writeUInt32LE(
reader().readCrc32(get<1>(crc32Offset) - 6));
1869 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.
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
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.
void readStatisticsFromTags(const std::vector< std::unique_ptr< MatroskaTag >> &tags, Diagnostics &diag)
Reads track-specific statistics from the specified tags.
MatroskaTrackHeaderMaker prepareMakingHeader(Diagnostics &diag) const
Prepares making header.
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 createBackupFile(const std::string &backupDir, const std::string &originalPath, std::string &backupPath, CppUtilities::NativeFileStream &originalStream, CppUtilities::NativeFileStream &backupStream)
TAG_PARSER_EXPORT void handleFailureAfterFileModified(MediaFileInfo &mediaFileInfo, const std::string &backupPath, CppUtilities::NativeFileStream &outputStream, CppUtilities::NativeFileStream &backupStream, Diagnostics &diag, const std::string &context="making file")
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.
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)