8 #include "../backuphelper.h" 9 #include "../exceptions.h" 10 #include "../mediafileinfo.h" 12 #include "resources/config.h" 14 #include <c++utilities/conversion/stringbuilder.h> 15 #include <c++utilities/conversion/stringconversion.h> 16 #include <c++utilities/io/catchiofailure.h> 22 #include <initializer_list> 26 #include <unordered_set> 41 uint64 MatroskaContainer::m_maxFullParseSize = 0x3200000;
46 MatroskaContainer::MatroskaContainer(
MediaFileInfo &fileInfo, uint64 startOffset)
73 m_tracksElements.clear();
74 m_segmentInfoElements.clear();
75 m_tagsElements.clear();
76 m_chaptersElements.clear();
77 m_attachmentsElements.clear();
79 m_editionEntries.clear();
80 m_attachments.clear();
90 static const string context(
"validating Matroska file index (cues)");
91 bool cuesElementsFound =
false;
93 unordered_set<EbmlElement::IdentifierType> ids;
94 bool cueTimeFound =
false, cueTrackPositionsFound =
false;
95 unique_ptr<EbmlElement> clusterElement;
96 uint64 pos, prevClusterSize = 0, currentOffset = 0;
100 segmentElement->parse(diag);
103 segmentChildElement = segmentChildElement->nextSibling()) {
104 segmentChildElement->parse(diag);
105 switch (segmentChildElement->id()) {
110 cuesElementsFound =
true;
113 cuePointElement = cuePointElement->nextSibling()) {
114 cuePointElement->parse(diag);
115 cueTimeFound = cueTrackPositionsFound =
false;
116 switch (cuePointElement->id()) {
123 cuePointChildElement = cuePointChildElement->nextSibling()) {
124 cuePointChildElement->parse(diag);
125 switch (cuePointChildElement->id()) {
130 DiagLevel::Warning,
"\"CuePoint\"-element contains multiple \"CueTime\" elements.", context);
136 cueTrackPositionsFound =
true;
138 clusterElement.reset();
140 subElement = subElement->nextSibling()) {
141 subElement->parse(diag);
142 switch (subElement->id()) {
150 if (ids.count(subElement->id())) {
152 "\"CueTrackPositions\"-element contains multiple \"" % subElement->idToString() +
"\" elements.",
155 ids.insert(subElement->id());
164 "\"CueTrackPositions\"-element contains unknown element \"" % subElement->idToString() +
"\".",
167 switch (subElement->id()) {
174 clusterElement = make_unique<EbmlElement>(
175 *
this, segmentElement->dataOffset() + subElement->readUInteger() - currentOffset);
177 clusterElement->parse(diag);
180 "\"CueClusterPosition\" element at " % numberToString(subElement->startOffset())
181 +
" does not point to \"Cluster\"-element (points to " 182 + numberToString(clusterElement->startOffset()) +
").",
190 pos = subElement->readUInteger();
206 "\"CueTrackPositions\"-element does not contain mandatory element \"CueTrack\".", context);
208 if (!clusterElement) {
210 "\"CueTrackPositions\"-element does not contain mandatory element \"CueClusterPosition\".", context);
213 EbmlElement referenceElement(*
this, clusterElement->dataOffset() + pos);
215 referenceElement.
parse(diag);
216 switch (referenceElement.id()) {
223 "\"CueRelativePosition\" element does not point to \"Block\"-, \"BlockGroup\", or " 224 "\"SimpleBlock\"-element (points to " 225 % numberToString(referenceElement.startOffset())
238 "\"CuePoint\"-element contains unknown element \"" % cuePointElement->idToString() +
"\".", context);
244 DiagLevel::Warning,
"\"CuePoint\"-element does not contain mandatory element \"CueTime\".", context);
246 if (!cueTrackPositionsFound) {
248 DiagLevel::Warning,
"\"CuePoint\"-element does not contain mandatory element \"CueClusterPosition\".", context);
258 clusterElementChild = clusterElementChild->nextSibling()) {
259 clusterElementChild->parse(diag);
260 switch (clusterElementChild->id()) {
266 if ((pos = clusterElementChild->readUInteger()) > 0
267 && (segmentChildElement->startOffset() - segmentElement->dataOffset() + currentOffset) != pos) {
269 argsToString(
"\"Position\"-element at ", clusterElementChild->startOffset(),
" points to ", pos,
270 " which is not the offset of the containing \"Cluster\"-element."),
276 if ((pos = clusterElementChild->readUInteger()) != prevClusterSize) {
278 argsToString(
"\"PrevSize\"-element at ", clusterElementChild->startOffset(),
" should be ", prevClusterSize,
279 " but is ", pos,
"."),
286 prevClusterSize = segmentChildElement->totalSize();
291 currentOffset += segmentElement->totalSize();
295 if (!cuesElementsFound) {
314 return find_if(elements.cbegin(), elements.cend(), std::bind(
sameOffset, offset, _1)) == elements.cend();
319 for (
const auto &entry : m_editionEntries) {
320 const auto &chapters = entry->chapters();
321 if (index < chapters.size()) {
322 return chapters[index].get();
324 index -= chapters.size();
333 for (
const auto &entry : m_editionEntries) {
334 count += entry->chapters().size();
342 static const auto randomEngine(
343 default_random_engine(static_cast<default_random_engine::result_type>(chrono::system_clock::now().time_since_epoch().count())));
345 auto dice(bind(uniform_int_distribution<decltype(attachmentId)>(), randomEngine));
348 attachmentId = dice();
350 for (
const auto &
attachment : m_attachments) {
353 goto generateRandomId;
358 m_attachments.emplace_back(make_unique<MatroskaAttachment>());
374 if (!segmentElement) {
377 for (
const EbmlElement *childElement = segmentElement->
firstChild(); childElement; childElement = childElement->nextSibling()) {
378 if (childElement->id() == elementId) {
381 for (
const auto &seekInfo : m_seekInfos) {
382 for (
const auto &info : seekInfo->info()) {
383 if (info.first == elementId) {
406 static const string context(
"parsing header of Matroska container");
410 m_tracksElements.clear();
411 m_segmentInfoElements.clear();
412 m_tagsElements.clear();
415 uint64 currentOffset = 0;
416 vector<MatroskaSeekInfo>::difference_type seekInfosIndex = 0;
419 for (
EbmlElement *topLevelElement =
m_firstElement.get(); topLevelElement; topLevelElement = topLevelElement->nextSibling()) {
421 topLevelElement->parse(diag);
422 switch (topLevelElement->id()) {
424 for (
EbmlElement *subElement = topLevelElement->
firstChild(); subElement; subElement = subElement->nextSibling()) {
426 subElement->parse(diag);
427 switch (subElement->id()) {
444 m_maxIdLength = subElement->readUInteger();
448 " bytes is not supported."),
454 m_maxSizeLength = subElement->readUInteger();
458 " bytes is not supported."),
465 diag.emplace_back(
DiagLevel::Critical,
"Unable to parse all childs of EBML header.", context);
472 for (
EbmlElement *subElement = topLevelElement->
firstChild(); subElement; subElement = subElement->nextSibling()) {
474 subElement->parse(diag);
475 switch (subElement->id()) {
477 m_seekInfos.emplace_back(make_unique<MatroskaSeekInfo>());
478 m_seekInfos.back()->parse(subElement, diag);
481 if (
excludesOffset(m_tracksElements, subElement->startOffset())) {
482 m_tracksElements.push_back(subElement);
486 if (
excludesOffset(m_segmentInfoElements, subElement->startOffset())) {
487 m_segmentInfoElements.push_back(subElement);
492 m_tagsElements.push_back(subElement);
496 if (
excludesOffset(m_chaptersElements, subElement->startOffset())) {
497 m_chaptersElements.push_back(subElement);
501 if (
excludesOffset(m_attachmentsElements, subElement->startOffset())) {
502 m_attachmentsElements.push_back(subElement);
508 for (
auto i = m_seekInfos.cbegin() + seekInfosIndex, end = m_seekInfos.cend(); i != end; ++i, ++seekInfosIndex) {
509 for (
const auto &infoPair : (*i)->info()) {
510 uint64 offset = currentOffset + topLevelElement->dataOffset() + infoPair.second;
513 argsToString(
"Offset (", offset,
") denoted by \"SeekHead\" element is invalid."), context);
515 auto element = make_unique<EbmlElement>(*
this, offset);
517 element->parse(diag);
518 if (element->id() != infoPair.first) {
520 argsToString(
"ID of element ", element->idToString(),
" at ", offset,
521 " does not match the ID denoted in the \"SeekHead\" element (0x",
522 numberToString(infoPair.first, 16),
")."),
525 switch (element->id()) {
560 argsToString(
"Can not parse element at ", offset,
" (denoted using \"SeekHead\" element)."), context);
568 if (((!m_tracksElements.empty() && !m_tagsElements.empty()) ||
fileInfo().
size() > m_maxFullParseSize)
569 && !m_segmentInfoElements.empty()) {
575 diag.emplace_back(
DiagLevel::Critical,
"Unable to parse all childs of \"Segment\"-element.", context);
579 currentOffset += topLevelElement->totalSize();
585 DiagLevel::Critical, argsToString(
"Unable to parse top-level element at ", topLevelElement->startOffset(),
'.'), context);
593 parseSegmentInfo(diag);
595 diag.emplace_back(
DiagLevel::Critical,
"Unable to parse EBML (segment) \"Info\"-element.", context);
608 void MatroskaContainer::parseSegmentInfo(
Diagnostics &diag)
610 if (m_segmentInfoElements.empty()) {
614 for (EbmlElement *element : m_segmentInfoElements) {
615 element->parse(diag);
616 EbmlElement *subElement = element->firstChild();
617 float64 rawDuration = 0.0;
619 bool hasTitle =
false;
621 subElement->parse(diag);
622 switch (subElement->id()) {
624 m_titles.emplace_back(subElement->readString());
628 rawDuration = subElement->readFloat();
634 subElement = subElement->nextSibling();
641 if (rawDuration > 0.0) {
652 void MatroskaContainer::readTrackStatisticsFromTags(Diagnostics &diag)
664 static const string context(
"parsing tags of Matroska container");
667 element->parse(diag);
668 for (
EbmlElement *subElement = element->
firstChild(); subElement; subElement = subElement->nextSibling()) {
669 subElement->parse(diag);
670 switch (subElement->id()) {
672 m_tags.emplace_back(make_unique<MatroskaTag>());
674 m_tags.back()->parse(*subElement, diag);
685 diag.emplace_back(
DiagLevel::Warning,
"\"Tags\"-element contains unknown child. It will be ignored.", context);
690 readTrackStatisticsFromTags(diag);
694 readTrackStatisticsFromTags(diag);
699 static const string context(
"parsing tracks of Matroska container");
702 element->parse(diag);
703 for (
EbmlElement *subElement = element->
firstChild(); subElement; subElement = subElement->nextSibling()) {
704 subElement->parse(diag);
705 switch (subElement->id()) {
707 m_tracks.emplace_back(make_unique<MatroskaTrack>(*subElement));
721 "\"Tracks\"-element contains unknown child element \"" % subElement->idToString() +
"\". It will be ignored.", context);
726 readTrackStatisticsFromTags(diag);
730 readTrackStatisticsFromTags(diag);
735 static const string context(
"parsing editions/chapters of Matroska container");
738 element->parse(diag);
739 for (
EbmlElement *subElement = element->
firstChild(); subElement; subElement = subElement->nextSibling()) {
740 subElement->parse(diag);
741 switch (subElement->id()) {
743 m_editionEntries.emplace_back(make_unique<MatroskaEditionEntry>(subElement));
745 m_editionEntries.back()->parseNested(diag);
747 m_editionEntries.pop_back();
749 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Unable to parse edition entry ", m_editionEntries.size(),
'.'), context);
757 "\"Chapters\"-element contains unknown child element \"" % subElement->idToString() +
"\". It will be ignored.", context);
769 static const string context(
"parsing attachments of Matroska container");
770 for (
EbmlElement *element : m_attachmentsElements) {
772 element->parse(diag);
773 for (
EbmlElement *subElement = element->
firstChild(); subElement; subElement = subElement->nextSibling()) {
774 subElement->parse(diag);
775 switch (subElement->id()) {
777 m_attachments.emplace_back(make_unique<MatroskaAttachment>());
779 m_attachments.back()->parse(subElement, diag);
781 m_attachments.pop_back();
783 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Unable to parse attached file ", m_attachments.size(),
'.'), context);
791 "\"Attachments\"-element contains unknown child element \"" % subElement->idToString() +
"\". It will be ignored.", context);
851 static const string context(
"making Matroska container");
852 progress.
updateStep(
"Calculating element sizes ...");
862 if (!level0Element) {
869 vector<MatroskaTagMaker> tagMaker;
870 tagMaker.reserve(
tags().size());
871 uint64 tagElementsSize = 0;
873 vector<MatroskaAttachmentMaker> attachmentMaker;
874 attachmentMaker.reserve(m_attachments.size());
875 uint64 attachedFileElementsSize = 0;
876 uint64 attachmentsSize;
877 vector<MatroskaTrackHeaderMaker> trackHeaderMaker;
878 trackHeaderMaker.reserve(
tracks().size());
879 uint64 trackHeaderElementsSize = 0;
880 uint64 trackHeaderSize;
884 unsigned int segmentIndex = 0;
886 vector<SegmentData> segmentData;
892 uint64 currentPosition = 0;
894 vector<tuple<uint64, uint64>> crc32Offsets;
898 uint64 clusterSize, clusterReadSize, clusterReadOffset;
910 unsigned int lastSegmentIndex = numeric_limits<unsigned int>::max();
918 uint64 ebmlHeaderDataSize = 2 * 7;
920 for (
auto headerValue :
931 constexpr
const char muxingAppName[] = APP_NAME
" v" APP_VERSION;
932 constexpr uint64 muxingAppElementDataSize =
sizeof(muxingAppName) - 1;
933 constexpr uint64 muxingAppElementTotalSize = 2 + 1 + muxingAppElementDataSize;
936 const uint64 writingAppElementDataSize
938 const uint64 writingAppElementTotalSize = 2 + 1 + writingAppElementDataSize;
945 if (tagMaker.back().requiredSize() > 3) {
947 tagElementsSize += tagMaker.back().requiredSize();
959 if (attachmentMaker.back().requiredSize() > 3) {
961 attachedFileElementsSize += attachmentMaker.back().requiredSize();
974 if (trackHeaderMaker.back().requiredSize() > 3) {
976 trackHeaderElementsSize += trackHeaderMaker.back().requiredSize();
988 for (
bool firstClusterFound =
false, firstTagFound =
false; level0Element; level0Element = level0Element->
nextSibling()) {
989 level0Element->
parse(diag);
990 switch (level0Element->
id()) {
993 for (level1Element = level0Element->
firstChild(); level1Element && !firstClusterFound && !firstTagFound;
995 level1Element->
parse(diag);
996 switch (level1Element->
id()) {
999 firstTagFound =
true;
1002 firstClusterFound =
true;
1005 if (firstTagFound) {
1007 }
else if (firstClusterFound) {
1014 segmentData.resize(lastSegmentIndex + 1);
1025 "Unable to parse content in top-level element at " % numberToString(level0Element->
startOffset()) +
" of original file.", context);
1029 progress.
nextStepOrStop(
"Calculating offsets of elements before cluster ...");
1030 calculateSegmentData:
1033 uint64 currentOffset = ebmlHeaderSize;
1035 uint64 readOffset = 0;
1040 if (rewriteRequired) {
1051 for (level0Element =
firstElement(), currentPosition = newPadding = segmentIndex = 0; level0Element;
1053 switch (level0Element->
id()) {
1082 newCuesPos = currentCuesPos;
1095 calculateSegmentSize:
1108 goto calculateSegmentSize;
1112 segment.
infoDataSize = muxingAppElementTotalSize + writingAppElementTotalSize;
1114 if (segmentIndex <
m_titles.size()) {
1116 if (!
title.empty()) {
1121 for (level2Element = level1Element->
firstChild(); level2Element; level2Element = level2Element->
nextSibling()) {
1122 level2Element->
parse(diag);
1123 switch (level2Element->
id()) {
1141 if (trackHeaderSize) {
1144 goto calculateSegmentSize;
1156 goto calculateSegmentSize;
1172 goto calculateSegmentSize;
1179 if (attachmentsSize) {
1182 goto calculateSegmentSize;
1196 goto calculateSegmentSize;
1199 progress.
updateStep(
"Calculating cluster offsets and index size ...");
1204 progress.
updateStep(
"Calculating cluster offsets ...");
1208 if (!rewriteRequired) {
1215 if (totalOffset <= segment.firstClusterElement->
startOffset()) {
1224 diag.emplace_back(
DiagLevel::Critical,
"Header size of \"Segment\"-element from original file is invalid.", context);
1229 nonRewriteCalculations:
1234 goto calculateSegmentSize;
1237 bool cuesInvalidated =
false;
1245 cuesInvalidated =
true;
1250 if (index % 50 == 0) {
1254 if (cuesInvalidated) {
1256 goto addCuesElementSize;
1261 progress.
updateStep(
"Calculating offsets of elements after cluster ...");
1265 goto calculateSegmentSize;
1277 goto calculateSegmentSize;
1284 if (attachmentsSize) {
1287 goto calculateSegmentSize;
1301 goto nonRewriteCalculations;
1304 totalOffset = currentOffset + 4 + sizeLength + offset;
1309 if (totalOffset <= segment.firstClusterElement->
startOffset()) {
1315 rewriteRequired =
true;
1318 rewriteRequired =
true;
1321 rewriteRequired =
true;
1324 diag.emplace_back(
DiagLevel::Warning, argsToString(
"There are no clusters in segment ", segmentIndex,
"."), context);
1327 if (rewriteRequired) {
1333 rewriteRequired =
false;
1335 && (!
fileInfo().forceIndexPosition()
1339 rewriteRequired =
false;
1342 goto calculateSegmentData;
1355 bool cuesInvalidated =
false;
1361 cuesInvalidated =
true;
1364 goto calculateSegmentSize;
1367 clusterSize = clusterReadSize = 0;
1368 for (level2Element = level1Element->
firstChild(); level2Element; level2Element = level2Element->
nextSibling()) {
1369 level2Element->
parse(diag);
1373 cuesInvalidated =
true;
1375 switch (level2Element->
id()) {
1383 clusterSize += level2Element->
totalSize();
1385 clusterReadSize += level2Element->
totalSize();
1394 if ((index % 50 == 0) &&
fileInfo().size()) {
1400 if (cuesInvalidated) {
1403 goto addCuesElementSize;
1407 progress.
updateStep(
"Calculating offsets of elements after cluster ...");
1411 goto calculateSegmentSize;
1426 goto calculateSegmentSize;
1433 if (attachmentsSize) {
1436 goto calculateSegmentSize;
1454 readOffset += level0Element->
totalSize();
1461 "The top-level element \"" % level0Element->
idToString() +
"\" of the original file is unknown and will just be copied.",
1463 currentOffset += level0Element->
totalSize();
1464 readOffset += level0Element->
totalSize();
1468 if (!rewriteRequired) {
1470 if ((rewriteRequired = (newPadding >
fileInfo().maxPadding() || newPadding <
fileInfo().minPadding()))) {
1472 goto calculateSegmentData;
1480 const char *what = catchIoFailure();
1481 diag.emplace_back(
DiagLevel::Critical,
"An IO error occured when parsing the original file.", context);
1482 throwIoFailure(what);
1492 NativeFileStream backupStream;
1493 BinaryWriter outputWriter(&outputStream);
1496 if (rewriteRequired) {
1497 if (
fileInfo().saveFilePath().empty()) {
1502 outputStream.open(
fileInfo().path(), ios_base::out | ios_base::binary | ios_base::trunc);
1504 const char *what = catchIoFailure();
1505 diag.emplace_back(
DiagLevel::Critical,
"Creation of temporary file (to rewrite the original file) failed.", context);
1506 throwIoFailure(what);
1511 backupStream.exceptions(ios_base::badbit | ios_base::failbit);
1512 backupStream.open(
fileInfo().path(), ios_base::in | ios_base::binary);
1514 outputStream.open(
fileInfo().saveFilePath(), ios_base::out | ios_base::binary | ios_base::trunc);
1516 const char *what = catchIoFailure();
1517 diag.emplace_back(
DiagLevel::Critical,
"Opening streams to write output file failed.", context);
1518 throwIoFailure(what);
1529 for (
auto &maker : attachmentMaker) {
1530 maker.bufferCurrentAttachments(diag);
1536 outputStream.open(
fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary);
1538 const char *what = catchIoFailure();
1539 diag.emplace_back(
DiagLevel::Critical,
"Opening the file with write permissions failed.", context);
1540 throwIoFailure(what);
1550 outputStream.write(buff, sizeLength);
1560 for (level0Element =
firstElement(), segmentIndex = 0, currentPosition = 0; level0Element; level0Element = level0Element->
nextSibling()) {
1563 switch (level0Element->
id()) {
1578 progress.
updateStep(
"Writing segment header ...");
1581 outputStream.write(buff, sizeLength);
1582 segment.
newDataOffset = offset =
static_cast<uint64
>(outputStream.tellp());
1588 *(buff + 1) = static_cast<char>(0x84);
1590 crc32Offsets.emplace_back(outputStream.tellp(), segment.
totalDataSize);
1591 outputStream.write(buff, 6);
1603 outputStream.write(buff, sizeLength);
1605 for (level2Element = level1Element->
firstChild(); level2Element; level2Element = level2Element->
nextSibling()) {
1606 switch (level2Element->
id()) {
1619 if (segmentIndex <
m_titles.size()) {
1621 if (!
title.empty()) {
1628 fileInfo().writingApplication().empty() ? muxingAppName :
fileInfo().writingApplication().data(), writingAppElementDataSize);
1632 if (trackHeaderElementsSize) {
1635 outputStream.write(buff, sizeLength);
1636 for (
auto &maker : trackHeaderMaker) {
1637 maker.make(outputStream);
1653 outputStream.write(buff, sizeLength);
1654 for (
auto &maker : tagMaker) {
1655 maker.make(outputStream);
1659 if (attachmentsSize) {
1662 outputStream.write(buff, sizeLength);
1663 for (
auto &maker : attachmentMaker) {
1664 maker.make(outputStream, diag);
1680 *buff =
static_cast<char>(voidLength = segment.
newPadding - 2) | 0x80;
1683 BE::getBytes(static_cast<uint64>((voidLength = segment.
newPadding - 9) | 0x100000000000000), buff);
1687 outputStream.write(buff, sizeLength);
1689 for (; voidLength; --voidLength) {
1690 outputStream.put(0);
1696 if (rewriteRequired) {
1699 "Writing cluster ...", static_cast<byte>((static_cast<uint64>(outputStream.tellp()) - offset) * 100 / segment.
totalDataSize));
1701 auto clusterSizesIterator = segment.
clusterSizes.cbegin();
1702 unsigned int index = 0;
1705 clusterSize = currentPosition + (
static_cast<uint64
>(outputStream.tellp()) - offset);
1709 outputStream.write(buff, sizeLength);
1711 for (level2Element = level1Element->
firstChild(); level2Element; level2Element = level2Element->
nextSibling()) {
1712 switch (level2Element->
id()) {
1720 level2Element->
copyEntirely(outputStream, diag,
nullptr);
1725 if (index % 50 == 0) {
1727 static_cast<byte>((static_cast<uint64>(outputStream.tellp()) - offset) * 100 / segment.
totalDataSize));
1733 static_cast<byte>((static_cast<uint64>(outputStream.tellp()) - offset) * 100 / segment.
totalDataSize));
1734 for (; level1Element; level1Element = level1Element->
nextSibling()) {
1735 for (level2Element = level1Element->
firstChild(); level2Element; level2Element = level2Element->
nextSibling()) {
1736 switch (level2Element->
id()) {
1740 level2Element->
dataSize() > 8 ? 8 :
static_cast<byte
>(level2Element->
dataSize()));
1742 if (level2Element->
dataSize() < sizeLength) {
1744 outputStream.seekp(static_cast<streamoff>(level2Element->
startOffset()));
1748 outputStream.seekp(static_cast<streamoff>(level2Element->
dataOffset()));
1749 outputStream.write(buff, sizeLength);
1760 progress.
updateStep(
"Writing segment tail ...");
1772 outputStream.write(buff, sizeLength);
1773 for (
auto &maker : tagMaker) {
1774 maker.make(outputStream);
1778 if (attachmentsSize) {
1781 outputStream.write(buff, sizeLength);
1782 for (
auto &maker : attachmentMaker) {
1783 maker.make(outputStream, diag);
1798 level0Element->
copyEntirely(outputStream, diag,
nullptr);
1799 currentPosition += level0Element->
totalSize();
1804 progress.
updateStep(
"Reparsing output file ...");
1805 if (rewriteRequired) {
1816 outputStream.close();
1817 outputStream.open(
fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary);
1820 const auto newSize =
static_cast<uint64
>(outputStream.tellp());
1824 outputStream.close();
1826 if (truncate(
fileInfo().path().c_str(), static_cast<iostream::off_type>(newSize)) == 0) {
1832 outputStream.open(
fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary);
1842 diag.emplace_back(
DiagLevel::Critical,
"Unable to reparse the header of the new file.", context);
1847 if (!crc32Offsets.empty()) {
1848 progress.
updateStep(
"Updating CRC-32 checksums ...");
1849 for (
const auto &crc32Offset : crc32Offsets) {
1850 outputStream.seekg(static_cast<streamoff>(get<0>(crc32Offset) + 6));
1851 outputStream.seekp(static_cast<streamoff>(get<0>(crc32Offset) + 2));
1852 writer().writeUInt32LE(
reader().readCrc32(get<1>(crc32Offset) - 6));
1857 outputStream.flush();
uint64 infoDataSize
size of the "SegmentInfo"-element
ElementPosition determineIndexPosition(Diagnostics &diag) const override
Determines the position of the index.
MatroskaTrackHeaderMaker prepareMakingHeader(Diagnostics &diag) const
Prepares making header.
bool sameOffset(uint64 offset, const EbmlElement *element)
Returns an indication whether offset equals the start offset of element.
void make(std::ostream &stream, Diagnostics &diag)
Writes the previously parsed "Cues"-element with updates positions to the specified stream...
void internalMakeFile(Diagnostics &diag, AbortableProgressFeedback &progress) override
Internally called to make the file.
std::unique_ptr< EbmlElement > m_firstElement
static void makeSimpleElement(std::ostream &stream, IdentifierType id, uint64 content)
Makes a simple EBML element.
MatroskaSeekInfo seekInfo
used to make "SeekHead"-element
void updateStepPercentage(byte stepPercentage)
Updates the current step percentage and invokes the second callback specified on construction (or the...
MatroskaTag * tag(std::size_t index) override
void internalParseAttachments(Diagnostics &diag) override
Internally called to parse the attachments.
void copyEntirely(std::ostream &targetStream, Diagnostics &diag, AbortableProgressFeedback *progress)
Writes the entire element including all childs to the specified targetStream.
MatroskaTagMaker prepareMaking(Diagnostics &diag)
Prepares making.
MatroskaCuePositionUpdater cuesUpdater
used to make "Cues"-element
EbmlElement * firstElement() const
Returns the first element of the file if available; otherwiese returns nullptr.
uint64 totalSize() const
Returns how many bytes will be written when calling the make() method.
void stopIfAborted() const
Throws an OperationAbortedException if aborted.
The MatroskaSeekInfo class helps parsing and making "SeekHead"-elements.
void makeBuffer()
Buffers the element (header and data).
bool hasCrc32
whether CRC-32 checksum is present
ImplementationType * nextSibling()
Returns the next sibling of the element.
MatroskaAttachment * createAttachment() override
Creates and returns a new attachment.
ImplementationType * firstChild()
Returns the first child of the element.
const std::vector< std::unique_ptr< MatroskaTrack > > & tracks() const
Returns the tracks of the file.
static byte calculateSizeDenotationLength(uint64 size)
Returns the length of the size denotation for the specified size in byte.
void internalParseChapters(Diagnostics &diag) override
Internally called to parse the chapters.
static constexpr uint32 maximumIdLengthSupported()
Returns the maximum id length supported by the class in byte.
Implementation of TagParser::AbstractAttachment for the Matroska container.
DataSizeType dataSize() const
Returns the data size of the element in byte.
uint64 endOffset() const
Returns the offset of the first byte which doesn't belong to this element anymore.
uint64 startOffset() const
Returns the start offset in the related stream.
void parse(EbmlElement *cuesElement, Diagnostics &diag)
Parses the specified cuesElement.
MatroskaTrack * track(std::size_t index) override
uint64 totalSize
total size of the segment data (in the new file, including header)
EbmlElement * cuesElement
"Cues"-element (original file)
static byte calculateUIntegerLength(uint64 integer)
Returns the length of the specified unsigned integer in byte.
void internalParseTracks(Diagnostics &diag) override
Internally called to parse the tracks.
The EbmlElement class helps to parse EBML files such as Matroska files.
uint64 totalSize() const
Returns the total size of the element.
uint32 timeScale() const
Returns the time scale of the file if known; otherwise returns 0.
uint64 newDataOffset
data offset of the segment in the new file
bool isHeaderParsed() const
Returns an indication whether the header has been parsed yet.
std::string idToString() const
Converts the specified EBML ID to a printable string.
MatroskaAttachment * attachment(std::size_t index) override
Returns the attachment with the specified index.
uint64 dataOffset() const
Returns the data offset of the element in the related stream.
void discardBuffer()
Discards buffered data.
uint64 clusterEndOffset
end offset of last "Cluster"-element (original file)
ChronoUtilities::TimeSpan m_duration
void validateIndex(Diagnostics &diag)
Validates the file index (cue entries).
uint64 id() const
Returns the ID of the attachment.
The MatroskaCuePositionUpdater class helps to rewrite the "Cues"-element with shifted positions...
std::vector< std::unique_ptr< EbmlElement > > m_additionalElements
void make(std::ostream &stream, Diagnostics &diag)
Writes a "SeekHead" element for the current instance to the specified stream.
ElementPosition determineElementPosition(uint64 elementId, Diagnostics &diag) const
Determines the position of the element with the specified elementId.
Contains utility classes helping to read and write streams.
void parse(Diagnostics &diag)
Parses the header information of the element which is read from the related stream at the start offse...
void setStream(std::iostream &stream)
Sets the related stream.
void setId(const uint64 &id)
Sets the ID of the attachment.
static byte makeUInteger(uint64 value, char *buff)
Writes value to buff.
uint64 startOffset
start offset (in the new file)
IoUtilities::BinaryWriter & writer()
Returns the related BinaryWriter.
static byte makeSizeDenotation(uint64 size, char *buff)
Makes the size denotation for the specified size and stores it to buff.
The exception that is thrown when the data to be parsed holds no parsable information.
The MatroskaChapter class provides an implementation of AbstractAttachment for Matroska files...
std::vector< std::unique_ptr< MatroskaTrack > > m_tracks
std::size_t chapterCount() const override
Returns the number of chapters the container holds.
ImplementationType * siblingById(const IdentifierType &id, Diagnostics &diag)
Returns the first sibling with the specified id.
void reportPathChanged(const std::string &newPath)
Call this function to report that the path changed.
vector< uint64 > clusterSizes
cluster sizes
byte sizeDenotationLength
header size (in the new file)
MatroskaChapter * chapter(std::size_t index) override
Returns the chapter with the specified index.
The private SegmentData struct is used in MatroskaContainer::internalMakeFile() to store segment spec...
uint32 headerSize() const
Returns the header size of the element in byte.
The AbortableProgressFeedback class provides feedback about an ongoing operation via callbacks...
void close()
A possibly opened std::fstream will be closed.
const std::vector< std::unique_ptr< MatroskaTag > > & tags() const
Returns the tags of the file.
std::vector< std::string > m_titles
void updateStep(const std::string &step, byte stepPercentage=0)
Updates the current step and invokes the first callback specified on construction.
Implementation of TagParser::Tag for the Matroska container.
ElementPosition determineTagPosition(Diagnostics &diag) const override
Determines the position of the tags inside the file.
TAG_PARSER_EXPORT void handleFailureAfterFileModified(MediaFileInfo &mediaFileInfo, const std::string &backupPath, IoUtilities::NativeFileStream &outputStream, IoUtilities::NativeFileStream &backupStream, Diagnostics &diag, const std::string &context="making file")
bool isIgnored() const
Returns whether the attachment is ignored/omitted when rewriting the container.
uint64 m_doctypeReadVersion
void reportSizeChanged(uint64 newSize)
Call this function to report that the size changed.
void copyBuffer(std::ostream &targetStream)
Copies buffered data to targetStream.
IoUtilities::BinaryReader & reader()
Returns the related BinaryReader.
uint64 newPadding
padding (in the new file)
SegmentData()
Constructs a new segment data object.
The exception that is thrown when the data to be parsed or to be made seems invalid and therefore can...
bool updateRelativeOffsets(uint64 referenceOffset, uint64 originalRelativeOffset, uint64 newRelativeOffset)
Sets the relative offset of the entries with the specified originalRelativeOffset and the specified r...
void reset() override
Discards all parsing results.
Implementation of TagParser::AbstractTrack for the Matroska container.
void internalParseHeader(Diagnostics &diag) override
Internally called to parse the header.
bool push(unsigned int index, EbmlElement::IdentifierType id, uint64 offset)
Pushes the specified offset of an element with the specified id to the info.
uint64 startOffset() const
Returns the start offset in the related stream.
uint64 size() const
Returns size of the current file in bytes.
IoUtilities::NativeFileStream & stream()
Returns the std::fstream for the current instance.
void reset() override
Discards all parsing results.
void internalParseTags(Diagnostics &diag) override
Internally called to parse the tags.
bool excludesOffset(const vector< EbmlElement *> &elements, uint64 offset)
Returns whether none of the specified elements have the specified offset.
uint64 totalDataSize
total size of the segment data (in the new file, excluding header)
void parseHeader(Diagnostics &diag)
Parses the header if not parsed yet.
~MatroskaContainer() override
static constexpr uint32 maximumSizeLengthSupported()
Returns the maximum size length supported by the class in byte.
void readStatisticsFromTags(const std::vector< std::unique_ptr< MatroskaTag >> &tags, Diagnostics &diag)
Reads track-specific statistics from the specified tags.
bool updateOffsets(uint64 originalOffset, uint64 newOffset)
Sets the offset of the entries with the specified originalOffset to newOffset.
MediaFileInfo & fileInfo() const
Returns the related file info.
The class inherits from std::exception and serves as base class for exceptions thrown by the elements...
const IdentifierType & id() const
Returns the element ID.
TAG_PARSER_EXPORT void createBackupFile(const std::string &originalPath, std::string &backupPath, IoUtilities::NativeFileStream &originalStream, IoUtilities::NativeFileStream &backupStream)
ImplementationType * childById(const IdentifierType &id, Diagnostics &diag)
Returns the first child with the specified id.
Contains all classes and functions of the TagInfo library.
TAG_PARSER_EXPORT const char * title()
EbmlElement * firstClusterElement
first "Cluster"-element (original file)
void nextStepOrStop(const std::string &step, byte stepPercentage=0)
Throws an OperationAbortedException if aborted; otherwise the data for the next step is set...
std::vector< std::unique_ptr< MatroskaTag > > m_tags
MatroskaAttachmentMaker prepareMaking(Diagnostics &diag)
Prepares making.
The Diagnostics class is a container for DiagMessage.
uint64 actualSize() const
Returns the number of bytes which will be written when calling the make() method. ...
The GenericContainer class helps parsing header, track, tag and chapter information of a file...