8 #include "../mediafileinfo.h" 9 #include "../exceptions.h" 10 #include "../backuphelper.h" 12 #include "resources/config.h" 14 #include <c++utilities/conversion/stringconversion.h> 15 #include <c++utilities/conversion/stringbuilder.h> 16 #include <c++utilities/io/catchiofailure.h> 21 #include <initializer_list> 22 #include <unordered_set> 36 constexpr
const char appInfo[] = APP_NAME
" v" APP_VERSION;
45 uint64 MatroskaContainer::m_maxFullParseSize = 0x3200000;
50 MatroskaContainer::MatroskaContainer(
MediaFileInfo &fileInfo, uint64 startOffset) :
76 m_tracksElements.clear();
77 m_segmentInfoElements.clear();
78 m_tagsElements.clear();
79 m_chaptersElements.clear();
80 m_attachmentsElements.clear();
82 m_editionEntries.clear();
83 m_attachments.clear();
93 static const string context(
"validating Matroska file index (cues)");
94 bool cuesElementsFound =
false;
96 unordered_set<EbmlElement::identifierType> ids;
97 bool cueTimeFound =
false, cueTrackPositionsFound =
false;
98 unique_ptr<EbmlElement> clusterElement;
99 uint64 pos, prevClusterSize = 0, currentOffset = 0;
102 segmentElement->parse();
104 for(
EbmlElement *segmentChildElement = segmentElement->
firstChild(); segmentChildElement; segmentChildElement = segmentChildElement->nextSibling()) {
105 segmentChildElement->parse();
106 switch(segmentChildElement->id()) {
111 cuesElementsFound =
true;
113 for(
EbmlElement *cuePointElement = segmentChildElement->
firstChild(); cuePointElement; cuePointElement = cuePointElement->nextSibling()) {
114 cuePointElement->parse();
115 cueTimeFound = cueTrackPositionsFound =
false;
116 switch(cuePointElement->id()) {
122 for(
EbmlElement *cuePointChildElement = cuePointElement->
firstChild(); cuePointChildElement; cuePointChildElement = cuePointChildElement->nextSibling()) {
123 cuePointChildElement->parse();
124 switch(cuePointChildElement->id()) {
134 cueTrackPositionsFound =
true;
136 clusterElement.reset();
137 for(
EbmlElement *subElement = cuePointChildElement->
firstChild(); subElement; subElement = subElement->nextSibling()) {
139 switch(subElement->id()) {
147 if(ids.count(subElement->id())) {
150 ids.insert(subElement->id());
160 switch(subElement->id()) {
167 clusterElement = make_unique<EbmlElement>(*
this, segmentElement->dataOffset() + subElement->readUInteger() - currentOffset);
169 clusterElement->parse();
171 addNotification(
NotificationType::Critical,
"\"CueClusterPosition\" element at " % numberToString(subElement->startOffset()) +
" does not point to \"Cluster\"-element (points to " + numberToString(clusterElement->startOffset()) +
").", context);
179 pos = subElement->readUInteger();
197 if(!clusterElement) {
202 EbmlElement referenceElement(*
this, clusterElement->dataOffset() + pos);
204 referenceElement.
parse();
205 switch(referenceElement.id()) {
211 addNotification(
NotificationType::Critical,
"\"CueRelativePosition\" element does not point to \"Block\"-, \"BlockGroup\", or \"SimpleBlock\"-element (points to " % numberToString(referenceElement.startOffset()) +
").", context);
230 if(!cueTrackPositionsFound) {
241 for(
EbmlElement *clusterElementChild = segmentChildElement->
firstChild(); clusterElementChild; clusterElementChild = clusterElementChild->nextSibling()) {
242 clusterElementChild->parse();
243 switch(clusterElementChild->id()) {
249 if((pos = clusterElementChild->readUInteger()) > 0 && (segmentChildElement->startOffset() - segmentElement->dataOffset() + currentOffset) != pos) {
250 addNotification(
NotificationType::Critical, argsToString(
"\"Position\"-element at ", clusterElementChild->startOffset(),
" points to ", pos,
" which is not the offset of the containing \"Cluster\"-element."), context);
255 if((pos = clusterElementChild->readUInteger()) != prevClusterSize) {
263 prevClusterSize = segmentChildElement->totalSize();
269 currentOffset += segmentElement->totalSize();
273 if(!cuesElementsFound) {
291 return find_if(elements.cbegin(), elements.cend(), std::bind(
sameOffset, offset, _1)) == elements.cend();
296 for(
const auto &entry : m_editionEntries) {
297 const auto &chapters = entry->chapters();
298 if(index < chapters.size()) {
299 return chapters[index].get();
301 index -= chapters.size();
310 for(
const auto &entry : m_editionEntries) {
311 count += entry->chapters().size();
319 static const auto randomEngine(default_random_engine(static_cast<default_random_engine::result_type>(chrono::system_clock::now().time_since_epoch().count())));
321 auto dice(bind(uniform_int_distribution<decltype(attachmentId)>(), randomEngine));
324 attachmentId = dice();
329 goto generateRandomId;
334 m_attachments.emplace_back(make_unique<MatroskaAttachment>());
350 if(!segmentElement) {
353 for(
const EbmlElement *childElement = segmentElement->
firstChild(); childElement; childElement = childElement->nextSibling()) {
354 if(childElement->id() == elementId) {
357 for(
const auto &seekInfo : m_seekInfos) {
358 for(
const auto &info : seekInfo->info()) {
359 if(info.first == elementId) {
382 static const string context(
"parsing header of Matroska container");
386 m_tracksElements.clear();
387 m_segmentInfoElements.clear();
388 m_tagsElements.clear();
391 uint64 currentOffset = 0;
392 vector<MatroskaSeekInfo>::difference_type seekInfosIndex = 0;
395 for(
EbmlElement *topLevelElement =
m_firstElement.get(); topLevelElement; topLevelElement = topLevelElement->nextSibling()) {
397 topLevelElement->parse();
398 switch(topLevelElement->id()) {
400 for(
EbmlElement *subElement = topLevelElement->
firstChild(); subElement; subElement = subElement->nextSibling()) {
403 switch(subElement->id()) {
420 m_maxIdLength = subElement->readUInteger();
429 m_maxSizeLength = subElement->readUInteger();
448 for(
EbmlElement *subElement = topLevelElement->
firstChild(); subElement; subElement = subElement->nextSibling()) {
451 switch(subElement->id()) {
453 m_seekInfos.emplace_back(make_unique<MatroskaSeekInfo>());
454 m_seekInfos.back()->parse(subElement);
459 m_tracksElements.push_back(subElement);
463 if(
excludesOffset(m_segmentInfoElements, subElement->startOffset())) {
464 m_segmentInfoElements.push_back(subElement);
469 m_tagsElements.push_back(subElement);
473 if(
excludesOffset(m_chaptersElements, subElement->startOffset())) {
474 m_chaptersElements.push_back(subElement);
478 if(
excludesOffset(m_attachmentsElements, subElement->startOffset())) {
479 m_attachmentsElements.push_back(subElement);
485 for(
auto i = m_seekInfos.cbegin() + seekInfosIndex, end = m_seekInfos.cend(); i != end; ++i, ++seekInfosIndex) {
486 for(
const auto &infoPair : (*i)->info()) {
487 uint64 offset = currentOffset + topLevelElement->dataOffset() + infoPair.second;
491 auto element = make_unique<EbmlElement>(*
this, offset);
494 if(element->id() != infoPair.first) {
495 addNotification(
NotificationType::Critical, argsToString(
"ID of element ", element->idToString(),
" at ", offset,
" does not match the ID denoted in the \"SeekHead\" element (0x", numberToString(infoPair.first, 16),
")."), context);
497 switch(element->id()) {
540 if(((!m_tracksElements.empty() && !m_tagsElements.empty()) ||
fileInfo().
size() > m_maxFullParseSize) && !m_segmentInfoElements.empty()) {
552 currentOffset += topLevelElement->totalSize();
583 void MatroskaContainer::parseSegmentInfo()
585 if(m_segmentInfoElements.empty()) {
589 for(EbmlElement *element : m_segmentInfoElements) {
591 EbmlElement *subElement = element->firstChild();
592 float64 rawDuration = 0.0;
594 bool hasTitle =
false;
597 switch(subElement->id()) {
599 m_titles.emplace_back(subElement->readString());
603 rawDuration = subElement->readFloat();
609 subElement = subElement->nextSibling();
616 if(rawDuration > 0.0) {
627 void MatroskaContainer::readTrackStatisticsFromTags()
639 static const string context(
"parsing tags of Matroska container");
643 for(
EbmlElement *subElement = element->
firstChild(); subElement; subElement = subElement->nextSibling()) {
645 switch(subElement->id()) {
647 m_tags.emplace_back(make_unique<MatroskaTag>());
649 m_tags.back()->parse(*subElement);
665 readTrackStatisticsFromTags();
669 readTrackStatisticsFromTags();
675 static const string context(
"parsing tracks of Matroska container");
679 for(
EbmlElement *subElement = element->
firstChild(); subElement; subElement = subElement->nextSibling()) {
681 switch(subElement->id()) {
683 m_tracks.emplace_back(make_unique<MatroskaTrack>(*subElement));
701 readTrackStatisticsFromTags();
705 readTrackStatisticsFromTags();
711 static const string context(
"parsing editions/chapters of Matroska container");
715 for(
EbmlElement *subElement = element->
firstChild(); subElement; subElement = subElement->nextSibling()) {
717 switch(subElement->id()) {
719 m_editionEntries.emplace_back(make_unique<MatroskaEditionEntry>(subElement));
721 m_editionEntries.back()->parseNested();
723 m_editionEntries.pop_back();
745 static const string context(
"parsing attachments of Matroska container");
746 for(
EbmlElement *element : m_attachmentsElements) {
749 for(
EbmlElement *subElement = element->
firstChild(); subElement; subElement = subElement->nextSibling()) {
751 switch(subElement->id()) {
753 m_attachments.emplace_back(make_unique<MatroskaAttachment>());
755 m_attachments.back()->parse(subElement);
757 m_attachments.pop_back();
828 static const string context(
"making Matroska container");
846 vector<MatroskaTagMaker> tagMaker;
847 tagMaker.reserve(
tags().size());
848 uint64 tagElementsSize = 0;
850 vector<MatroskaAttachmentMaker> attachmentMaker;
851 attachmentMaker.reserve(m_attachments.size());
852 uint64 attachedFileElementsSize = 0;
853 uint64 attachmentsSize;
854 vector<MatroskaTrackHeaderMaker> trackHeaderMaker;
855 trackHeaderMaker.reserve(
tracks().size());
856 uint64 trackHeaderElementsSize = 0;
857 uint64 trackHeaderSize;
861 unsigned int segmentIndex = 0;
863 vector<SegmentData> segmentData;
869 uint64 currentPosition = 0;
871 vector<tuple<uint64, uint64> > crc32Offsets;
875 uint64 clusterSize, clusterReadSize, clusterReadOffset;
887 unsigned int lastSegmentIndex = numeric_limits<unsigned int>::max();
895 uint64 ebmlHeaderDataSize = 2 * 7;
912 if(tagMaker.back().requiredSize() > 3) {
914 tagElementsSize += tagMaker.back().requiredSize();
929 if(attachmentMaker.back().requiredSize() > 3) {
931 attachedFileElementsSize += attachmentMaker.back().requiredSize();
946 if(trackHeaderMaker.back().requiredSize() > 3) {
948 trackHeaderElementsSize += trackHeaderMaker.back().requiredSize();
962 for(
bool firstClusterFound =
false, firstTagFound =
false; level0Element; level0Element = level0Element->
nextSibling()) {
963 level0Element->
parse();
964 switch(level0Element->
id()) {
967 for(level1Element = level0Element->
firstChild(); level1Element && !firstClusterFound && !firstTagFound; level1Element = level1Element->
nextSibling()) {
968 level1Element->
parse();
969 switch(level1Element->
id()) {
972 firstTagFound =
true;
975 firstClusterFound =
true;
980 }
else if(firstClusterFound) {
987 segmentData.resize(lastSegmentIndex + 1);
1001 updateStatus(
"Calculating offsets of elements before cluster ...", 0.0);
1002 calculateSegmentData:
1005 uint64 currentOffset = ebmlHeaderSize;
1007 uint64 readOffset = 0;
1012 if(rewriteRequired) {
1023 for(level0Element =
firstElement(), currentPosition = newPadding = segmentIndex = 0; level0Element; level0Element = level0Element->
nextSibling()) {
1024 switch(level0Element->
id()) {
1058 newCuesPos = currentCuesPos;
1071 calculateSegmentSize:
1083 goto calculateSegmentSize;
1089 if(segmentIndex <
m_titles.size()) {
1091 if(!
title.empty()) {
1096 for(level2Element = level1Element->
firstChild(); level2Element; level2Element = level2Element->
nextSibling()) {
1097 level2Element->
parse();
1098 switch(level2Element->
id()) {
1116 if(trackHeaderSize) {
1119 goto calculateSegmentSize;
1130 goto calculateSegmentSize;
1146 goto calculateSegmentSize;
1153 if(attachmentsSize) {
1156 goto calculateSegmentSize;
1170 goto calculateSegmentSize;
1173 updateStatus(
"Calculating cluster offsets and index size ...", 0.0);
1182 if(!rewriteRequired) {
1189 if(totalOffset <= segment.firstClusterElement->
startOffset()) {
1203 nonRewriteCalculations:
1207 goto calculateSegmentSize;
1210 bool cuesInvalidated =
false;
1215 cuesInvalidated =
true;
1222 if(index % 50 == 0) {
1226 if(cuesInvalidated) {
1228 goto addCuesElementSize;
1232 updateStatus(
"Calculating offsets of elements after cluster ...", 0.0);
1238 goto calculateSegmentSize;
1250 goto calculateSegmentSize;
1257 if(attachmentsSize) {
1260 goto calculateSegmentSize;
1274 goto nonRewriteCalculations;
1277 totalOffset = currentOffset + 4 + sizeLength + offset;
1282 if(totalOffset <= segment.firstClusterElement->
startOffset()) {
1288 rewriteRequired =
true;
1291 rewriteRequired =
true;
1294 rewriteRequired =
true;
1300 if(rewriteRequired) {
1304 rewriteRequired =
false;
1308 rewriteRequired =
false;
1311 goto calculateSegmentData;
1324 bool cuesInvalidated =
false;
1329 cuesInvalidated =
true;
1332 goto calculateSegmentSize;
1335 clusterSize = clusterReadSize = 0;
1336 for(level2Element = level1Element->
firstChild(); level2Element; level2Element = level2Element->
nextSibling()) {
1337 level2Element->
parse();
1339 cuesInvalidated =
true;
1341 switch(level2Element->
id()) {
1349 clusterSize += level2Element->
totalSize();
1351 clusterReadSize += level2Element->
totalSize();
1363 if((index % 50 == 0) &&
fileInfo().size()) {
1369 if(cuesInvalidated) {
1372 goto addCuesElementSize;
1375 updateStatus(
"Calculating offsets of elements after cluster ...", 0.0);
1381 goto calculateSegmentSize;
1396 goto calculateSegmentSize;
1403 if(attachmentsSize) {
1406 goto calculateSegmentSize;
1424 readOffset += level0Element->
totalSize();
1431 currentOffset += level0Element->
totalSize();
1432 readOffset += level0Element->
totalSize();
1436 if(!rewriteRequired) {
1438 if((rewriteRequired = (newPadding >
fileInfo().maxPadding() || newPadding <
fileInfo().minPadding()))) {
1440 goto calculateSegmentData;
1448 const char *what = catchIoFailure();
1450 throwIoFailure(what);
1464 NativeFileStream backupStream;
1465 BinaryWriter outputWriter(&outputStream);
1468 if(rewriteRequired) {
1469 if(
fileInfo().saveFilePath().empty()) {
1474 outputStream.open(
fileInfo().path(), ios_base::out | ios_base::binary | ios_base::trunc);
1476 const char *what = catchIoFailure();
1478 throwIoFailure(what);
1483 backupStream.exceptions(ios_base::badbit | ios_base::failbit);
1484 backupStream.open(
fileInfo().path(), ios_base::in | ios_base::binary);
1486 outputStream.open(
fileInfo().saveFilePath(), ios_base::out | ios_base::binary | ios_base::trunc);
1488 const char *what = catchIoFailure();
1490 throwIoFailure(what);
1501 for(
auto &maker : attachmentMaker) {
1502 maker.bufferCurrentAttachments();
1508 outputStream.open(
fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary);
1510 const char *what = catchIoFailure();
1512 throwIoFailure(what);
1522 outputStream.write(buff, sizeLength);
1532 for(level0Element =
firstElement(), segmentIndex = 0, currentPosition = 0; level0Element; level0Element = level0Element->
nextSibling()) {
1535 switch(level0Element->
id()) {
1553 outputStream.write(buff, sizeLength);
1562 crc32Offsets.emplace_back(outputStream.tellp(), segment.
totalDataSize);
1563 outputStream.write(buff, 6);
1576 outputStream.write(buff, sizeLength);
1578 for(level2Element = level1Element->
firstChild(); level2Element; level2Element = level2Element->
nextSibling()) {
1579 switch(level2Element->
id()) {
1592 if(segmentIndex <
m_titles.size()) {
1594 if(!
title.empty()) {
1604 if(trackHeaderElementsSize) {
1607 outputStream.write(buff, sizeLength);
1608 for(
auto &maker : trackHeaderMaker) {
1609 maker.make(outputStream);
1625 outputStream.write(buff, sizeLength);
1626 for(
auto &maker : tagMaker) {
1627 maker.make(outputStream);
1632 if(attachmentsSize) {
1635 outputStream.write(buff, sizeLength);
1636 for(
auto &maker : attachmentMaker) {
1637 maker.make(outputStream);
1660 *buff =
static_cast<char>(voidLength = segment.
newPadding - 2) | 0x80;
1663 BE::getBytes(static_cast<uint64>((voidLength = segment.
newPadding - 9) | 0x100000000000000), buff);
1667 outputStream.write(buff, sizeLength);
1669 for(; voidLength; --voidLength) {
1670 outputStream.put(0);
1676 if(rewriteRequired) {
1681 updateStatus(
"Writing cluster ...", static_cast<double>(static_cast<uint64>(outputStream.tellp()) - offset) / segment.
totalDataSize);
1683 auto clusterSizesIterator = segment.
clusterSizes.cbegin();
1684 unsigned int index = 0;
1687 clusterSize = currentPosition + (
static_cast<uint64
>(outputStream.tellp()) - offset);
1691 outputStream.write(buff, sizeLength);
1693 for(level2Element = level1Element->
firstChild(); level2Element; level2Element = level2Element->
nextSibling()) {
1694 switch(level2Element->
id()) {
1708 }
else if(index % 50 == 0) {
1714 updateStatus(
"Updateing cluster ...", static_cast<double>(static_cast<uint64>(outputStream.tellp()) - offset) / segment.
totalDataSize);
1715 for(; level1Element; level1Element = level1Element->
nextSibling()) {
1716 for(level2Element = level1Element->
firstChild(); level2Element; level2Element = level2Element->
nextSibling()) {
1717 switch(level2Element->
id()) {
1722 if(level2Element->
dataSize() < sizeLength) {
1728 outputStream.seekp(level2Element->
dataOffset());
1729 outputStream.write(buff, sizeLength);
1759 outputStream.write(buff, sizeLength);
1760 for(
auto &maker : tagMaker) {
1761 maker.make(outputStream);
1766 if(attachmentsSize) {
1769 outputStream.write(buff, sizeLength);
1770 for(
auto &maker : attachmentMaker) {
1771 maker.make(outputStream);
1788 currentPosition += level0Element->
totalSize();
1794 if(rewriteRequired) {
1805 outputStream.close();
1806 outputStream.open(
fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary);
1809 const auto newSize =
static_cast<uint64
>(outputStream.tellp());
1813 outputStream.close();
1815 if(truncate(
fileInfo().path().c_str(), static_cast<iostream::off_type>(newSize)) == 0) {
1821 outputStream.open(
fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary);
1836 if(!crc32Offsets.empty()) {
1838 for(
const auto &crc32Offset : crc32Offsets) {
1839 outputStream.seekg(get<0>(crc32Offset) + 6);
1840 outputStream.seekp(get<0>(crc32Offset) + 2);
1841 writer().writeUInt32LE(
reader().readCrc32(get<1>(crc32Offset) - 6));
1848 outputStream.flush();
Contains utility classes helping to read and write streams.