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/io/catchiofailure.h> 16 #include <c++utilities/misc/memory.h> 21 #include <initializer_list> 22 #include <unordered_set> 32 constexpr
const char appInfo[] = APP_NAME
" v" APP_VERSION;
41 uint64 MatroskaContainer::m_maxFullParseSize = 0x3200000;
46 MatroskaContainer::MatroskaContainer(
MediaFileInfo &fileInfo, uint64 startOffset) :
72 m_tracksElements.clear();
73 m_segmentInfoElements.clear();
74 m_tagsElements.clear();
75 m_chaptersElements.clear();
76 m_attachmentsElements.clear();
78 m_editionEntries.clear();
79 m_attachments.clear();
89 static const string context(
"validating Matroska file index (cues)");
90 bool cuesElementsFound =
false;
92 unordered_set<int> ids;
93 bool cueTimeFound =
false, cueTrackPositionsFound =
false;
94 unique_ptr<EbmlElement> clusterElement;
95 uint64 pos, prevClusterSize = 0, currentOffset = 0;
98 segmentElement->parse();
100 for(
EbmlElement *segmentChildElement = segmentElement->
firstChild(); segmentChildElement; segmentChildElement = segmentChildElement->nextSibling()) {
101 segmentChildElement->parse();
102 switch(segmentChildElement->id()) {
107 cuesElementsFound =
true;
109 for(
EbmlElement *cuePointElement = segmentChildElement->
firstChild(); cuePointElement; cuePointElement = cuePointElement->nextSibling()) {
110 cuePointElement->parse();
111 cueTimeFound = cueTrackPositionsFound =
false;
112 switch(cuePointElement->id()) {
118 for(
EbmlElement *cuePointChildElement = cuePointElement->
firstChild(); cuePointChildElement; cuePointChildElement = cuePointChildElement->nextSibling()) {
119 cuePointChildElement->parse();
120 switch(cuePointChildElement->id()) {
130 cueTrackPositionsFound =
true;
132 clusterElement.reset();
133 for(
EbmlElement *subElement = cuePointChildElement->
firstChild(); subElement; subElement = subElement->nextSibling()) {
135 switch(subElement->id()) {
143 if(ids.count(subElement->id())) {
146 ids.insert(subElement->id());
156 switch(subElement->id()) {
163 clusterElement = make_unique<EbmlElement>(*
this, segmentElement->dataOffset() + subElement->readUInteger() - currentOffset);
165 clusterElement->parse();
167 addNotification(
NotificationType::Critical,
"\"CueClusterPosition\" element at " + numberToString(subElement->startOffset()) +
" does not point to \"Cluster\"-element (points to " + numberToString(clusterElement->startOffset()) +
").", context);
175 pos = subElement->readUInteger();
193 if(!clusterElement) {
198 EbmlElement referenceElement(*
this, clusterElement->dataOffset() + pos);
200 referenceElement.
parse();
201 switch(referenceElement.id()) {
207 addNotification(
NotificationType::Critical,
"\"CueRelativePosition\" element does not point to \"Block\"-, \"BlockGroup\", or \"SimpleBlock\"-element (points to " + numberToString(referenceElement.startOffset()) +
").", context);
226 if(!cueTrackPositionsFound) {
237 for(
EbmlElement *clusterElementChild = segmentChildElement->
firstChild(); clusterElementChild; clusterElementChild = clusterElementChild->nextSibling()) {
238 clusterElementChild->parse();
239 switch(clusterElementChild->id()) {
245 if((pos = clusterElementChild->readUInteger()) > 0 && (segmentChildElement->startOffset() - segmentElement->dataOffset() + currentOffset) != pos) {
246 addNotification(
NotificationType::Critical,
"\"Position\"-element at " + numberToString(clusterElementChild->startOffset()) +
" points to " + numberToString(pos) +
" which is not the offset of the containing \"Cluster\"-element.", context);
251 if(clusterElementChild->readUInteger() != prevClusterSize) {
259 prevClusterSize = segmentChildElement->totalSize();
265 currentOffset += segmentElement->totalSize();
269 if(!cuesElementsFound) {
287 return find_if(elements.cbegin(), elements.cend(), std::bind(
sameOffset, offset, _1)) == elements.cend();
292 for(
const auto &entry : m_editionEntries) {
293 const auto &chapters = entry->chapters();
294 if(index < chapters.size()) {
295 return chapters[index].get();
297 index -= chapters.size();
306 for(
const auto &entry : m_editionEntries) {
307 count += entry->chapters().size();
315 srand(time(
nullptr));
319 attachmentId = rand();
324 goto generateRandomId;
329 m_attachments.emplace_back(make_unique<MatroskaAttachment>());
343 for(
const EbmlElement *childElement = segmentElement->
firstChild(); childElement; childElement = childElement->nextSibling()) {
344 if(childElement->id() == elementId) {
347 for(
const auto &seekInfo : m_seekInfos) {
348 for(
const auto &info : seekInfo->info()) {
349 if(info.first == elementId) {
374 static const string context(
"parsing header of Matroska container");
378 m_tracksElements.clear();
379 m_segmentInfoElements.clear();
380 m_tagsElements.clear();
383 uint64 currentOffset = 0;
384 vector<MatroskaSeekInfo>::size_type seekInfosIndex = 0;
386 for(
EbmlElement *topLevelElement =
m_firstElement.get(); topLevelElement; topLevelElement = topLevelElement->nextSibling()) {
388 topLevelElement->parse();
389 switch(topLevelElement->id()) {
391 for(
EbmlElement *subElement = topLevelElement->
firstChild(); subElement; subElement = subElement->nextSibling()) {
394 switch(subElement->id()) {
411 m_maxIdLength = subElement->readUInteger();
415 +
" bytes is not supported.", context);
420 m_maxSizeLength = subElement->readUInteger();
424 +
" bytes is not supported.", context);
439 for(
EbmlElement *subElement = topLevelElement->
firstChild(); subElement; subElement = subElement->nextSibling()) {
442 switch(subElement->id()) {
444 m_seekInfos.emplace_back(make_unique<MatroskaSeekInfo>());
445 m_seekInfos.back()->parse(subElement);
450 m_tracksElements.push_back(subElement);
454 if(
excludesOffset(m_segmentInfoElements, subElement->startOffset())) {
455 m_segmentInfoElements.push_back(subElement);
460 m_tagsElements.push_back(subElement);
464 if(
excludesOffset(m_chaptersElements, subElement->startOffset())) {
465 m_chaptersElements.push_back(subElement);
469 if(
excludesOffset(m_attachmentsElements, subElement->startOffset())) {
470 m_attachmentsElements.push_back(subElement);
476 for(
auto i = m_seekInfos.cbegin() + seekInfosIndex, end = m_seekInfos.cend(); i != end; ++i, ++seekInfosIndex) {
477 for(
const auto &infoPair : (*i)->info()) {
478 uint64 offset = currentOffset + topLevelElement->dataOffset() + infoPair.second;
482 auto element = make_unique<EbmlElement>(*
this, offset);
485 if(element->id() != infoPair.first) {
486 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);
488 switch(element->id()) {
531 if(((!m_tracksElements.empty() && !m_tagsElements.empty()) ||
fileInfo().
size() > m_maxFullParseSize) && !m_segmentInfoElements.empty()) {
543 currentOffset += topLevelElement->totalSize();
573 void MatroskaContainer::parseSegmentInfo()
575 if(m_segmentInfoElements.empty()) {
579 for(
EbmlElement *element : m_segmentInfoElements) {
582 float64 rawDuration = 0.0;
584 bool hasTitle =
false;
587 switch(subElement->
id()) {
606 if(rawDuration > 0.0 && timeScale > 0) {
607 m_duration += TimeSpan::fromSeconds(rawDuration * timeScale / 1000000000);
614 static const string context(
"parsing tags of Matroska container");
618 for(
EbmlElement *subElement = element->
firstChild(); subElement; subElement = subElement->nextSibling()) {
620 switch(subElement->id()) {
622 m_tags.emplace_back(make_unique<MatroskaTag>());
624 m_tags.back()->parse(*subElement);
648 static const string context(
"parsing tracks of Matroska container");
652 for(
EbmlElement *subElement = element->
firstChild(); subElement; subElement = subElement->nextSibling()) {
654 switch(subElement->id()) {
656 m_tracks.emplace_back(make_unique<MatroskaTrack>(*subElement));
682 static const string context(
"parsing editions/chapters of Matroska container");
686 for(
EbmlElement *subElement = element->
firstChild(); subElement; subElement = subElement->nextSibling()) {
688 switch(subElement->id()) {
690 m_editionEntries.emplace_back(make_unique<MatroskaEditionEntry>(subElement));
692 m_editionEntries.back()->parseNested();
694 m_editionEntries.pop_back();
716 static const string context(
"parsing attachments of Matroska container");
717 for(
EbmlElement *element : m_attachmentsElements) {
720 for(
EbmlElement *subElement = element->
firstChild(); subElement; subElement = subElement->nextSibling()) {
722 switch(subElement->id()) {
724 m_attachments.emplace_back(make_unique<MatroskaAttachment>());
726 m_attachments.back()->parse(subElement);
728 m_attachments.pop_back();
753 cuesElement(nullptr),
755 firstClusterElement(nullptr),
759 sizeDenotationLength(0),
799 static const string context(
"making Matroska container");
817 vector<MatroskaTagMaker> tagMaker;
818 uint64 tagElementsSize = 0;
820 vector<MatroskaAttachmentMaker> attachmentMaker;
821 uint64 attachedFileElementsSize = 0;
822 uint64 attachmentsSize;
826 unsigned int segmentIndex = 0;
828 vector<SegmentData> segmentData;
834 uint64 currentPosition = 0;
836 vector<tuple<uint64, uint64> > crc32Offsets;
840 uint64 clusterSize, clusterReadSize, clusterReadOffset;
852 unsigned int lastSegmentIndex =
static_cast<unsigned int>(-1);
860 uint64 ebmlHeaderDataSize = 2 * 7;
877 if(tagMaker.back().requiredSize() > 3) {
879 tagElementsSize += tagMaker.back().requiredSize();
894 if(attachmentMaker.back().requiredSize() > 3) {
896 attachedFileElementsSize += attachmentMaker.back().requiredSize();
910 for(
bool firstClusterFound =
false, firstTagFound =
false; level0Element; level0Element = level0Element->
nextSibling()) {
911 level0Element->
parse();
912 switch(level0Element->
id()) {
915 for(level1Element = level0Element->
firstChild(); level1Element && !firstClusterFound && !firstTagFound; level1Element = level1Element->
nextSibling()) {
916 level1Element->
parse();
917 switch(level1Element->
id()) {
920 firstTagFound =
true;
923 firstClusterFound =
true;
928 }
else if(firstClusterFound) {
935 segmentData.resize(lastSegmentIndex + 1);
949 calculateSegmentData:
952 uint64 currentOffset = ebmlHeaderSize;
954 uint64 readOffset = 0;
959 if(rewriteRequired) {
971 for(level0Element =
firstElement(), currentPosition = newPadding = segmentIndex = 0; level0Element; level0Element = level0Element->
nextSibling()) {
972 switch(level0Element->
id()) {
1008 newCuesPos = currentCuesPos;
1021 calculateSegmentSize:
1033 goto calculateSegmentSize;
1039 if(segmentIndex <
m_titles.size()) {
1041 if(!
title.empty()) {
1046 for(level2Element = level1Element->
firstChild(); level2Element; level2Element = level2Element->
nextSibling()) {
1047 level2Element->
parse();
1048 switch(level2Element->
id()) {
1067 for(level1Element = level0Element->
childById(
id), index = 0; level1Element; level1Element = level1Element->
siblingById(
id), ++index) {
1070 goto calculateSegmentSize;
1087 goto calculateSegmentSize;
1094 if(attachmentsSize) {
1097 goto calculateSegmentSize;
1111 goto calculateSegmentSize;
1120 if(!rewriteRequired) {
1129 if(totalOffset <= segment.firstClusterElement->
startOffset()) {
1143 nonRewriteCalculations:
1147 goto calculateSegmentSize;
1155 goto addCuesElementSize;
1164 goto calculateSegmentSize;
1176 goto calculateSegmentSize;
1183 if(attachmentsSize) {
1186 goto calculateSegmentSize;
1200 goto nonRewriteCalculations;
1203 totalOffset = currentOffset + 4 + sizeLength + offset;
1208 if(totalOffset <= segment.firstClusterElement->
startOffset()) {
1214 rewriteRequired =
true;
1217 rewriteRequired =
true;
1220 rewriteRequired =
true;
1232 if(rewriteRequired) {
1236 rewriteRequired =
false;
1240 rewriteRequired =
false;
1243 goto calculateSegmentData;
1261 goto addCuesElementSize;
1264 goto calculateSegmentSize;
1267 clusterSize = clusterReadSize = 0;
1268 for(level2Element = level1Element->
firstChild(); level2Element; level2Element = level2Element->
nextSibling()) {
1269 level2Element->
parse();
1272 goto addCuesElementSize;
1274 switch(level2Element->
id()) {
1282 clusterSize += level2Element->
totalSize();
1284 clusterReadSize += level2Element->
totalSize();
1296 goto calculateSegmentSize;
1311 goto calculateSegmentSize;
1318 if(attachmentsSize) {
1321 goto calculateSegmentSize;
1339 readOffset += level0Element->
totalSize();
1346 currentOffset += level0Element->
totalSize();
1347 readOffset += level0Element->
totalSize();
1351 if(!rewriteRequired) {
1353 if((rewriteRequired = (newPadding >
fileInfo().maxPadding() || newPadding <
fileInfo().minPadding()))) {
1355 goto calculateSegmentData;
1363 const char *what = catchIoFailure();
1365 throwIoFailure(what);
1379 NativeFileStream backupStream;
1380 BinaryWriter outputWriter(&outputStream);
1383 if(rewriteRequired) {
1384 if(
fileInfo().saveFilePath().empty()) {
1389 outputStream.open(
fileInfo().path(), ios_base::out | ios_base::binary | ios_base::trunc);
1391 const char *what = catchIoFailure();
1393 throwIoFailure(what);
1398 backupStream.exceptions(ios_base::badbit | ios_base::failbit);
1399 backupStream.open(
fileInfo().path(), ios_base::in | ios_base::binary);
1401 outputStream.open(
fileInfo().saveFilePath(), ios_base::out | ios_base::binary | ios_base::trunc);
1403 const char *what = catchIoFailure();
1405 throwIoFailure(what);
1416 for(
auto &maker : attachmentMaker) {
1417 maker.bufferCurrentAttachments();
1423 outputStream.open(
fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary);
1425 const char *what = catchIoFailure();
1427 throwIoFailure(what);
1437 outputStream.write(buff, sizeLength);
1447 for(level0Element =
firstElement(), segmentIndex = 0, currentPosition = 0; level0Element; level0Element = level0Element->
nextSibling()) {
1450 switch(level0Element->
id()) {
1468 outputStream.write(buff, sizeLength);
1477 crc32Offsets.emplace_back(outputStream.tellp(), segment.
totalDataSize);
1478 outputStream.write(buff, 6);
1491 outputStream.write(buff, sizeLength);
1493 for(level2Element = level1Element->
firstChild(); level2Element; level2Element = level2Element->
nextSibling()) {
1494 switch(level2Element->
id()) {
1508 if(segmentIndex <
m_titles.size()) {
1510 if(!
title.empty()) {
1521 for(level1Element = level0Element->
childById(
id); level1Element; level1Element = level1Element->
siblingById(
id)) {
1533 outputStream.write(buff, sizeLength);
1534 for(
auto &maker : tagMaker) {
1535 maker.make(outputStream);
1540 if(attachmentsSize) {
1543 outputStream.write(buff, sizeLength);
1544 for(
auto &maker : attachmentMaker) {
1545 maker.make(outputStream);
1569 *buff = (voidLength = segment.
newPadding - 2) | 0x80;
1572 BE::getBytes(static_cast<uint64>((voidLength = segment.
newPadding - 9) | 0x100000000000000), buff);
1576 outputStream.write(buff, sizeLength);
1578 for(; voidLength; --voidLength) {
1579 outputStream.put(0);
1585 if(rewriteRequired) {
1591 updateStatus(
"Writing clusters ...", static_cast<double>(static_cast<uint64>(outputStream.tellp()) - offset) / segment.
totalDataSize);
1593 for(
auto clusterSizesIterator = segment.
clusterSizes.cbegin();
1596 clusterSize = currentPosition + (
static_cast<uint64
>(outputStream.tellp()) - offset);
1600 outputStream.write(buff, sizeLength);
1602 for(level2Element = level1Element->
firstChild(); level2Element; level2Element = level2Element->
nextSibling()) {
1603 switch(level2Element->
id()) {
1623 for(; level1Element; level1Element = level1Element->
nextSibling()) {
1624 for(level2Element = level1Element->
firstChild(); level2Element; level2Element = level2Element->
nextSibling()) {
1625 switch(level2Element->
id()) {
1630 if(level2Element->
dataSize() < sizeLength) {
1636 outputStream.seekp(level2Element->
dataOffset());
1637 outputStream.write(buff, sizeLength);
1665 outputStream.write(buff, sizeLength);
1666 for(
auto &maker : tagMaker) {
1667 maker.make(outputStream);
1672 if(attachmentsSize) {
1675 outputStream.write(buff, sizeLength);
1676 for(
auto &maker : attachmentMaker) {
1677 maker.make(outputStream);
1694 currentPosition += level0Element->
totalSize();
1700 if(rewriteRequired) {
1711 outputStream.close();
1712 outputStream.open(
fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary);
1715 const auto newSize =
static_cast<uint64
>(outputStream.tellp());
1719 outputStream.close();
1721 if(truncate(
fileInfo().path().c_str(), newSize) == 0) {
1727 outputStream.open(
fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary);
1742 if(!crc32Offsets.empty()) {
1744 for(
const auto &crc32Offset : crc32Offsets) {
1745 outputStream.seekg(get<0>(crc32Offset) + 6);
1746 outputStream.seekp(get<0>(crc32Offset) + 2);
1747 writer().writeUInt32LE(
reader().readCrc32(get<1>(crc32Offset) - 6));
1754 outputStream.flush();
Contains utility classes helping to read and write streams.