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 auto context = std::string(
"validating Matroska file index (cues)");
86 auto cuesElementsFound =
false;
88 auto ids = std::unordered_set<EbmlElement::IdentifierType>();
89 auto cueTimeFound =
false, cueTrackPositionsFound =
false;
90 auto clusterElement = std::unique_ptr<EbmlElement>();
91 auto pos = std::uint64_t(), prevClusterSize = std::uint64_t(), currentOffset = std::uint64_t();
95 segmentElement->parse(diag);
98 segmentChildElement = segmentChildElement->
nextSibling()) {
100 segmentChildElement->parse(diag);
101 switch (segmentChildElement->id()) {
106 cuesElementsFound =
true;
109 cuePointElement = cuePointElement->
nextSibling()) {
111 cuePointElement->parse(diag);
112 cueTimeFound = cueTrackPositionsFound =
false;
113 switch (cuePointElement->id()) {
120 cuePointChildElement = cuePointChildElement->
nextSibling()) {
121 cuePointChildElement->parse(diag);
122 switch (cuePointChildElement->id()) {
127 DiagLevel::Warning,
"\"CuePoint\"-element contains multiple \"CueTime\" elements.", context);
133 cueTrackPositionsFound =
true;
135 clusterElement.reset();
138 subElement->parse(diag);
139 switch (subElement->id()) {
147 if (ids.count(subElement->id())) {
149 "\"CueTrackPositions\"-element contains multiple \"" % subElement->idToString() +
"\" elements.",
152 ids.insert(subElement->id());
161 "\"CueTrackPositions\"-element contains unknown element \"" % subElement->idToString() +
"\".",
164 switch (subElement->id()) {
171 clusterElement = make_unique<EbmlElement>(
172 *
this, segmentElement->dataOffset() + subElement->readUInteger() - currentOffset);
174 clusterElement->parse(diag);
177 "\"CueClusterPosition\" element at " % numberToString(subElement->startOffset())
178 +
" does not point to \"Cluster\"-element (points to "
179 + numberToString(clusterElement->startOffset()) +
").",
187 pos = subElement->readUInteger();
203 "\"CueTrackPositions\"-element does not contain mandatory element \"CueTrack\".", context);
205 if (!clusterElement) {
207 "\"CueTrackPositions\"-element does not contain mandatory element \"CueClusterPosition\".", context);
210 EbmlElement referenceElement(*
this, clusterElement->dataOffset() + pos);
212 referenceElement.
parse(diag);
213 switch (referenceElement.
id()) {
220 "\"CueRelativePosition\" element does not point to \"Block\"-, \"BlockGroup\", or "
221 "\"SimpleBlock\"-element (points to "
235 "\"CuePoint\"-element contains unknown element \"" % cuePointElement->idToString() +
"\".", context);
241 DiagLevel::Warning,
"\"CuePoint\"-element does not contain mandatory element \"CueTime\".", context);
243 if (!cueTrackPositionsFound) {
245 DiagLevel::Warning,
"\"CuePoint\"-element does not contain mandatory element \"CueClusterPosition\".", context);
255 clusterElementChild = clusterElementChild->
nextSibling()) {
256 clusterElementChild->parse(diag);
257 switch (clusterElementChild->id()) {
263 if ((pos = clusterElementChild->readUInteger()) > 0
264 && (segmentChildElement->startOffset() - segmentElement->dataOffset() + currentOffset) != pos) {
266 argsToString(
"\"Position\"-element at ", clusterElementChild->startOffset(),
" points to ", pos,
267 " which is not the offset of the containing \"Cluster\"-element."),
273 if ((pos = clusterElementChild->readUInteger()) != prevClusterSize) {
275 argsToString(
"\"PrevSize\"-element at ", clusterElementChild->startOffset(),
" should be ", prevClusterSize,
276 " but is ", pos,
"."),
283 prevClusterSize = segmentChildElement->totalSize();
288 currentOffset += segmentElement->totalSize();
292 if (!cuesElementsFound) {
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) {
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);
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);
960 ? std::string_view(writingApps.front())
961 : std::string_view(
fileInfo().writingApplication().empty() ? muxingAppName : std::string_view(
fileInfo().writingApplication()));
962 const auto writingAppElementTotalSize
970 if (maker.requiredSize() > 3) {
972 tagElementsSize += maker.requiredSize();
984 if (maker.requiredSize() > 3) {
986 attachedFileElementsSize += maker.requiredSize();
999 if (maker.requiredSize() > 3) {
1001 trackHeaderElementsSize += maker.requiredSize();
1013 for (
bool firstClusterFound =
false, firstTagFound =
false; level0Element; level0Element = level0Element->
nextSibling()) {
1014 level0Element->
parse(diag);
1015 switch (level0Element->
id()) {
1018 for (level1Element = level0Element->
firstChild(); level1Element && !firstClusterFound && !firstTagFound;
1020 level1Element->
parse(diag);
1021 switch (level1Element->
id()) {
1024 firstTagFound =
true;
1027 firstClusterFound =
true;
1030 if (firstTagFound) {
1032 }
else if (firstClusterFound) {
1039 segmentData.resize(lastSegmentIndex + 1);
1050 "Unable to parse content in top-level element at " % numberToString(level0Element->
startOffset()) +
" of original file.", context);
1054 progress.
nextStepOrStop(
"Calculating offsets of elements before cluster ...");
1055 calculateSegmentData:
1058 std::uint64_t currentOffset = ebmlHeaderSize;
1060 std::uint64_t readOffset = 0;
1065 if (rewriteRequired) {
1076 for (level0Element =
firstElement(), currentPosition = newPadding = segmentIndex = 0; level0Element;
1078 switch (level0Element->
id()) {
1107 newCuesPos = currentCuesPos;
1120 calculateSegmentSize:
1133 goto calculateSegmentSize;
1137 segment.
infoDataSize = muxingAppElementTotalSize + writingAppElementTotalSize;
1139 if (segmentIndex <
m_titles.size()) {
1140 const auto &title =
m_titles[segmentIndex];
1141 if (!title.empty()) {
1146 for (level2Element = level1Element->
firstChild(); level2Element; level2Element = level2Element->
nextSibling()) {
1147 level2Element->
parse(diag);
1148 switch (level2Element->
id()) {
1166 if (trackHeaderSize) {
1169 goto calculateSegmentSize;
1181 goto calculateSegmentSize;
1197 goto calculateSegmentSize;
1204 if (attachmentsSize) {
1207 goto calculateSegmentSize;
1221 goto calculateSegmentSize;
1224 progress.
updateStep(
"Calculating cluster offsets and index size ...");
1229 progress.
updateStep(
"Calculating cluster offsets ...");
1233 if (!rewriteRequired) {
1240 if (totalOffset <= segment.firstClusterElement->
startOffset()) {
1249 diag.emplace_back(
DiagLevel::Critical,
"Header size of \"Segment\"-element from original file is invalid.", context);
1254 nonRewriteCalculations:
1259 goto calculateSegmentSize;
1262 bool cuesInvalidated =
false;
1270 cuesInvalidated =
true;
1275 if (index % 50 == 0) {
1279 if (cuesInvalidated) {
1281 goto addCuesElementSize;
1286 progress.
updateStep(
"Calculating offsets of elements after cluster ...");
1290 goto calculateSegmentSize;
1302 goto calculateSegmentSize;
1309 if (attachmentsSize) {
1312 goto calculateSegmentSize;
1326 goto nonRewriteCalculations;
1329 totalOffset = currentOffset + 4 + sizeLength + offset;
1334 if (totalOffset <= segment.firstClusterElement->
startOffset()) {
1340 rewriteRequired =
true;
1343 rewriteRequired =
true;
1346 rewriteRequired =
true;
1349 diag.emplace_back(
DiagLevel::Warning, argsToString(
"There are no clusters in segment ", segmentIndex,
"."), context);
1352 if (rewriteRequired) {
1358 rewriteRequired =
false;
1360 && (!
fileInfo().forceIndexPosition()
1364 rewriteRequired =
false;
1367 goto calculateSegmentData;
1380 bool cuesInvalidated =
false;
1386 cuesInvalidated =
true;
1389 goto calculateSegmentSize;
1392 clusterSize = clusterReadSize = 0;
1393 for (level2Element = level1Element->
firstChild(); level2Element; level2Element = level2Element->
nextSibling()) {
1394 level2Element->
parse(diag);
1398 cuesInvalidated =
true;
1400 switch (level2Element->
id()) {
1408 clusterSize += level2Element->
totalSize();
1410 clusterReadSize += level2Element->
totalSize();
1419 if ((index % 50 == 0) &&
fileInfo().size()) {
1425 if (cuesInvalidated) {
1428 goto addCuesElementSize;
1432 progress.
updateStep(
"Calculating offsets of elements after cluster ...");
1436 goto calculateSegmentSize;
1451 goto calculateSegmentSize;
1458 if (attachmentsSize) {
1461 goto calculateSegmentSize;
1479 readOffset += level0Element->
totalSize();
1486 "The top-level element \"" % level0Element->
idToString() +
"\" of the original file is unknown and will just be copied.",
1488 currentOffset += level0Element->
totalSize();
1489 readOffset += level0Element->
totalSize();
1493 if (!rewriteRequired) {
1495 if ((rewriteRequired = (newPadding >
fileInfo().maxPadding() || newPadding <
fileInfo().minPadding()))) {
1497 goto calculateSegmentData;
1507 }
catch (
const std::ios_base::failure &failure) {
1508 diag.emplace_back(
DiagLevel::Critical, argsToString(
"An IO error occurred when parsing the original file: ", failure.what()), context);
1519 NativeFileStream backupStream;
1520 BinaryWriter outputWriter(&outputStream);
1523 if (rewriteRequired) {
1524 if (
fileInfo().saveFilePath().empty()) {
1529 outputStream.open(originalPath, ios_base::out | ios_base::binary | ios_base::trunc);
1530 }
catch (
const std::ios_base::failure &failure) {
1532 DiagLevel::Critical, argsToString(
"Creation of temporary file (to rewrite the original file) failed: ", failure.what()), context);
1538 backupStream.exceptions(ios_base::badbit | ios_base::failbit);
1542 }
catch (
const std::ios_base::failure &failure) {
1543 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Opening streams to write output file failed: ", failure.what()), context);
1555 for (
auto &maker : attachmentMaker) {
1556 maker.bufferCurrentAttachments(diag);
1562 outputStream.open(
fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary);
1563 }
catch (
const std::ios_base::failure &failure) {
1564 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Opening the file with write permissions failed: ", failure.what()), context);
1575 outputStream.write(buff, sizeLength);
1585 for (level0Element =
firstElement(), segmentIndex = 0, currentPosition = 0; level0Element; level0Element = level0Element->
nextSibling()) {
1588 switch (level0Element->
id()) {
1603 progress.
updateStep(
"Writing segment header ...");
1606 outputStream.write(buff, sizeLength);
1607 segment.
newDataOffset = offset =
static_cast<std::uint64_t
>(outputStream.tellp());
1613 *(buff + 1) =
static_cast<char>(0x84);
1615 crc32Offsets.emplace_back(outputStream.tellp(), segment.
totalDataSize);
1616 outputStream.write(buff, 6);
1628 outputStream.write(buff, sizeLength);
1630 for (level2Element = level1Element->
firstChild(); level2Element; level2Element = level2Element->
nextSibling()) {
1631 switch (level2Element->
id()) {
1644 if (segmentIndex <
m_titles.size()) {
1645 const auto &title =
m_titles[segmentIndex];
1646 if (!title.empty()) {
1656 if (trackHeaderElementsSize) {
1659 outputStream.write(buff, sizeLength);
1660 for (
auto &maker : trackHeaderMaker) {
1661 maker.make(outputStream);
1677 outputStream.write(buff, sizeLength);
1678 for (
auto &maker : tagMaker) {
1679 maker.make(outputStream);
1683 if (attachmentsSize) {
1686 outputStream.write(buff, sizeLength);
1687 for (
auto &maker : attachmentMaker) {
1688 maker.make(outputStream, diag);
1701 std::uint64_t voidLength;
1704 *buff =
static_cast<char>(voidLength = segment.
newPadding - 2) |
static_cast<char>(0x80);
1707 BE::getBytes(
static_cast<std::uint64_t
>((voidLength = segment.
newPadding - 9) | 0x100000000000000), buff);
1711 outputStream.write(buff, sizeLength);
1718 if (rewriteRequired) {
1721 static_cast<std::uint8_t
>((
static_cast<std::uint64_t
>(outputStream.tellp()) - offset) * 100 / segment.
totalDataSize));
1723 auto clusterSizesIterator = segment.
clusterSizes.cbegin();
1724 unsigned int index = 0;
1727 clusterSize = currentPosition + (
static_cast<std::uint64_t
>(outputStream.tellp()) - offset);
1731 outputStream.write(buff, sizeLength);
1733 for (level2Element = level1Element->
firstChild(); level2Element; level2Element = level2Element->
nextSibling()) {
1734 switch (level2Element->
id()) {
1742 level2Element->
copyEntirely(outputStream, diag,
nullptr);
1747 if (index % 50 == 0) {
1749 static_cast<std::uint8_t
>((
static_cast<std::uint64_t
>(outputStream.tellp()) - offset) * 100 / segment.
totalDataSize));
1755 static_cast<std::uint8_t
>((
static_cast<std::uint64_t
>(outputStream.tellp()) - offset) * 100 / segment.
totalDataSize));
1756 for (; level1Element; level1Element = level1Element->
nextSibling()) {
1757 for (level2Element = level1Element->
firstChild(); level2Element; level2Element = level2Element->
nextSibling()) {
1758 switch (level2Element->
id()) {
1762 level2Element->
dataSize() > 8 ? 8 :
static_cast<std::uint8_t
>(level2Element->
dataSize()));
1764 if (level2Element->
dataSize() < sizeLength) {
1766 outputStream.seekp(
static_cast<streamoff
>(level2Element->
startOffset()));
1770 outputStream.seekp(
static_cast<streamoff
>(level2Element->
dataOffset()));
1771 outputStream.write(buff, sizeLength);
1782 progress.
updateStep(
"Writing segment tail ...");
1794 outputStream.write(buff, sizeLength);
1795 for (
auto &maker : tagMaker) {
1796 maker.make(outputStream);
1800 if (attachmentsSize) {
1803 outputStream.write(buff, sizeLength);
1804 for (
auto &maker : attachmentMaker) {
1805 maker.make(outputStream, diag);
1820 level0Element->
copyEntirely(outputStream, diag,
nullptr);
1821 currentPosition += level0Element->
totalSize();
1826 progress.
updateStep(
"Reparsing output file ...");
1827 if (rewriteRequired) {
1832 if (!
fileInfo().saveFilePath().empty()) {
1838 outputStream.close();
1839 outputStream.open(
fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary);
1842 const auto newSize =
static_cast<std::uint64_t
>(outputStream.tellp());
1846 outputStream.close();
1848 auto ec = std::error_code();
1849 std::filesystem::resize_file(makeNativePath(
fileInfo().path()), newSize, ec);
1853 diag.emplace_back(
DiagLevel::Critical,
"Unable to truncate the file: " + ec.message(), context);
1856 outputStream.open(
fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary);
1868 diag.emplace_back(
DiagLevel::Critical,
"Unable to reparse the header of the new file.", context);
1873 if (!crc32Offsets.empty()) {
1874 progress.
updateStep(
"Updating CRC-32 checksums ...");
1875 for (
const auto &crc32Offset : crc32Offsets) {
1876 outputStream.seekg(
static_cast<streamoff
>(get<0>(crc32Offset) + 6));
1877 outputStream.seekp(
static_cast<streamoff
>(get<0>(crc32Offset) + 2));
1878 writer().writeUInt32LE(
reader().readCrc32(get<1>(crc32Offset) - 6));
1883 outputStream.flush();
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.
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
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
std::unique_ptr< EbmlElement > m_firstElement
MediaFileInfo & fileInfo() const
std::vector< std::unique_ptr< EbmlElement > > m_additionalElements
EbmlElement * firstElement() const
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()
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()
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, needed because cluster elements are not necessarily copied as-is so they're size might...
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)