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> 33 constexpr
const char appInfo[] = APP_NAME
" v" APP_VERSION;
42 uint64 MatroskaContainer::m_maxFullParseSize = 0x3200000;
47 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<int> ids;
94 bool cueTimeFound =
false, cueTrackPositionsFound =
false;
95 unique_ptr<EbmlElement> clusterElement;
96 uint64 pos, prevClusterSize = 0, currentOffset = 0;
99 segmentElement->parse();
101 for(
EbmlElement *segmentChildElement = segmentElement->
firstChild(); segmentChildElement; segmentChildElement = segmentChildElement->nextSibling()) {
102 segmentChildElement->parse();
103 switch(segmentChildElement->id()) {
108 cuesElementsFound =
true;
110 for(
EbmlElement *cuePointElement = segmentChildElement->
firstChild(); cuePointElement; cuePointElement = cuePointElement->nextSibling()) {
111 cuePointElement->parse();
112 cueTimeFound = cueTrackPositionsFound =
false;
113 switch(cuePointElement->id()) {
119 for(
EbmlElement *cuePointChildElement = cuePointElement->
firstChild(); cuePointChildElement; cuePointChildElement = cuePointChildElement->nextSibling()) {
120 cuePointChildElement->parse();
121 switch(cuePointChildElement->id()) {
131 cueTrackPositionsFound =
true;
133 clusterElement.reset();
134 for(
EbmlElement *subElement = cuePointChildElement->
firstChild(); subElement; subElement = subElement->nextSibling()) {
136 switch(subElement->id()) {
144 if(ids.count(subElement->id())) {
147 ids.insert(subElement->id());
157 switch(subElement->id()) {
164 clusterElement = make_unique<EbmlElement>(*
this, segmentElement->dataOffset() + subElement->readUInteger() - currentOffset);
166 clusterElement->parse();
168 addNotification(
NotificationType::Critical,
"\"CueClusterPosition\" element at " % numberToString(subElement->startOffset()) +
" does not point to \"Cluster\"-element (points to " + numberToString(clusterElement->startOffset()) +
").", context);
176 pos = subElement->readUInteger();
194 if(!clusterElement) {
199 EbmlElement referenceElement(*
this, clusterElement->dataOffset() + pos);
201 referenceElement.
parse();
202 switch(referenceElement.id()) {
208 addNotification(
NotificationType::Critical,
"\"CueRelativePosition\" element does not point to \"Block\"-, \"BlockGroup\", or \"SimpleBlock\"-element (points to " % numberToString(referenceElement.startOffset()) +
").", context);
227 if(!cueTrackPositionsFound) {
238 for(
EbmlElement *clusterElementChild = segmentChildElement->
firstChild(); clusterElementChild; clusterElementChild = clusterElementChild->nextSibling()) {
239 clusterElementChild->parse();
240 switch(clusterElementChild->id()) {
246 if((pos = clusterElementChild->readUInteger()) > 0 && (segmentChildElement->startOffset() - segmentElement->dataOffset() + currentOffset) != pos) {
247 addNotification(
NotificationType::Critical,
"\"Position\"-element at " % numberToString(clusterElementChild->startOffset()) %
" points to " % numberToString(pos) +
" which is not the offset of the containing \"Cluster\"-element.", context);
252 if(clusterElementChild->readUInteger() != prevClusterSize) {
260 prevClusterSize = segmentChildElement->totalSize();
266 currentOffset += segmentElement->totalSize();
270 if(!cuesElementsFound) {
288 return find_if(elements.cbegin(), elements.cend(), std::bind(
sameOffset, offset, _1)) == elements.cend();
293 for(
const auto &entry : m_editionEntries) {
294 const auto &chapters = entry->chapters();
295 if(index < chapters.size()) {
296 return chapters[index].get();
298 index -= chapters.size();
307 for(
const auto &entry : m_editionEntries) {
308 count += entry->chapters().size();
316 srand(time(
nullptr));
320 attachmentId = rand();
325 goto generateRandomId;
330 m_attachments.emplace_back(make_unique<MatroskaAttachment>());
344 for(
const EbmlElement *childElement = segmentElement->
firstChild(); childElement; childElement = childElement->nextSibling()) {
345 if(childElement->id() == elementId) {
348 for(
const auto &seekInfo : m_seekInfos) {
349 for(
const auto &info : seekInfo->info()) {
350 if(info.first == elementId) {
375 static const string context(
"parsing header of Matroska container");
379 m_tracksElements.clear();
380 m_segmentInfoElements.clear();
381 m_tagsElements.clear();
384 uint64 currentOffset = 0;
385 vector<MatroskaSeekInfo>::size_type seekInfosIndex = 0;
387 for(
EbmlElement *topLevelElement =
m_firstElement.get(); topLevelElement; topLevelElement = topLevelElement->nextSibling()) {
389 topLevelElement->parse();
390 switch(topLevelElement->id()) {
392 for(
EbmlElement *subElement = topLevelElement->
firstChild(); subElement; subElement = subElement->nextSibling()) {
395 switch(subElement->id()) {
412 m_maxIdLength = subElement->readUInteger();
416 +
" bytes is not supported.", context);
421 m_maxSizeLength = subElement->readUInteger();
425 +
" bytes is not supported.", context);
440 for(
EbmlElement *subElement = topLevelElement->
firstChild(); subElement; subElement = subElement->nextSibling()) {
443 switch(subElement->id()) {
445 m_seekInfos.emplace_back(make_unique<MatroskaSeekInfo>());
446 m_seekInfos.back()->parse(subElement);
451 m_tracksElements.push_back(subElement);
455 if(
excludesOffset(m_segmentInfoElements, subElement->startOffset())) {
456 m_segmentInfoElements.push_back(subElement);
461 m_tagsElements.push_back(subElement);
465 if(
excludesOffset(m_chaptersElements, subElement->startOffset())) {
466 m_chaptersElements.push_back(subElement);
470 if(
excludesOffset(m_attachmentsElements, subElement->startOffset())) {
471 m_attachmentsElements.push_back(subElement);
477 for(
auto i = m_seekInfos.cbegin() + seekInfosIndex, end = m_seekInfos.cend(); i != end; ++i, ++seekInfosIndex) {
478 for(
const auto &infoPair : (*i)->info()) {
479 uint64 offset = currentOffset + topLevelElement->dataOffset() + infoPair.second;
483 auto element = make_unique<EbmlElement>(*
this, offset);
486 if(element->id() != infoPair.first) {
487 addNotification(
NotificationType::Critical,
"ID of element " % element->idToString() %
" at " % numberToString(offset) %
" does not match the ID denoted in the \"SeekHead\" element (0x" % numberToString(infoPair.first, 16) +
").", context);
489 switch(element->id()) {
532 if(((!m_tracksElements.empty() && !m_tagsElements.empty()) ||
fileInfo().
size() > m_maxFullParseSize) && !m_segmentInfoElements.empty()) {
544 currentOffset += topLevelElement->totalSize();
574 void MatroskaContainer::parseSegmentInfo()
576 if(m_segmentInfoElements.empty()) {
580 for(
EbmlElement *element : m_segmentInfoElements) {
583 float64 rawDuration = 0.0;
585 bool hasTitle =
false;
588 switch(subElement->
id()) {
607 if(rawDuration > 0.0 && timeScale > 0) {
608 m_duration += TimeSpan::fromSeconds(rawDuration * timeScale / 1000000000);
615 static const string context(
"parsing tags of Matroska container");
619 for(
EbmlElement *subElement = element->
firstChild(); subElement; subElement = subElement->nextSibling()) {
621 switch(subElement->id()) {
623 m_tags.emplace_back(make_unique<MatroskaTag>());
625 m_tags.back()->parse(*subElement);
649 static const string context(
"parsing tracks of Matroska container");
653 for(
EbmlElement *subElement = element->
firstChild(); subElement; subElement = subElement->nextSibling()) {
655 switch(subElement->id()) {
657 m_tracks.emplace_back(make_unique<MatroskaTrack>(*subElement));
683 static const string context(
"parsing editions/chapters of Matroska container");
687 for(
EbmlElement *subElement = element->
firstChild(); subElement; subElement = subElement->nextSibling()) {
689 switch(subElement->id()) {
691 m_editionEntries.emplace_back(make_unique<MatroskaEditionEntry>(subElement));
693 m_editionEntries.back()->parseNested();
695 m_editionEntries.pop_back();
717 static const string context(
"parsing attachments of Matroska container");
718 for(
EbmlElement *element : m_attachmentsElements) {
721 for(
EbmlElement *subElement = element->
firstChild(); subElement; subElement = subElement->nextSibling()) {
723 switch(subElement->id()) {
725 m_attachments.emplace_back(make_unique<MatroskaAttachment>());
727 m_attachments.back()->parse(subElement);
729 m_attachments.pop_back();
754 cuesElement(nullptr),
756 firstClusterElement(nullptr),
760 sizeDenotationLength(0),
800 static const string context(
"making Matroska container");
818 vector<MatroskaTagMaker> tagMaker;
819 uint64 tagElementsSize = 0;
821 vector<MatroskaAttachmentMaker> attachmentMaker;
822 uint64 attachedFileElementsSize = 0;
823 uint64 attachmentsSize;
827 unsigned int segmentIndex = 0;
829 vector<SegmentData> segmentData;
835 uint64 currentPosition = 0;
837 vector<tuple<uint64, uint64> > crc32Offsets;
841 uint64 clusterSize, clusterReadSize, clusterReadOffset;
853 unsigned int lastSegmentIndex =
static_cast<unsigned int>(-1);
861 uint64 ebmlHeaderDataSize = 2 * 7;
878 if(tagMaker.back().requiredSize() > 3) {
880 tagElementsSize += tagMaker.back().requiredSize();
895 if(attachmentMaker.back().requiredSize() > 3) {
897 attachedFileElementsSize += attachmentMaker.back().requiredSize();
911 for(
bool firstClusterFound =
false, firstTagFound =
false; level0Element; level0Element = level0Element->
nextSibling()) {
912 level0Element->
parse();
913 switch(level0Element->
id()) {
916 for(level1Element = level0Element->
firstChild(); level1Element && !firstClusterFound && !firstTagFound; level1Element = level1Element->
nextSibling()) {
917 level1Element->
parse();
918 switch(level1Element->
id()) {
921 firstTagFound =
true;
924 firstClusterFound =
true;
929 }
else if(firstClusterFound) {
936 segmentData.resize(lastSegmentIndex + 1);
950 calculateSegmentData:
953 uint64 currentOffset = ebmlHeaderSize;
955 uint64 readOffset = 0;
960 if(rewriteRequired) {
972 for(level0Element =
firstElement(), currentPosition = newPadding = segmentIndex = 0; level0Element; level0Element = level0Element->
nextSibling()) {
973 switch(level0Element->
id()) {
1009 newCuesPos = currentCuesPos;
1022 calculateSegmentSize:
1034 goto calculateSegmentSize;
1040 if(segmentIndex <
m_titles.size()) {
1042 if(!
title.empty()) {
1047 for(level2Element = level1Element->
firstChild(); level2Element; level2Element = level2Element->
nextSibling()) {
1048 level2Element->
parse();
1049 switch(level2Element->
id()) {
1068 for(level1Element = level0Element->
childById(
id), index = 0; level1Element; level1Element = level1Element->
siblingById(
id), ++index) {
1071 goto calculateSegmentSize;
1088 goto calculateSegmentSize;
1095 if(attachmentsSize) {
1098 goto calculateSegmentSize;
1112 goto calculateSegmentSize;
1121 if(!rewriteRequired) {
1130 if(totalOffset <= segment.firstClusterElement->
startOffset()) {
1144 nonRewriteCalculations:
1148 goto calculateSegmentSize;
1156 goto addCuesElementSize;
1165 goto calculateSegmentSize;
1177 goto calculateSegmentSize;
1184 if(attachmentsSize) {
1187 goto calculateSegmentSize;
1201 goto nonRewriteCalculations;
1204 totalOffset = currentOffset + 4 + sizeLength + offset;
1209 if(totalOffset <= segment.firstClusterElement->
startOffset()) {
1215 rewriteRequired =
true;
1218 rewriteRequired =
true;
1221 rewriteRequired =
true;
1233 if(rewriteRequired) {
1237 rewriteRequired =
false;
1241 rewriteRequired =
false;
1244 goto calculateSegmentData;
1262 goto addCuesElementSize;
1265 goto calculateSegmentSize;
1268 clusterSize = clusterReadSize = 0;
1269 for(level2Element = level1Element->
firstChild(); level2Element; level2Element = level2Element->
nextSibling()) {
1270 level2Element->
parse();
1273 goto addCuesElementSize;
1275 switch(level2Element->
id()) {
1283 clusterSize += level2Element->
totalSize();
1285 clusterReadSize += level2Element->
totalSize();
1297 goto calculateSegmentSize;
1312 goto calculateSegmentSize;
1319 if(attachmentsSize) {
1322 goto calculateSegmentSize;
1340 readOffset += level0Element->
totalSize();
1347 currentOffset += level0Element->
totalSize();
1348 readOffset += level0Element->
totalSize();
1352 if(!rewriteRequired) {
1354 if((rewriteRequired = (newPadding >
fileInfo().maxPadding() || newPadding <
fileInfo().minPadding()))) {
1356 goto calculateSegmentData;
1364 const char *what = catchIoFailure();
1366 throwIoFailure(what);
1380 NativeFileStream backupStream;
1381 BinaryWriter outputWriter(&outputStream);
1384 if(rewriteRequired) {
1385 if(
fileInfo().saveFilePath().empty()) {
1390 outputStream.open(
fileInfo().path(), ios_base::out | ios_base::binary | ios_base::trunc);
1392 const char *what = catchIoFailure();
1394 throwIoFailure(what);
1399 backupStream.exceptions(ios_base::badbit | ios_base::failbit);
1400 backupStream.open(
fileInfo().path(), ios_base::in | ios_base::binary);
1402 outputStream.open(
fileInfo().saveFilePath(), ios_base::out | ios_base::binary | ios_base::trunc);
1404 const char *what = catchIoFailure();
1406 throwIoFailure(what);
1417 for(
auto &maker : attachmentMaker) {
1418 maker.bufferCurrentAttachments();
1424 outputStream.open(
fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary);
1426 const char *what = catchIoFailure();
1428 throwIoFailure(what);
1438 outputStream.write(buff, sizeLength);
1448 for(level0Element =
firstElement(), segmentIndex = 0, currentPosition = 0; level0Element; level0Element = level0Element->
nextSibling()) {
1451 switch(level0Element->
id()) {
1469 outputStream.write(buff, sizeLength);
1478 crc32Offsets.emplace_back(outputStream.tellp(), segment.
totalDataSize);
1479 outputStream.write(buff, 6);
1492 outputStream.write(buff, sizeLength);
1494 for(level2Element = level1Element->
firstChild(); level2Element; level2Element = level2Element->
nextSibling()) {
1495 switch(level2Element->
id()) {
1509 if(segmentIndex <
m_titles.size()) {
1511 if(!
title.empty()) {
1522 for(level1Element = level0Element->
childById(
id); level1Element; level1Element = level1Element->
siblingById(
id)) {
1534 outputStream.write(buff, sizeLength);
1535 for(
auto &maker : tagMaker) {
1536 maker.make(outputStream);
1541 if(attachmentsSize) {
1544 outputStream.write(buff, sizeLength);
1545 for(
auto &maker : attachmentMaker) {
1546 maker.make(outputStream);
1570 *buff = (voidLength = segment.
newPadding - 2) | 0x80;
1573 BE::getBytes(static_cast<uint64>((voidLength = segment.
newPadding - 9) | 0x100000000000000), buff);
1577 outputStream.write(buff, sizeLength);
1579 for(; voidLength; --voidLength) {
1580 outputStream.put(0);
1586 if(rewriteRequired) {
1592 updateStatus(
"Writing clusters ...", static_cast<double>(static_cast<uint64>(outputStream.tellp()) - offset) / segment.
totalDataSize);
1594 for(
auto clusterSizesIterator = segment.
clusterSizes.cbegin();
1597 clusterSize = currentPosition + (
static_cast<uint64
>(outputStream.tellp()) - offset);
1601 outputStream.write(buff, sizeLength);
1603 for(level2Element = level1Element->
firstChild(); level2Element; level2Element = level2Element->
nextSibling()) {
1604 switch(level2Element->
id()) {
1624 for(; level1Element; level1Element = level1Element->
nextSibling()) {
1625 for(level2Element = level1Element->
firstChild(); level2Element; level2Element = level2Element->
nextSibling()) {
1626 switch(level2Element->
id()) {
1631 if(level2Element->
dataSize() < sizeLength) {
1637 outputStream.seekp(level2Element->
dataOffset());
1638 outputStream.write(buff, sizeLength);
1666 outputStream.write(buff, sizeLength);
1667 for(
auto &maker : tagMaker) {
1668 maker.make(outputStream);
1673 if(attachmentsSize) {
1676 outputStream.write(buff, sizeLength);
1677 for(
auto &maker : attachmentMaker) {
1678 maker.make(outputStream);
1695 currentPosition += level0Element->
totalSize();
1701 if(rewriteRequired) {
1712 outputStream.close();
1713 outputStream.open(
fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary);
1716 const auto newSize =
static_cast<uint64
>(outputStream.tellp());
1720 outputStream.close();
1722 if(truncate(
fileInfo().path().c_str(), newSize) == 0) {
1728 outputStream.open(
fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary);
1743 if(!crc32Offsets.empty()) {
1745 for(
const auto &crc32Offset : crc32Offsets) {
1746 outputStream.seekg(get<0>(crc32Offset) + 6);
1747 outputStream.seekp(get<0>(crc32Offset) + 2);
1748 writer().writeUInt32LE(
reader().readCrc32(get<1>(crc32Offset) - 6));
1755 outputStream.flush();
Contains utility classes helping to read and write streams.