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
virtual AbstractTrack * track(std::size_t index)
Returns the track with the specified index.
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...
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
GenericContainer(MediaFileInfo &fileInfo, std::uint64_t startOffset)
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.
InvalidDataException() noexcept
Constructs a new exception.
Implementation of TagParser::AbstractAttachment for the Matroska container.
MatroskaAttachmentMaker prepareMaking(Diagnostics &diag)
Prepares making.
The MatroskaChapter class provides an implementation of AbstractAttachment for Matroska files.
void internalParseChapters(Diagnostics &diag, AbortableProgressFeedback &progress) override
Internally called to parse the chapters.
void internalParseHeader(Diagnostics &diag, AbortableProgressFeedback &progress) override
Internally called to parse the header.
std::size_t chapterCount() const override
Returns the number of chapters the container holds.
void internalParseTracks(Diagnostics &diag, AbortableProgressFeedback &progress) override
Internally called to parse the tracks.
ElementPosition determineTagPosition(Diagnostics &diag) const override
Determines the position of the tags inside the file.
MatroskaAttachment * createAttachment() override
Creates and returns a new attachment.
void reset() override
Discards all parsing results.
~MatroskaContainer() override
MatroskaContainer(MediaFileInfo &stream, std::uint64_t startOffset)
Constructs a new container for the specified fileInfo at the specified startOffset.
MatroskaAttachment * attachment(std::size_t index) override
Returns the attachment with the specified index.
void internalMakeFile(Diagnostics &diag, AbortableProgressFeedback &progress) override
Internally called to make the file.
MatroskaChapter * chapter(std::size_t index) override
Returns the chapter with the specified index.
ElementPosition determineIndexPosition(Diagnostics &diag) const override
Determines the position of the index.
void internalParseAttachments(Diagnostics &diag, AbortableProgressFeedback &progress) override
Internally called to parse the attachments.
ElementPosition determineElementPosition(std::uint64_t elementId, Diagnostics &diag) const
Determines the position of the element with the specified elementId.
void validateIndex(Diagnostics &diag, AbortableProgressFeedback &progress)
Validates the file index (cue entries).
void internalParseTags(Diagnostics &diag, AbortableProgressFeedback &progress) override
Internally called to parse the tags.
The MatroskaCuePositionUpdater class helps to rewrite the "Cues"-element with shifted positions.
std::uint64_t totalSize() const
Returns how many bytes will be written when calling the make() method.
bool updateOffsets(std::uint64_t originalOffset, std::uint64_t newOffset)
Sets the offset of the entries with the specified originalOffset to newOffset.
bool updateRelativeOffsets(std::uint64_t referenceOffset, std::uint64_t originalRelativeOffset, std::uint64_t newRelativeOffset)
Sets the relative offset of the entries with the specified originalRelativeOffset and the specified r...
void make(std::ostream &stream, Diagnostics &diag)
Writes the previously parsed "Cues"-element with updated positions to the specified stream.
void parse(EbmlElement *cuesElement, Diagnostics &diag)
Parses the specified cuesElement.
The MatroskaSeekInfo class helps parsing and making "SeekHead"-elements.
bool push(unsigned int index, EbmlElement::IdentifierType id, std::uint64_t offset)
Pushes the specified offset of an element with the specified id to the info.
void make(std::ostream &stream, Diagnostics &diag)
Writes a "SeekHead" element for the current instance to the specified stream.
std::uint64_t actualSize() const
Returns the number of bytes which will be written when calling the make() method.
Implementation of TagParser::Tag for the Matroska container.
MatroskaTagMaker prepareMaking(Diagnostics &diag)
Prepares making.
Implementation of TagParser::AbstractTrack for the Matroska container.
MatroskaTrackHeaderMaker prepareMakingHeader(Diagnostics &diag) const
Prepares making header.
void readStatisticsFromTags(const std::vector< std::unique_ptr< MatroskaTag > > &tags, Diagnostics &diag)
Reads track-specific statistics from the specified tags.
The exception that is thrown when the data to be parsed holds no parsable information (e....
NoDataFoundException() noexcept
Constructs a new exception.
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)