2015-09-06 19:57:33 +02:00
|
|
|
#include "./matroskacontainer.h"
|
|
|
|
#include "./ebmlid.h"
|
|
|
|
#include "./matroskacues.h"
|
|
|
|
#include "./matroskaeditionentry.h"
|
2018-03-07 01:17:50 +01:00
|
|
|
#include "./matroskaid.h"
|
2015-09-06 19:57:33 +02:00
|
|
|
#include "./matroskaseekinfo.h"
|
|
|
|
|
|
|
|
#include "../backuphelper.h"
|
2018-03-07 01:17:50 +01:00
|
|
|
#include "../exceptions.h"
|
|
|
|
#include "../mediafileinfo.h"
|
2015-04-22 19:22:01 +02:00
|
|
|
|
2015-12-08 08:38:26 +01:00
|
|
|
#include "resources/config.h"
|
2015-12-05 22:53:30 +01:00
|
|
|
|
2017-01-27 18:59:22 +01:00
|
|
|
#include <c++utilities/conversion/stringbuilder.h>
|
2018-03-07 01:17:50 +01:00
|
|
|
#include <c++utilities/conversion/stringconversion.h>
|
2015-04-22 19:22:01 +02:00
|
|
|
|
2015-12-21 00:04:56 +01:00
|
|
|
#include <unistd.h>
|
|
|
|
|
2018-03-07 01:17:50 +01:00
|
|
|
#include <chrono>
|
2015-04-22 19:22:01 +02:00
|
|
|
#include <functional>
|
|
|
|
#include <initializer_list>
|
2017-10-09 20:59:43 +02:00
|
|
|
#include <limits>
|
2018-03-07 01:17:50 +01:00
|
|
|
#include <memory>
|
2018-02-05 00:40:11 +01:00
|
|
|
#include <random>
|
2018-03-07 01:17:50 +01:00
|
|
|
#include <unordered_set>
|
2015-04-22 19:22:01 +02:00
|
|
|
|
|
|
|
using namespace std;
|
|
|
|
using namespace std::placeholders;
|
2019-06-10 22:49:11 +02:00
|
|
|
using namespace CppUtilities;
|
2015-04-22 19:22:01 +02:00
|
|
|
|
2018-03-06 23:09:15 +01:00
|
|
|
namespace TagParser {
|
2015-04-22 19:22:01 +02:00
|
|
|
|
|
|
|
/*!
|
2018-06-03 20:38:32 +02:00
|
|
|
* \class TagParser::MatroskaContainer
|
2015-04-22 19:22:01 +02:00
|
|
|
* \brief Implementation of GenericContainer<MediaFileInfo, MatroskaTag, MatroskaTrack, EbmlElement>.
|
|
|
|
*/
|
|
|
|
|
2021-08-25 19:04:51 +02:00
|
|
|
std::uint64_t MatroskaContainer::m_maxFullParseSize = 0x3200000; // FIXME v11: move to MediaFileInfo
|
2015-04-22 19:22:01 +02:00
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Constructs a new container for the specified \a fileInfo at the specified \a startOffset.
|
|
|
|
*/
|
2019-03-13 19:06:42 +01:00
|
|
|
MatroskaContainer::MatroskaContainer(MediaFileInfo &fileInfo, std::uint64_t startOffset)
|
2018-03-07 01:17:50 +01:00
|
|
|
: GenericContainer<MediaFileInfo, MatroskaTag, MatroskaTrack, EbmlElement>(fileInfo, startOffset)
|
|
|
|
, m_maxIdLength(4)
|
|
|
|
, m_maxSizeLength(8)
|
|
|
|
, m_segmentCount(0)
|
2015-04-22 19:22:01 +02:00
|
|
|
{
|
|
|
|
m_version = 1;
|
|
|
|
m_readVersion = 1;
|
|
|
|
m_doctype = "matroska";
|
|
|
|
m_doctypeVersion = 1;
|
|
|
|
m_doctypeReadVersion = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
MatroskaContainer::~MatroskaContainer()
|
2018-03-07 01:17:50 +01:00
|
|
|
{
|
|
|
|
}
|
2015-04-22 19:22:01 +02:00
|
|
|
|
2015-10-06 22:39:18 +02:00
|
|
|
void MatroskaContainer::reset()
|
|
|
|
{
|
|
|
|
GenericContainer<MediaFileInfo, MatroskaTag, MatroskaTrack, EbmlElement>::reset();
|
|
|
|
m_maxIdLength = 4;
|
|
|
|
m_maxSizeLength = 8;
|
|
|
|
m_version = 1;
|
|
|
|
m_readVersion = 1;
|
|
|
|
m_doctype = "matroska";
|
|
|
|
m_doctypeVersion = 1;
|
|
|
|
m_doctypeReadVersion = 1;
|
|
|
|
m_tracksElements.clear();
|
|
|
|
m_segmentInfoElements.clear();
|
|
|
|
m_tagsElements.clear();
|
|
|
|
m_chaptersElements.clear();
|
|
|
|
m_attachmentsElements.clear();
|
|
|
|
m_seekInfos.clear();
|
|
|
|
m_editionEntries.clear();
|
|
|
|
m_attachments.clear();
|
2015-12-27 19:51:57 +01:00
|
|
|
m_segmentCount = 0;
|
2015-10-06 22:39:18 +02:00
|
|
|
}
|
|
|
|
|
2015-04-22 19:22:01 +02:00
|
|
|
/*!
|
|
|
|
* \brief Validates the file index (cue entries).
|
|
|
|
* \remarks Checks only for cluster positions and missing, unknown or surplus elements.
|
|
|
|
*/
|
2021-02-04 23:21:50 +01:00
|
|
|
void MatroskaContainer::validateIndex(Diagnostics &diag, AbortableProgressFeedback &progress)
|
2015-04-22 19:22:01 +02:00
|
|
|
{
|
|
|
|
static const string context("validating Matroska file index (cues)");
|
|
|
|
bool cuesElementsFound = false;
|
2018-03-07 01:17:50 +01:00
|
|
|
if (m_firstElement) {
|
2017-03-07 17:16:17 +01:00
|
|
|
unordered_set<EbmlElement::IdentifierType> ids;
|
2015-04-22 19:22:01 +02:00
|
|
|
bool cueTimeFound = false, cueTrackPositionsFound = false;
|
|
|
|
unique_ptr<EbmlElement> clusterElement;
|
2019-03-13 19:06:42 +01:00
|
|
|
std::uint64_t pos, prevClusterSize = 0, currentOffset = 0;
|
2021-07-02 03:00:50 +02:00
|
|
|
// iterate through all segments
|
2018-03-07 01:17:50 +01:00
|
|
|
for (EbmlElement *segmentElement = m_firstElement->siblingById(MatroskaIds::Segment, diag); segmentElement;
|
|
|
|
segmentElement = segmentElement->siblingById(MatroskaIds::Segment, diag)) {
|
2018-03-05 17:49:29 +01:00
|
|
|
segmentElement->parse(diag);
|
2021-07-02 03:00:50 +02:00
|
|
|
// iterate through all child elements of the segment (only "Cues"- and "Cluster"-elements are relevant for this method)
|
2018-03-07 01:17:50 +01:00
|
|
|
for (EbmlElement *segmentChildElement = segmentElement->firstChild(); segmentChildElement;
|
|
|
|
segmentChildElement = segmentChildElement->nextSibling()) {
|
2021-02-04 23:21:50 +01:00
|
|
|
progress.stopIfAborted();
|
2018-03-05 17:49:29 +01:00
|
|
|
segmentChildElement->parse(diag);
|
2018-03-07 01:17:50 +01:00
|
|
|
switch (segmentChildElement->id()) {
|
2015-04-22 19:22:01 +02:00
|
|
|
case EbmlIds::Void:
|
|
|
|
case EbmlIds::Crc32:
|
|
|
|
break;
|
|
|
|
case MatroskaIds::Cues:
|
|
|
|
cuesElementsFound = true;
|
2019-12-30 22:54:11 +01:00
|
|
|
// parse children of "Cues"-element ("CuePoint"-elements)
|
2018-03-07 01:17:50 +01:00
|
|
|
for (EbmlElement *cuePointElement = segmentChildElement->firstChild(); cuePointElement;
|
|
|
|
cuePointElement = cuePointElement->nextSibling()) {
|
2021-02-04 23:21:50 +01:00
|
|
|
progress.stopIfAborted();
|
2018-03-05 17:49:29 +01:00
|
|
|
cuePointElement->parse(diag);
|
2015-04-22 19:22:01 +02:00
|
|
|
cueTimeFound = cueTrackPositionsFound = false; // to validate quantity of these elements
|
2018-03-07 01:17:50 +01:00
|
|
|
switch (cuePointElement->id()) {
|
2015-04-22 19:22:01 +02:00
|
|
|
case EbmlIds::Void:
|
|
|
|
case EbmlIds::Crc32:
|
|
|
|
break;
|
|
|
|
case MatroskaIds::CuePoint:
|
2019-12-30 22:54:11 +01:00
|
|
|
// parse children of "CuePoint"-element
|
2018-03-07 01:17:50 +01:00
|
|
|
for (EbmlElement *cuePointChildElement = cuePointElement->firstChild(); cuePointChildElement;
|
|
|
|
cuePointChildElement = cuePointChildElement->nextSibling()) {
|
2018-03-05 17:49:29 +01:00
|
|
|
cuePointChildElement->parse(diag);
|
2018-03-07 01:17:50 +01:00
|
|
|
switch (cuePointChildElement->id()) {
|
2015-04-22 19:22:01 +02:00
|
|
|
case MatroskaIds::CueTime:
|
|
|
|
// validate uniqueness
|
2018-03-07 01:17:50 +01:00
|
|
|
if (cueTimeFound) {
|
|
|
|
diag.emplace_back(
|
|
|
|
DiagLevel::Warning, "\"CuePoint\"-element contains multiple \"CueTime\" elements.", context);
|
2015-04-22 19:22:01 +02:00
|
|
|
} else {
|
|
|
|
cueTimeFound = true;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case MatroskaIds::CueTrackPositions:
|
|
|
|
cueTrackPositionsFound = true;
|
|
|
|
ids.clear();
|
|
|
|
clusterElement.reset();
|
2018-03-07 01:17:50 +01:00
|
|
|
for (EbmlElement *subElement = cuePointChildElement->firstChild(); subElement;
|
|
|
|
subElement = subElement->nextSibling()) {
|
2018-03-05 17:49:29 +01:00
|
|
|
subElement->parse(diag);
|
2018-03-07 01:17:50 +01:00
|
|
|
switch (subElement->id()) {
|
2015-04-22 19:22:01 +02:00
|
|
|
case MatroskaIds::CueTrack:
|
|
|
|
case MatroskaIds::CueClusterPosition:
|
|
|
|
case MatroskaIds::CueRelativePosition:
|
|
|
|
case MatroskaIds::CueDuration:
|
|
|
|
case MatroskaIds::CueBlockNumber:
|
|
|
|
case MatroskaIds::CueCodecState:
|
|
|
|
// validate uniqueness
|
2018-03-07 01:17:50 +01:00
|
|
|
if (ids.count(subElement->id())) {
|
|
|
|
diag.emplace_back(DiagLevel::Warning,
|
|
|
|
"\"CueTrackPositions\"-element contains multiple \"" % subElement->idToString() + "\" elements.",
|
|
|
|
context);
|
2015-04-22 19:22:01 +02:00
|
|
|
} else {
|
|
|
|
ids.insert(subElement->id());
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case EbmlIds::Crc32:
|
|
|
|
case EbmlIds::Void:
|
|
|
|
case MatroskaIds::CueReference:
|
|
|
|
break;
|
|
|
|
default:
|
2018-03-07 01:17:50 +01:00
|
|
|
diag.emplace_back(DiagLevel::Warning,
|
|
|
|
"\"CueTrackPositions\"-element contains unknown element \"" % subElement->idToString() + "\".",
|
|
|
|
context);
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
2018-03-07 01:17:50 +01:00
|
|
|
switch (subElement->id()) {
|
2015-04-22 19:22:01 +02:00
|
|
|
case EbmlIds::Void:
|
|
|
|
case EbmlIds::Crc32:
|
|
|
|
case MatroskaIds::CueTrack:
|
|
|
|
break;
|
|
|
|
case MatroskaIds::CueClusterPosition:
|
|
|
|
// validate "Cluster" position denoted by "CueClusterPosition"-element
|
2018-03-07 01:17:50 +01:00
|
|
|
clusterElement = make_unique<EbmlElement>(
|
|
|
|
*this, segmentElement->dataOffset() + subElement->readUInteger() - currentOffset);
|
2015-04-22 19:22:01 +02:00
|
|
|
try {
|
2018-03-05 17:49:29 +01:00
|
|
|
clusterElement->parse(diag);
|
2018-03-07 01:17:50 +01:00
|
|
|
if (clusterElement->id() != MatroskaIds::Cluster) {
|
|
|
|
diag.emplace_back(DiagLevel::Critical,
|
|
|
|
"\"CueClusterPosition\" element at " % numberToString(subElement->startOffset())
|
|
|
|
+ " does not point to \"Cluster\"-element (points to "
|
|
|
|
+ numberToString(clusterElement->startOffset()) + ").",
|
|
|
|
context);
|
2018-03-05 17:49:29 +01:00
|
|
|
}
|
2018-03-07 01:17:50 +01:00
|
|
|
} catch (const Failure &) {
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case MatroskaIds::CueRelativePosition:
|
|
|
|
// read "Block" position denoted by "CueRelativePosition"-element (validate later since the "Cluster"-element is needed to validate)
|
|
|
|
pos = subElement->readUInteger();
|
|
|
|
break;
|
|
|
|
case MatroskaIds::CueDuration:
|
|
|
|
break;
|
|
|
|
case MatroskaIds::CueBlockNumber:
|
|
|
|
break;
|
|
|
|
case MatroskaIds::CueCodecState:
|
|
|
|
break;
|
|
|
|
case MatroskaIds::CueReference:
|
|
|
|
break;
|
2018-03-07 01:17:50 +01:00
|
|
|
default:;
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
// validate existence of mandatory elements
|
2018-03-07 01:17:50 +01:00
|
|
|
if (!ids.count(MatroskaIds::CueTrack)) {
|
|
|
|
diag.emplace_back(DiagLevel::Warning,
|
|
|
|
"\"CueTrackPositions\"-element does not contain mandatory element \"CueTrack\".", context);
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
2018-03-07 01:17:50 +01:00
|
|
|
if (!clusterElement) {
|
|
|
|
diag.emplace_back(DiagLevel::Warning,
|
|
|
|
"\"CueTrackPositions\"-element does not contain mandatory element \"CueClusterPosition\".", context);
|
|
|
|
} else if (ids.count(MatroskaIds::CueRelativePosition)) {
|
2018-03-05 17:49:29 +01:00
|
|
|
// validate "Block" position denoted by "CueRelativePosition"-element
|
|
|
|
EbmlElement referenceElement(*this, clusterElement->dataOffset() + pos);
|
|
|
|
try {
|
|
|
|
referenceElement.parse(diag);
|
2018-03-07 01:17:50 +01:00
|
|
|
switch (referenceElement.id()) {
|
2018-03-05 17:49:29 +01:00
|
|
|
case MatroskaIds::SimpleBlock:
|
|
|
|
case MatroskaIds::Block:
|
|
|
|
case MatroskaIds::BlockGroup:
|
|
|
|
break;
|
|
|
|
default:
|
2018-03-07 01:17:50 +01:00
|
|
|
diag.emplace_back(DiagLevel::Critical,
|
|
|
|
"\"CueRelativePosition\" element does not point to \"Block\"-, \"BlockGroup\", or "
|
|
|
|
"\"SimpleBlock\"-element (points to "
|
|
|
|
% numberToString(referenceElement.startOffset())
|
|
|
|
+ ").",
|
|
|
|
context);
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
2018-03-07 01:17:50 +01:00
|
|
|
} catch (const Failure &) {
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case EbmlIds::Crc32:
|
|
|
|
case EbmlIds::Void:
|
|
|
|
break;
|
|
|
|
default:
|
2018-03-07 01:17:50 +01:00
|
|
|
diag.emplace_back(DiagLevel::Warning,
|
|
|
|
"\"CuePoint\"-element contains unknown element \"" % cuePointElement->idToString() + "\".", context);
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
// validate existence of mandatory elements
|
2018-03-07 01:17:50 +01:00
|
|
|
if (!cueTimeFound) {
|
|
|
|
diag.emplace_back(
|
|
|
|
DiagLevel::Warning, "\"CuePoint\"-element does not contain mandatory element \"CueTime\".", context);
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
2018-03-07 01:17:50 +01:00
|
|
|
if (!cueTrackPositionsFound) {
|
|
|
|
diag.emplace_back(
|
|
|
|
DiagLevel::Warning, "\"CuePoint\"-element does not contain mandatory element \"CueClusterPosition\".", context);
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
|
|
|
break;
|
2018-03-07 01:17:50 +01:00
|
|
|
default:;
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case MatroskaIds::Cluster:
|
2019-12-30 22:54:11 +01:00
|
|
|
// parse children of "Cluster"-element
|
2018-03-07 01:17:50 +01:00
|
|
|
for (EbmlElement *clusterElementChild = segmentChildElement->firstChild(); clusterElementChild;
|
|
|
|
clusterElementChild = clusterElementChild->nextSibling()) {
|
2018-03-05 17:49:29 +01:00
|
|
|
clusterElementChild->parse(diag);
|
2018-03-07 01:17:50 +01:00
|
|
|
switch (clusterElementChild->id()) {
|
2015-04-22 19:22:01 +02:00
|
|
|
case EbmlIds::Void:
|
|
|
|
case EbmlIds::Crc32:
|
|
|
|
break;
|
|
|
|
case MatroskaIds::Position:
|
|
|
|
// validate position
|
2018-03-07 01:17:50 +01:00
|
|
|
if ((pos = clusterElementChild->readUInteger()) > 0
|
|
|
|
&& (segmentChildElement->startOffset() - segmentElement->dataOffset() + currentOffset) != pos) {
|
|
|
|
diag.emplace_back(DiagLevel::Critical,
|
|
|
|
argsToString("\"Position\"-element at ", clusterElementChild->startOffset(), " points to ", pos,
|
|
|
|
" which is not the offset of the containing \"Cluster\"-element."),
|
|
|
|
context);
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case MatroskaIds::PrevSize:
|
|
|
|
// validate prev size
|
2018-03-07 01:17:50 +01:00
|
|
|
if ((pos = clusterElementChild->readUInteger()) != prevClusterSize) {
|
|
|
|
diag.emplace_back(DiagLevel::Critical,
|
|
|
|
argsToString("\"PrevSize\"-element at ", clusterElementChild->startOffset(), " should be ", prevClusterSize,
|
|
|
|
" but is ", pos, "."),
|
|
|
|
context);
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
|
|
|
break;
|
2018-03-07 01:17:50 +01:00
|
|
|
default:;
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
prevClusterSize = segmentChildElement->totalSize();
|
|
|
|
break;
|
2018-03-07 01:17:50 +01:00
|
|
|
default:;
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
currentOffset += segmentElement->totalSize();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// add a warning when no index could be found
|
2018-03-07 01:17:50 +01:00
|
|
|
if (!cuesElementsFound) {
|
2018-03-05 17:49:29 +01:00
|
|
|
diag.emplace_back(DiagLevel::Information, "No \"Cues\"-elements (index) found.", context);
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Returns an indication whether \a offset equals the start offset of \a element.
|
|
|
|
*/
|
2019-03-13 19:06:42 +01:00
|
|
|
bool sameOffset(std::uint64_t offset, const EbmlElement *element)
|
2018-03-07 01:17:50 +01:00
|
|
|
{
|
2015-04-22 19:22:01 +02:00
|
|
|
return element->startOffset() == offset;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Returns whether none of the specified \a elements have the specified \a offset.
|
2016-08-06 20:51:57 +02:00
|
|
|
* \remarks This method is used when gathering elements to avoid adding the same element twice.
|
2015-04-22 19:22:01 +02:00
|
|
|
*/
|
2019-03-13 19:06:42 +01:00
|
|
|
inline bool excludesOffset(const vector<EbmlElement *> &elements, std::uint64_t offset)
|
2015-04-22 19:22:01 +02:00
|
|
|
{
|
|
|
|
return find_if(elements.cbegin(), elements.cend(), std::bind(sameOffset, offset, _1)) == elements.cend();
|
|
|
|
}
|
|
|
|
|
|
|
|
MatroskaChapter *MatroskaContainer::chapter(std::size_t index)
|
|
|
|
{
|
2018-03-07 01:17:50 +01:00
|
|
|
for (const auto &entry : m_editionEntries) {
|
2016-08-06 20:51:57 +02:00
|
|
|
const auto &chapters = entry->chapters();
|
2018-03-07 01:17:50 +01:00
|
|
|
if (index < chapters.size()) {
|
2016-08-06 20:51:57 +02:00
|
|
|
return chapters[index].get();
|
|
|
|
} else {
|
|
|
|
index -= chapters.size();
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t MatroskaContainer::chapterCount() const
|
|
|
|
{
|
|
|
|
size_t count = 0;
|
2018-03-07 01:17:50 +01:00
|
|
|
for (const auto &entry : m_editionEntries) {
|
2015-04-22 19:22:01 +02:00
|
|
|
count += entry->chapters().size();
|
|
|
|
}
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
MatroskaAttachment *MatroskaContainer::createAttachment()
|
|
|
|
{
|
|
|
|
// generate unique ID
|
2018-03-07 01:17:50 +01:00
|
|
|
static const auto randomEngine(
|
|
|
|
default_random_engine(static_cast<default_random_engine::result_type>(chrono::system_clock::now().time_since_epoch().count())));
|
2019-03-13 19:06:42 +01:00
|
|
|
std::uint64_t attachmentId;
|
2018-02-05 00:40:11 +01:00
|
|
|
auto dice(bind(uniform_int_distribution<decltype(attachmentId)>(), randomEngine));
|
2019-03-13 19:06:42 +01:00
|
|
|
std::uint8_t tries = 0;
|
2015-11-26 14:40:35 +01:00
|
|
|
generateRandomId:
|
2018-02-05 00:40:11 +01:00
|
|
|
attachmentId = dice();
|
2018-03-07 01:17:50 +01:00
|
|
|
if (tries < 0xFF) {
|
|
|
|
for (const auto &attachment : m_attachments) {
|
|
|
|
if (attachmentId == attachment->id()) {
|
2015-04-22 19:22:01 +02:00
|
|
|
++tries;
|
|
|
|
goto generateRandomId;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// create new attachment, set ID
|
|
|
|
m_attachments.emplace_back(make_unique<MatroskaAttachment>());
|
|
|
|
auto &attachment = m_attachments.back();
|
|
|
|
attachment->setId(attachmentId);
|
|
|
|
return attachment.get();
|
|
|
|
}
|
|
|
|
|
2016-11-16 22:06:12 +01:00
|
|
|
/*!
|
|
|
|
* \brief Determines the position of the element with the specified \a elementId.
|
|
|
|
* \sa determineTagPosition() and determineIndexPosition()
|
|
|
|
*/
|
2019-03-13 19:06:42 +01:00
|
|
|
ElementPosition MatroskaContainer::determineElementPosition(std::uint64_t elementId, Diagnostics &diag) const
|
2016-11-16 22:06:12 +01:00
|
|
|
{
|
2018-03-07 01:17:50 +01:00
|
|
|
if (!m_firstElement || m_segmentCount != 1) {
|
2018-02-05 00:59:34 +01:00
|
|
|
return ElementPosition::Keep;
|
|
|
|
}
|
2018-03-07 00:30:08 +01:00
|
|
|
const auto *const segmentElement = m_firstElement->siblingByIdIncludingThis(MatroskaIds::Segment, diag);
|
2018-03-07 01:17:50 +01:00
|
|
|
if (!segmentElement) {
|
2018-02-05 00:59:34 +01:00
|
|
|
return ElementPosition::Keep;
|
|
|
|
}
|
2018-03-07 01:17:50 +01:00
|
|
|
for (const EbmlElement *childElement = segmentElement->firstChild(); childElement; childElement = childElement->nextSibling()) {
|
|
|
|
if (childElement->id() == elementId) {
|
2018-02-05 00:59:34 +01:00
|
|
|
return ElementPosition::BeforeData;
|
2018-03-07 01:17:50 +01:00
|
|
|
} else if (childElement->id() == MatroskaIds::Cluster) {
|
|
|
|
for (const auto &seekInfo : m_seekInfos) {
|
|
|
|
for (const auto &info : seekInfo->info()) {
|
|
|
|
if (info.first == elementId) {
|
2018-02-05 00:59:34 +01:00
|
|
|
return ElementPosition::AfterData;
|
2016-11-16 22:06:12 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-02-05 00:59:34 +01:00
|
|
|
return ElementPosition::Keep;
|
2016-11-16 22:06:12 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return ElementPosition::Keep;
|
|
|
|
}
|
|
|
|
|
2018-03-05 17:49:29 +01:00
|
|
|
ElementPosition MatroskaContainer::determineTagPosition(Diagnostics &diag) const
|
2016-11-15 22:48:38 +01:00
|
|
|
{
|
2018-03-05 17:49:29 +01:00
|
|
|
return determineElementPosition(MatroskaIds::Tags, diag);
|
2016-11-15 22:48:38 +01:00
|
|
|
}
|
|
|
|
|
2018-03-05 17:49:29 +01:00
|
|
|
ElementPosition MatroskaContainer::determineIndexPosition(Diagnostics &diag) const
|
2016-11-16 19:31:09 +01:00
|
|
|
{
|
2018-03-05 17:49:29 +01:00
|
|
|
return determineElementPosition(MatroskaIds::Cues, diag);
|
2016-11-16 19:31:09 +01:00
|
|
|
}
|
|
|
|
|
2021-02-04 23:21:50 +01:00
|
|
|
void MatroskaContainer::internalParseHeader(Diagnostics &diag, AbortableProgressFeedback &progress)
|
2015-04-22 19:22:01 +02:00
|
|
|
{
|
2021-02-04 23:21:50 +01:00
|
|
|
CPP_UTILITIES_UNUSED(progress)
|
|
|
|
|
2015-04-22 19:22:01 +02:00
|
|
|
static const string context("parsing header of Matroska container");
|
|
|
|
// reset old results
|
|
|
|
m_firstElement = make_unique<EbmlElement>(*this, startOffset());
|
|
|
|
m_additionalElements.clear();
|
|
|
|
m_tracksElements.clear();
|
|
|
|
m_segmentInfoElements.clear();
|
|
|
|
m_tagsElements.clear();
|
|
|
|
m_seekInfos.clear();
|
2015-12-27 19:51:57 +01:00
|
|
|
m_segmentCount = 0;
|
2019-03-13 19:06:42 +01:00
|
|
|
std::uint64_t currentOffset = 0;
|
2018-02-05 00:41:35 +01:00
|
|
|
vector<MatroskaSeekInfo>::difference_type seekInfosIndex = 0;
|
|
|
|
|
2015-04-22 19:22:01 +02:00
|
|
|
// loop through all top level elements
|
2018-03-07 01:17:50 +01:00
|
|
|
for (EbmlElement *topLevelElement = m_firstElement.get(); topLevelElement; topLevelElement = topLevelElement->nextSibling()) {
|
2015-11-26 14:40:35 +01:00
|
|
|
try {
|
2018-03-05 17:49:29 +01:00
|
|
|
topLevelElement->parse(diag);
|
2018-03-07 01:17:50 +01:00
|
|
|
switch (topLevelElement->id()) {
|
2016-03-13 22:00:23 +01:00
|
|
|
case EbmlIds::Header:
|
2018-03-07 01:17:50 +01:00
|
|
|
for (EbmlElement *subElement = topLevelElement->firstChild(); subElement; subElement = subElement->nextSibling()) {
|
2016-03-13 22:00:23 +01:00
|
|
|
try {
|
2018-03-05 17:49:29 +01:00
|
|
|
subElement->parse(diag);
|
2018-03-07 01:17:50 +01:00
|
|
|
switch (subElement->id()) {
|
2016-03-13 22:00:23 +01:00
|
|
|
case EbmlIds::Version:
|
|
|
|
m_version = subElement->readUInteger();
|
|
|
|
break;
|
|
|
|
case EbmlIds::ReadVersion:
|
|
|
|
m_readVersion = subElement->readUInteger();
|
|
|
|
break;
|
|
|
|
case EbmlIds::DocType:
|
|
|
|
m_doctype = subElement->readString();
|
|
|
|
break;
|
|
|
|
case EbmlIds::DocTypeVersion:
|
|
|
|
m_doctypeVersion = subElement->readUInteger();
|
|
|
|
break;
|
|
|
|
case EbmlIds::DocTypeReadVersion:
|
|
|
|
m_doctypeReadVersion = subElement->readUInteger();
|
|
|
|
break;
|
|
|
|
case EbmlIds::MaxIdLength:
|
|
|
|
m_maxIdLength = subElement->readUInteger();
|
2018-03-07 01:17:50 +01:00
|
|
|
if (m_maxIdLength > EbmlElement::maximumIdLengthSupported()) {
|
2018-03-05 17:49:29 +01:00
|
|
|
diag.emplace_back(DiagLevel::Critical,
|
2021-07-02 03:00:50 +02:00
|
|
|
argsToString("Maximum EBML element ID length greater than ", EbmlElement::maximumIdLengthSupported(),
|
2018-03-07 01:17:50 +01:00
|
|
|
" bytes is not supported."),
|
|
|
|
context);
|
2016-03-13 22:00:23 +01:00
|
|
|
throw InvalidDataException();
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case EbmlIds::MaxSizeLength:
|
|
|
|
m_maxSizeLength = subElement->readUInteger();
|
2018-03-07 01:17:50 +01:00
|
|
|
if (m_maxSizeLength > EbmlElement::maximumSizeLengthSupported()) {
|
2018-03-05 17:49:29 +01:00
|
|
|
diag.emplace_back(DiagLevel::Critical,
|
2021-07-02 03:00:50 +02:00
|
|
|
argsToString("Maximum EBML element size length greater than ", EbmlElement::maximumSizeLengthSupported(),
|
2018-03-07 01:17:50 +01:00
|
|
|
" bytes is not supported."),
|
|
|
|
context);
|
2016-03-13 22:00:23 +01:00
|
|
|
throw InvalidDataException();
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2018-03-07 01:17:50 +01:00
|
|
|
} catch (const Failure &) {
|
2019-12-30 22:54:11 +01:00
|
|
|
diag.emplace_back(DiagLevel::Critical, "Unable to parse all children of EBML header.", context);
|
2016-03-13 22:00:23 +01:00
|
|
|
break;
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
|
|
|
}
|
2016-03-13 22:00:23 +01:00
|
|
|
break;
|
|
|
|
case MatroskaIds::Segment:
|
|
|
|
++m_segmentCount;
|
2018-03-07 01:17:50 +01:00
|
|
|
for (EbmlElement *subElement = topLevelElement->firstChild(); subElement; subElement = subElement->nextSibling()) {
|
2016-03-13 22:00:23 +01:00
|
|
|
try {
|
2018-03-05 17:49:29 +01:00
|
|
|
subElement->parse(diag);
|
2018-03-07 01:17:50 +01:00
|
|
|
switch (subElement->id()) {
|
2016-03-13 22:00:23 +01:00
|
|
|
case MatroskaIds::SeekHead:
|
|
|
|
m_seekInfos.emplace_back(make_unique<MatroskaSeekInfo>());
|
2018-03-05 17:49:29 +01:00
|
|
|
m_seekInfos.back()->parse(subElement, diag);
|
2016-03-13 22:00:23 +01:00
|
|
|
break;
|
|
|
|
case MatroskaIds::Tracks:
|
2018-03-07 01:17:50 +01:00
|
|
|
if (excludesOffset(m_tracksElements, subElement->startOffset())) {
|
2016-03-13 22:00:23 +01:00
|
|
|
m_tracksElements.push_back(subElement);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case MatroskaIds::SegmentInfo:
|
2018-03-07 01:17:50 +01:00
|
|
|
if (excludesOffset(m_segmentInfoElements, subElement->startOffset())) {
|
2016-03-13 22:00:23 +01:00
|
|
|
m_segmentInfoElements.push_back(subElement);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case MatroskaIds::Tags:
|
2018-03-07 01:17:50 +01:00
|
|
|
if (excludesOffset(m_tagsElements, subElement->startOffset())) {
|
2016-03-13 22:00:23 +01:00
|
|
|
m_tagsElements.push_back(subElement);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case MatroskaIds::Chapters:
|
2018-03-07 01:17:50 +01:00
|
|
|
if (excludesOffset(m_chaptersElements, subElement->startOffset())) {
|
2016-03-13 22:00:23 +01:00
|
|
|
m_chaptersElements.push_back(subElement);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case MatroskaIds::Attachments:
|
2018-03-07 01:17:50 +01:00
|
|
|
if (excludesOffset(m_attachmentsElements, subElement->startOffset())) {
|
2016-03-13 22:00:23 +01:00
|
|
|
m_attachmentsElements.push_back(subElement);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case MatroskaIds::Cluster:
|
2019-06-17 19:11:00 +02:00
|
|
|
// stop as soon as the first cluster has been reached if all relevant information has been gathered
|
|
|
|
// -> take elements from seek tables within this segment into account
|
2018-03-07 01:17:50 +01:00
|
|
|
for (auto i = m_seekInfos.cbegin() + seekInfosIndex, end = m_seekInfos.cend(); i != end; ++i, ++seekInfosIndex) {
|
|
|
|
for (const auto &infoPair : (*i)->info()) {
|
2019-03-13 19:06:42 +01:00
|
|
|
std::uint64_t offset = currentOffset + topLevelElement->dataOffset() + infoPair.second;
|
2018-03-07 01:17:50 +01:00
|
|
|
if (offset >= fileInfo().size()) {
|
|
|
|
diag.emplace_back(DiagLevel::Critical,
|
|
|
|
argsToString("Offset (", offset, ") denoted by \"SeekHead\" element is invalid."), context);
|
2016-03-13 22:00:23 +01:00
|
|
|
} else {
|
|
|
|
auto element = make_unique<EbmlElement>(*this, offset);
|
|
|
|
try {
|
2018-03-05 17:49:29 +01:00
|
|
|
element->parse(diag);
|
2018-03-07 01:17:50 +01:00
|
|
|
if (element->id() != infoPair.first) {
|
|
|
|
diag.emplace_back(DiagLevel::Critical,
|
|
|
|
argsToString("ID of element ", element->idToString(), " at ", offset,
|
|
|
|
" does not match the ID denoted in the \"SeekHead\" element (0x",
|
2021-03-20 21:26:25 +01:00
|
|
|
numberToString(infoPair.first, 16u), ")."),
|
2018-03-07 01:17:50 +01:00
|
|
|
context);
|
2016-03-13 22:00:23 +01:00
|
|
|
}
|
2018-03-07 01:17:50 +01:00
|
|
|
switch (element->id()) {
|
2016-03-13 22:00:23 +01:00
|
|
|
case MatroskaIds::SegmentInfo:
|
2018-03-07 01:17:50 +01:00
|
|
|
if (excludesOffset(m_segmentInfoElements, offset)) {
|
2016-03-13 22:00:23 +01:00
|
|
|
m_additionalElements.emplace_back(move(element));
|
|
|
|
m_segmentInfoElements.emplace_back(m_additionalElements.back().get());
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case MatroskaIds::Tracks:
|
2018-03-07 01:17:50 +01:00
|
|
|
if (excludesOffset(m_tracksElements, offset)) {
|
2016-03-13 22:00:23 +01:00
|
|
|
m_additionalElements.emplace_back(move(element));
|
|
|
|
m_tracksElements.emplace_back(m_additionalElements.back().get());
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case MatroskaIds::Tags:
|
2018-03-07 01:17:50 +01:00
|
|
|
if (excludesOffset(m_tagsElements, offset)) {
|
2016-03-13 22:00:23 +01:00
|
|
|
m_additionalElements.emplace_back(move(element));
|
|
|
|
m_tagsElements.emplace_back(m_additionalElements.back().get());
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case MatroskaIds::Chapters:
|
2018-03-07 01:17:50 +01:00
|
|
|
if (excludesOffset(m_chaptersElements, offset)) {
|
2016-03-13 22:00:23 +01:00
|
|
|
m_additionalElements.emplace_back(move(element));
|
|
|
|
m_chaptersElements.emplace_back(m_additionalElements.back().get());
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case MatroskaIds::Attachments:
|
2018-03-07 01:17:50 +01:00
|
|
|
if (excludesOffset(m_attachmentsElements, offset)) {
|
2016-03-13 22:00:23 +01:00
|
|
|
m_additionalElements.emplace_back(move(element));
|
|
|
|
m_attachmentsElements.emplace_back(m_additionalElements.back().get());
|
|
|
|
}
|
|
|
|
break;
|
2018-03-07 01:17:50 +01:00
|
|
|
default:;
|
2016-03-13 22:00:23 +01:00
|
|
|
}
|
2018-03-07 01:17:50 +01:00
|
|
|
} catch (const Failure &) {
|
|
|
|
diag.emplace_back(DiagLevel::Critical,
|
|
|
|
argsToString("Can not parse element at ", offset, " (denoted using \"SeekHead\" element)."), context);
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-06-17 19:11:00 +02:00
|
|
|
// -> stop if tracks and tags have been found or the file exceeds the max. size to fully process
|
2018-03-07 01:17:50 +01:00
|
|
|
if (((!m_tracksElements.empty() && !m_tagsElements.empty()) || fileInfo().size() > m_maxFullParseSize)
|
|
|
|
&& !m_segmentInfoElements.empty()) {
|
2016-03-13 22:00:23 +01:00
|
|
|
goto finish;
|
|
|
|
}
|
|
|
|
break;
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
2018-03-07 01:17:50 +01:00
|
|
|
} catch (const Failure &) {
|
2019-12-30 22:54:11 +01:00
|
|
|
diag.emplace_back(DiagLevel::Critical, "Unable to parse all children of \"Segment\"-element.", context);
|
2016-03-13 22:00:23 +01:00
|
|
|
break;
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
|
|
|
}
|
2016-03-13 22:00:23 +01:00
|
|
|
currentOffset += topLevelElement->totalSize();
|
|
|
|
break;
|
2018-03-07 01:17:50 +01:00
|
|
|
default:;
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
2018-03-07 01:17:50 +01:00
|
|
|
} catch (const Failure &) {
|
|
|
|
diag.emplace_back(
|
|
|
|
DiagLevel::Critical, argsToString("Unable to parse top-level element at ", topLevelElement->startOffset(), '.'), context);
|
2015-04-22 19:22:01 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2018-02-05 00:41:35 +01:00
|
|
|
|
2015-04-22 19:22:01 +02:00
|
|
|
// finally parse the "Info"-element and fetch "EditionEntry"-elements
|
2015-11-26 14:40:35 +01:00
|
|
|
finish:
|
2015-04-22 19:22:01 +02:00
|
|
|
try {
|
2018-03-05 17:49:29 +01:00
|
|
|
parseSegmentInfo(diag);
|
2018-03-07 01:17:50 +01:00
|
|
|
} catch (const Failure &) {
|
2018-03-05 17:49:29 +01:00
|
|
|
diag.emplace_back(DiagLevel::Critical, "Unable to parse EBML (segment) \"Info\"-element.", context);
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Parses the (segment) "Info"-element.
|
|
|
|
*
|
|
|
|
* This private method is called when parsing the header.
|
|
|
|
*
|
|
|
|
* \throws Throws std::ios_base::failure when an IO error occurs.
|
2018-06-03 20:38:32 +02:00
|
|
|
* \throws Throws TagParser::Failure or a derived exception when a parsing
|
2015-04-22 19:22:01 +02:00
|
|
|
* error occurs.
|
|
|
|
*/
|
2018-03-05 17:49:29 +01:00
|
|
|
void MatroskaContainer::parseSegmentInfo(Diagnostics &diag)
|
2015-04-22 19:22:01 +02:00
|
|
|
{
|
2018-03-07 01:17:50 +01:00
|
|
|
if (m_segmentInfoElements.empty()) {
|
2015-04-22 19:22:01 +02:00
|
|
|
throw NoDataFoundException();
|
|
|
|
}
|
|
|
|
m_duration = TimeSpan();
|
2018-03-07 01:17:50 +01:00
|
|
|
for (EbmlElement *element : m_segmentInfoElements) {
|
2018-03-05 17:49:29 +01:00
|
|
|
element->parse(diag);
|
2015-04-22 19:22:01 +02:00
|
|
|
EbmlElement *subElement = element->firstChild();
|
2019-03-13 19:06:42 +01:00
|
|
|
double rawDuration = 0.0;
|
|
|
|
std::uint64_t timeScale = 1000000;
|
2015-10-14 19:42:48 +02:00
|
|
|
bool hasTitle = false;
|
2018-03-07 01:17:50 +01:00
|
|
|
while (subElement) {
|
2018-03-05 17:49:29 +01:00
|
|
|
subElement->parse(diag);
|
2018-03-07 01:17:50 +01:00
|
|
|
switch (subElement->id()) {
|
2015-04-22 19:22:01 +02:00
|
|
|
case MatroskaIds::Title:
|
2015-10-14 19:42:48 +02:00
|
|
|
m_titles.emplace_back(subElement->readString());
|
|
|
|
hasTitle = true;
|
2015-04-22 19:22:01 +02:00
|
|
|
break;
|
|
|
|
case MatroskaIds::Duration:
|
|
|
|
rawDuration = subElement->readFloat();
|
|
|
|
break;
|
|
|
|
case MatroskaIds::TimeCodeScale:
|
|
|
|
timeScale = subElement->readUInteger();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
subElement = subElement->nextSibling();
|
|
|
|
}
|
2018-02-05 00:11:28 +01:00
|
|
|
// add empty string as title for segment if no
|
|
|
|
// "Title"-element has been specified
|
2018-03-07 01:17:50 +01:00
|
|
|
if (!hasTitle) {
|
2015-10-14 19:42:48 +02:00
|
|
|
m_titles.emplace_back();
|
|
|
|
}
|
2018-03-07 01:17:50 +01:00
|
|
|
if (rawDuration > 0.0) {
|
2021-03-20 21:26:25 +01:00
|
|
|
m_duration += TimeSpan::fromSeconds(rawDuration * static_cast<double>(timeScale) / 1000000000.0);
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-27 00:36:32 +02:00
|
|
|
/*!
|
|
|
|
* \brief Reads track-specific statistics from tags.
|
|
|
|
* \remarks Tags and tracks must have been parsed before calling this method.
|
|
|
|
* \sa MatroskaTrack::readStatisticsFromTags()
|
|
|
|
*/
|
2018-03-05 17:49:29 +01:00
|
|
|
void MatroskaContainer::readTrackStatisticsFromTags(Diagnostics &diag)
|
2017-06-27 00:36:32 +02:00
|
|
|
{
|
2018-03-07 01:17:50 +01:00
|
|
|
if (tracks().empty() || tags().empty()) {
|
2017-06-27 00:36:32 +02:00
|
|
|
return;
|
|
|
|
}
|
2018-03-07 01:17:50 +01:00
|
|
|
for (const auto &track : tracks()) {
|
2018-03-05 17:49:29 +01:00
|
|
|
track->readStatisticsFromTags(tags(), diag);
|
2017-06-27 00:36:32 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-04 23:21:50 +01:00
|
|
|
void MatroskaContainer::internalParseTags(Diagnostics &diag, AbortableProgressFeedback &progress)
|
2015-04-22 19:22:01 +02:00
|
|
|
{
|
2021-02-04 23:21:50 +01:00
|
|
|
CPP_UTILITIES_UNUSED(progress)
|
|
|
|
|
2015-04-22 19:22:01 +02:00
|
|
|
static const string context("parsing tags of Matroska container");
|
2021-08-25 18:53:20 +02:00
|
|
|
auto flags = MatroskaTagFlags::None;
|
|
|
|
if (fileInfo().fileHandlingFlags() & MediaFileHandlingFlags::NormalizeKnownTagFieldIds) {
|
|
|
|
flags += MatroskaTagFlags::NormalizeKnownFieldIds;
|
|
|
|
}
|
|
|
|
for (EbmlElement *const element : m_tagsElements) {
|
2015-04-22 19:22:01 +02:00
|
|
|
try {
|
2018-03-05 17:49:29 +01:00
|
|
|
element->parse(diag);
|
2018-03-07 01:17:50 +01:00
|
|
|
for (EbmlElement *subElement = element->firstChild(); subElement; subElement = subElement->nextSibling()) {
|
2018-03-05 17:49:29 +01:00
|
|
|
subElement->parse(diag);
|
2018-03-07 01:17:50 +01:00
|
|
|
switch (subElement->id()) {
|
2015-04-22 19:22:01 +02:00
|
|
|
case MatroskaIds::Tag:
|
|
|
|
m_tags.emplace_back(make_unique<MatroskaTag>());
|
|
|
|
try {
|
2021-08-25 18:53:20 +02:00
|
|
|
m_tags.back()->parse2(*subElement, flags, diag);
|
2018-03-07 01:17:50 +01:00
|
|
|
} catch (const NoDataFoundException &) {
|
2015-04-22 19:22:01 +02:00
|
|
|
m_tags.pop_back();
|
2018-03-07 01:17:50 +01:00
|
|
|
} catch (const Failure &) {
|
2018-03-05 17:49:29 +01:00
|
|
|
diag.emplace_back(DiagLevel::Critical, argsToString("Unable to parse tag ", m_tags.size(), '.'), context);
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case EbmlIds::Crc32:
|
|
|
|
case EbmlIds::Void:
|
|
|
|
break;
|
|
|
|
default:
|
2018-03-05 17:49:29 +01:00
|
|
|
diag.emplace_back(DiagLevel::Warning, "\"Tags\"-element contains unknown child. It will be ignored.", context);
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
|
|
|
}
|
2018-03-07 01:17:50 +01:00
|
|
|
} catch (const Failure &) {
|
2018-03-05 17:49:29 +01:00
|
|
|
diag.emplace_back(DiagLevel::Critical, "Element structure seems to be invalid.", context);
|
|
|
|
readTrackStatisticsFromTags(diag);
|
2015-04-22 19:22:01 +02:00
|
|
|
throw;
|
|
|
|
}
|
|
|
|
}
|
2018-03-05 17:49:29 +01:00
|
|
|
readTrackStatisticsFromTags(diag);
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
|
|
|
|
2021-02-04 23:21:50 +01:00
|
|
|
void MatroskaContainer::internalParseTracks(Diagnostics &diag, AbortableProgressFeedback &progress)
|
2015-04-22 19:22:01 +02:00
|
|
|
{
|
|
|
|
static const string context("parsing tracks of Matroska container");
|
2018-03-07 01:17:50 +01:00
|
|
|
for (EbmlElement *element : m_tracksElements) {
|
2015-04-22 19:22:01 +02:00
|
|
|
try {
|
2018-03-05 17:49:29 +01:00
|
|
|
element->parse(diag);
|
2018-03-07 01:17:50 +01:00
|
|
|
for (EbmlElement *subElement = element->firstChild(); subElement; subElement = subElement->nextSibling()) {
|
2018-03-05 17:49:29 +01:00
|
|
|
subElement->parse(diag);
|
2018-03-07 01:17:50 +01:00
|
|
|
switch (subElement->id()) {
|
2015-04-22 19:22:01 +02:00
|
|
|
case MatroskaIds::TrackEntry:
|
|
|
|
m_tracks.emplace_back(make_unique<MatroskaTrack>(*subElement));
|
|
|
|
try {
|
2021-02-04 23:21:50 +01:00
|
|
|
m_tracks.back()->parseHeader(diag, progress);
|
2018-03-07 01:17:50 +01:00
|
|
|
} catch (const NoDataFoundException &) {
|
2015-04-22 19:22:01 +02:00
|
|
|
m_tracks.pop_back();
|
2018-03-07 01:17:50 +01:00
|
|
|
} catch (const Failure &) {
|
2018-03-05 17:49:29 +01:00
|
|
|
diag.emplace_back(DiagLevel::Critical, argsToString("Unable to parse track ", m_tracks.size(), '.'), context);
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case EbmlIds::Crc32:
|
|
|
|
case EbmlIds::Void:
|
|
|
|
break;
|
|
|
|
default:
|
2018-03-07 01:17:50 +01:00
|
|
|
diag.emplace_back(DiagLevel::Warning,
|
|
|
|
"\"Tracks\"-element contains unknown child element \"" % subElement->idToString() + "\". It will be ignored.", context);
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
|
|
|
}
|
2018-03-07 01:17:50 +01:00
|
|
|
} catch (const Failure &) {
|
2018-03-05 17:49:29 +01:00
|
|
|
diag.emplace_back(DiagLevel::Critical, "Element structure seems to be invalid.", context);
|
|
|
|
readTrackStatisticsFromTags(diag);
|
2015-04-22 19:22:01 +02:00
|
|
|
throw;
|
|
|
|
}
|
|
|
|
}
|
2018-03-05 17:49:29 +01:00
|
|
|
readTrackStatisticsFromTags(diag);
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
|
|
|
|
2021-02-04 23:21:50 +01:00
|
|
|
void MatroskaContainer::internalParseChapters(Diagnostics &diag, AbortableProgressFeedback &progress)
|
2015-04-22 19:22:01 +02:00
|
|
|
{
|
|
|
|
static const string context("parsing editions/chapters of Matroska container");
|
2018-03-07 01:17:50 +01:00
|
|
|
for (EbmlElement *element : m_chaptersElements) {
|
2015-04-22 19:22:01 +02:00
|
|
|
try {
|
2018-03-05 17:49:29 +01:00
|
|
|
element->parse(diag);
|
2018-03-07 01:17:50 +01:00
|
|
|
for (EbmlElement *subElement = element->firstChild(); subElement; subElement = subElement->nextSibling()) {
|
2018-03-05 17:49:29 +01:00
|
|
|
subElement->parse(diag);
|
2018-03-07 01:17:50 +01:00
|
|
|
switch (subElement->id()) {
|
2015-04-22 19:22:01 +02:00
|
|
|
case MatroskaIds::EditionEntry:
|
|
|
|
m_editionEntries.emplace_back(make_unique<MatroskaEditionEntry>(subElement));
|
|
|
|
try {
|
2021-02-04 23:21:50 +01:00
|
|
|
m_editionEntries.back()->parseNested(diag, progress);
|
2018-03-07 01:17:50 +01:00
|
|
|
} catch (const NoDataFoundException &) {
|
2015-04-22 19:22:01 +02:00
|
|
|
m_editionEntries.pop_back();
|
2018-03-07 01:17:50 +01:00
|
|
|
} catch (const Failure &) {
|
2018-03-05 17:49:29 +01:00
|
|
|
diag.emplace_back(DiagLevel::Critical, argsToString("Unable to parse edition entry ", m_editionEntries.size(), '.'), context);
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case EbmlIds::Crc32:
|
|
|
|
case EbmlIds::Void:
|
|
|
|
break;
|
|
|
|
default:
|
2018-03-07 01:17:50 +01:00
|
|
|
diag.emplace_back(DiagLevel::Warning,
|
|
|
|
"\"Chapters\"-element contains unknown child element \"" % subElement->idToString() + "\". It will be ignored.", context);
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
|
|
|
}
|
2018-03-07 01:17:50 +01:00
|
|
|
} catch (const Failure &) {
|
2018-03-05 17:49:29 +01:00
|
|
|
diag.emplace_back(DiagLevel::Critical, "Element structure seems to be invalid.", context);
|
2015-04-22 19:22:01 +02:00
|
|
|
throw;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-04 23:21:50 +01:00
|
|
|
void MatroskaContainer::internalParseAttachments(Diagnostics &diag, AbortableProgressFeedback &progress)
|
2015-04-22 19:22:01 +02:00
|
|
|
{
|
2021-02-04 23:21:50 +01:00
|
|
|
CPP_UTILITIES_UNUSED(progress)
|
|
|
|
|
2015-04-22 19:22:01 +02:00
|
|
|
static const string context("parsing attachments of Matroska container");
|
2018-03-07 01:17:50 +01:00
|
|
|
for (EbmlElement *element : m_attachmentsElements) {
|
2015-04-22 19:22:01 +02:00
|
|
|
try {
|
2018-03-05 17:49:29 +01:00
|
|
|
element->parse(diag);
|
2018-03-07 01:17:50 +01:00
|
|
|
for (EbmlElement *subElement = element->firstChild(); subElement; subElement = subElement->nextSibling()) {
|
2018-03-05 17:49:29 +01:00
|
|
|
subElement->parse(diag);
|
2018-03-07 01:17:50 +01:00
|
|
|
switch (subElement->id()) {
|
2015-04-22 19:22:01 +02:00
|
|
|
case MatroskaIds::AttachedFile:
|
|
|
|
m_attachments.emplace_back(make_unique<MatroskaAttachment>());
|
|
|
|
try {
|
2018-03-05 17:49:29 +01:00
|
|
|
m_attachments.back()->parse(subElement, diag);
|
2018-03-07 01:17:50 +01:00
|
|
|
} catch (const NoDataFoundException &) {
|
2015-04-22 19:22:01 +02:00
|
|
|
m_attachments.pop_back();
|
2018-03-07 01:17:50 +01:00
|
|
|
} catch (const Failure &) {
|
2018-03-05 17:49:29 +01:00
|
|
|
diag.emplace_back(DiagLevel::Critical, argsToString("Unable to parse attached file ", m_attachments.size(), '.'), context);
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case EbmlIds::Crc32:
|
|
|
|
case EbmlIds::Void:
|
|
|
|
break;
|
|
|
|
default:
|
2018-03-07 01:17:50 +01:00
|
|
|
diag.emplace_back(DiagLevel::Warning,
|
|
|
|
"\"Attachments\"-element contains unknown child element \"" % subElement->idToString() + "\". It will be ignored.", context);
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
|
|
|
}
|
2018-03-07 01:17:50 +01:00
|
|
|
} catch (const Failure &) {
|
2018-03-05 17:49:29 +01:00
|
|
|
diag.emplace_back(DiagLevel::Critical, "Element structure seems to be invalid.", context);
|
2015-04-22 19:22:01 +02:00
|
|
|
throw;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-08-04 00:16:19 +02:00
|
|
|
/// \brief The private SegmentData struct is used in MatroskaContainer::internalMakeFile() to store segment specific data.
|
2018-03-07 01:17:50 +01:00
|
|
|
struct SegmentData {
|
2016-08-04 00:16:19 +02:00
|
|
|
/// \brief Constructs a new segment data object.
|
2018-03-07 01:17:50 +01:00
|
|
|
SegmentData()
|
|
|
|
: hasCrc32(false)
|
|
|
|
, cuesElement(nullptr)
|
|
|
|
, infoDataSize(0)
|
|
|
|
, firstClusterElement(nullptr)
|
|
|
|
, clusterEndOffset(0)
|
|
|
|
, startOffset(0)
|
|
|
|
, newPadding(0)
|
|
|
|
, totalDataSize(0)
|
|
|
|
, totalSize(0)
|
|
|
|
, newDataOffset(0)
|
|
|
|
, sizeDenotationLength(0)
|
|
|
|
{
|
|
|
|
}
|
2015-11-26 14:40:35 +01:00
|
|
|
|
2016-08-04 00:16:19 +02:00
|
|
|
/// \brief whether CRC-32 checksum is present
|
2015-11-26 14:40:35 +01:00
|
|
|
bool hasCrc32;
|
2016-08-04 00:16:19 +02:00
|
|
|
/// \brief used to make "SeekHead"-element
|
2015-11-26 14:40:35 +01:00
|
|
|
MatroskaSeekInfo seekInfo;
|
2016-08-04 00:16:19 +02:00
|
|
|
/// \brief "Cues"-element (original file)
|
2015-11-26 14:40:35 +01:00
|
|
|
EbmlElement *cuesElement;
|
2016-08-04 00:16:19 +02:00
|
|
|
/// \brief used to make "Cues"-element
|
2015-11-26 14:40:35 +01:00
|
|
|
MatroskaCuePositionUpdater cuesUpdater;
|
2016-08-04 00:16:19 +02:00
|
|
|
/// \brief size of the "SegmentInfo"-element
|
2019-03-13 19:06:42 +01:00
|
|
|
std::uint64_t infoDataSize;
|
2016-08-04 00:16:19 +02:00
|
|
|
/// \brief cluster sizes
|
2019-03-13 19:06:42 +01:00
|
|
|
vector<std::uint64_t> clusterSizes;
|
2016-08-04 00:16:19 +02:00
|
|
|
/// \brief first "Cluster"-element (original file)
|
2015-11-26 14:40:35 +01:00
|
|
|
EbmlElement *firstClusterElement;
|
2016-08-04 00:16:19 +02:00
|
|
|
/// \brief end offset of last "Cluster"-element (original file)
|
2019-03-13 19:06:42 +01:00
|
|
|
std::uint64_t clusterEndOffset;
|
2016-08-04 00:16:19 +02:00
|
|
|
/// \brief start offset (in the new file)
|
2019-03-13 19:06:42 +01:00
|
|
|
std::uint64_t startOffset;
|
2016-08-04 00:16:19 +02:00
|
|
|
/// \brief padding (in the new file)
|
2019-03-13 19:06:42 +01:00
|
|
|
std::uint64_t newPadding;
|
2016-08-04 00:16:19 +02:00
|
|
|
/// \brief total size of the segment data (in the new file, excluding header)
|
2019-03-13 19:06:42 +01:00
|
|
|
std::uint64_t totalDataSize;
|
2016-08-04 00:16:19 +02:00
|
|
|
/// \brief total size of the segment data (in the new file, including header)
|
2019-03-13 19:06:42 +01:00
|
|
|
std::uint64_t totalSize;
|
2016-08-04 00:16:19 +02:00
|
|
|
/// \brief data offset of the segment in the new file
|
2019-03-13 19:06:42 +01:00
|
|
|
std::uint64_t newDataOffset;
|
2018-02-05 00:59:44 +01:00
|
|
|
/// \brief header size (in the new file)
|
2019-03-13 19:06:42 +01:00
|
|
|
std::uint8_t sizeDenotationLength;
|
2015-11-26 14:40:35 +01:00
|
|
|
};
|
|
|
|
|
2018-03-05 17:49:29 +01:00
|
|
|
void MatroskaContainer::internalMakeFile(Diagnostics &diag, AbortableProgressFeedback &progress)
|
2015-04-22 19:22:01 +02:00
|
|
|
{
|
|
|
|
static const string context("making Matroska container");
|
2018-03-05 17:49:29 +01:00
|
|
|
progress.updateStep("Calculating element sizes ...");
|
2015-11-26 14:40:35 +01:00
|
|
|
|
|
|
|
// basic validation of original file
|
2018-03-07 01:17:50 +01:00
|
|
|
if (!isHeaderParsed()) {
|
2018-03-05 17:49:29 +01:00
|
|
|
diag.emplace_back(DiagLevel::Critical, "The header has not been parsed yet.", context);
|
2015-04-22 19:22:01 +02:00
|
|
|
throw InvalidDataException();
|
|
|
|
}
|
2021-02-01 17:10:29 +01:00
|
|
|
switch (fileInfo().attachmentsParsingStatus()) {
|
|
|
|
case ParsingStatus::Ok:
|
|
|
|
case ParsingStatus::NotSupported:
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
diag.emplace_back(DiagLevel::Critical, "Attachments have to be parsed without critical errors before changes can be applied.", context);
|
|
|
|
throw InvalidDataException();
|
|
|
|
}
|
2015-11-26 14:40:35 +01:00
|
|
|
|
|
|
|
// define variables for parsing the elements of the original file
|
|
|
|
EbmlElement *level0Element = firstElement();
|
2018-03-07 01:17:50 +01:00
|
|
|
if (!level0Element) {
|
2018-03-05 17:49:29 +01:00
|
|
|
diag.emplace_back(DiagLevel::Critical, "No EBML elements could be found.", context);
|
2015-04-22 19:22:01 +02:00
|
|
|
throw InvalidDataException();
|
|
|
|
}
|
2015-11-26 14:40:35 +01:00
|
|
|
EbmlElement *level1Element, *level2Element;
|
|
|
|
|
|
|
|
// define variables needed for precalculation of "Tags"- and "Attachments"-element
|
|
|
|
vector<MatroskaTagMaker> tagMaker;
|
2017-06-11 01:21:56 +02:00
|
|
|
tagMaker.reserve(tags().size());
|
2019-03-13 19:06:42 +01:00
|
|
|
std::uint64_t tagElementsSize = 0;
|
|
|
|
std::uint64_t tagsSize;
|
2015-11-26 14:40:35 +01:00
|
|
|
vector<MatroskaAttachmentMaker> attachmentMaker;
|
2017-06-11 01:21:56 +02:00
|
|
|
attachmentMaker.reserve(m_attachments.size());
|
2019-03-13 19:06:42 +01:00
|
|
|
std::uint64_t attachedFileElementsSize = 0;
|
|
|
|
std::uint64_t attachmentsSize;
|
2017-06-11 01:21:56 +02:00
|
|
|
vector<MatroskaTrackHeaderMaker> trackHeaderMaker;
|
|
|
|
trackHeaderMaker.reserve(tracks().size());
|
2019-03-13 19:06:42 +01:00
|
|
|
std::uint64_t trackHeaderElementsSize = 0;
|
|
|
|
std::uint64_t trackHeaderSize;
|
2015-11-26 14:40:35 +01:00
|
|
|
|
|
|
|
// define variables to store sizes, offsets and other information required to make a header and "Segment"-elements
|
|
|
|
// current segment index
|
|
|
|
unsigned int segmentIndex = 0;
|
|
|
|
// segment specific data
|
|
|
|
vector<SegmentData> segmentData;
|
|
|
|
// offset of the segment which is currently written / offset of "Cues"-element in segment
|
2019-03-13 19:06:42 +01:00
|
|
|
std::uint64_t offset;
|
2015-11-26 14:40:35 +01:00
|
|
|
// current total offset (including EBML header)
|
2019-03-13 19:06:42 +01:00
|
|
|
std::uint64_t totalOffset;
|
2015-11-26 14:40:35 +01:00
|
|
|
// current write offset (used to calculate positions)
|
2019-03-13 19:06:42 +01:00
|
|
|
std::uint64_t currentPosition = 0;
|
2015-11-26 14:40:35 +01:00
|
|
|
// holds the offsets of all CRC-32 elements and the length of the enclosing block
|
2019-03-13 19:06:42 +01:00
|
|
|
vector<tuple<std::uint64_t, std::uint64_t>> crc32Offsets;
|
2015-11-26 14:40:35 +01:00
|
|
|
// size length used to make size denotations
|
2019-03-13 19:06:42 +01:00
|
|
|
std::uint8_t sizeLength;
|
2015-11-26 14:40:35 +01:00
|
|
|
// sizes and offsets for cluster calculation
|
2019-03-13 19:06:42 +01:00
|
|
|
std::uint64_t clusterSize, clusterReadSize, clusterReadOffset;
|
2015-11-26 14:40:35 +01:00
|
|
|
|
|
|
|
// define variables needed to manage file layout
|
|
|
|
// -> use the preferred tag position by default (might be changed later if not forced)
|
|
|
|
ElementPosition newTagPos = fileInfo().tagPosition();
|
|
|
|
// -> current tag position (determined later)
|
|
|
|
ElementPosition currentTagPos = ElementPosition::Keep;
|
|
|
|
// -> use the preferred cue position by default (might be changed later if not forced)
|
|
|
|
ElementPosition newCuesPos = fileInfo().indexPosition();
|
|
|
|
// --> current cue position (determined later)
|
|
|
|
ElementPosition currentCuesPos = ElementPosition::Keep;
|
|
|
|
// -> index of the last segment
|
2017-10-09 20:59:43 +02:00
|
|
|
unsigned int lastSegmentIndex = numeric_limits<unsigned int>::max();
|
2015-12-21 00:04:56 +01:00
|
|
|
// -> holds new padding
|
2019-03-13 19:06:42 +01:00
|
|
|
std::uint64_t newPadding;
|
2015-12-21 00:04:56 +01:00
|
|
|
// -> whether rewrite is required (always required when forced to rewrite)
|
2016-05-01 20:02:44 +02:00
|
|
|
bool rewriteRequired = fileInfo().isForcingRewrite() || !fileInfo().saveFilePath().empty();
|
2015-11-26 14:40:35 +01:00
|
|
|
|
|
|
|
// calculate EBML header size
|
|
|
|
// -> sub element ID sizes
|
2019-03-13 19:06:42 +01:00
|
|
|
std::uint64_t ebmlHeaderDataSize = 2 * 7;
|
2015-11-26 14:40:35 +01:00
|
|
|
// -> content and size denotation length of numeric sub elements
|
2018-03-07 01:17:50 +01:00
|
|
|
for (auto headerValue :
|
2019-03-13 19:06:42 +01:00
|
|
|
initializer_list<std::uint64_t>{ m_version, m_readVersion, m_maxIdLength, m_maxSizeLength, m_doctypeVersion, m_doctypeReadVersion }) {
|
2015-11-26 14:40:35 +01:00
|
|
|
ebmlHeaderDataSize += sizeLength = EbmlElement::calculateUIntegerLength(headerValue);
|
|
|
|
ebmlHeaderDataSize += EbmlElement::calculateSizeDenotationLength(sizeLength);
|
|
|
|
}
|
|
|
|
// -> content and size denotation length of string sub elements
|
|
|
|
ebmlHeaderDataSize += m_doctype.size();
|
|
|
|
ebmlHeaderDataSize += EbmlElement::calculateSizeDenotationLength(m_doctype.size());
|
2019-03-13 19:06:42 +01:00
|
|
|
const std::uint64_t ebmlHeaderSize = 4 + EbmlElement::calculateSizeDenotationLength(ebmlHeaderDataSize) + ebmlHeaderDataSize;
|
2015-11-26 14:40:35 +01:00
|
|
|
|
2018-03-20 21:41:05 +01:00
|
|
|
// calculate size of "WritingLib"-element
|
2021-01-30 21:53:06 +01:00
|
|
|
constexpr std::string_view muxingAppName = APP_NAME " v" APP_VERSION;
|
|
|
|
constexpr std::uint64_t muxingAppElementTotalSize = 2 + 1 + muxingAppName.size();
|
2018-03-20 21:41:05 +01:00
|
|
|
|
|
|
|
// calculate size of "WritingApp"-element
|
2019-03-13 19:06:42 +01:00
|
|
|
const std::uint64_t writingAppElementDataSize
|
2021-01-30 21:53:06 +01:00
|
|
|
= fileInfo().writingApplication().empty() ? muxingAppName.size() : fileInfo().writingApplication().size();
|
2019-03-13 19:06:42 +01:00
|
|
|
const std::uint64_t writingAppElementTotalSize = 2 + 1 + writingAppElementDataSize;
|
2018-03-20 21:41:05 +01:00
|
|
|
|
2015-04-22 19:22:01 +02:00
|
|
|
try {
|
2015-11-26 14:40:35 +01:00
|
|
|
// calculate size of "Tags"-element
|
2018-03-07 01:17:50 +01:00
|
|
|
for (auto &tag : tags()) {
|
2015-11-07 15:23:36 +01:00
|
|
|
try {
|
2018-03-05 17:49:29 +01:00
|
|
|
tagMaker.emplace_back(tag->prepareMaking(diag));
|
2018-03-07 01:17:50 +01:00
|
|
|
if (tagMaker.back().requiredSize() > 3) {
|
2015-11-07 15:23:36 +01:00
|
|
|
// a tag of 3 bytes size is empty and can be skipped
|
|
|
|
tagElementsSize += tagMaker.back().requiredSize();
|
|
|
|
}
|
2018-03-07 01:17:50 +01:00
|
|
|
} catch (const Failure &) {
|
2015-11-07 15:23:36 +01:00
|
|
|
}
|
|
|
|
}
|
2015-11-26 14:40:35 +01:00
|
|
|
tagsSize = tagElementsSize ? 4 + EbmlElement::calculateSizeDenotationLength(tagElementsSize) + tagElementsSize : 0;
|
|
|
|
|
|
|
|
// calculate size of "Attachments"-element
|
2018-03-07 01:17:50 +01:00
|
|
|
for (auto &attachment : m_attachments) {
|
|
|
|
if (!attachment->isIgnored()) {
|
2015-11-07 15:23:36 +01:00
|
|
|
try {
|
2018-03-05 17:49:29 +01:00
|
|
|
attachmentMaker.emplace_back(attachment->prepareMaking(diag));
|
2018-03-07 01:17:50 +01:00
|
|
|
if (attachmentMaker.back().requiredSize() > 3) {
|
2015-11-07 15:23:36 +01:00
|
|
|
// an attachment of 3 bytes size is empty and can be skipped
|
|
|
|
attachedFileElementsSize += attachmentMaker.back().requiredSize();
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
2018-03-07 01:17:50 +01:00
|
|
|
} catch (const Failure &) {
|
2015-11-07 15:23:36 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-03-07 01:17:50 +01:00
|
|
|
attachmentsSize
|
|
|
|
= attachedFileElementsSize ? 4 + EbmlElement::calculateSizeDenotationLength(attachedFileElementsSize) + attachedFileElementsSize : 0;
|
2015-11-26 14:40:35 +01:00
|
|
|
|
2017-06-11 01:21:56 +02:00
|
|
|
// calculate size of "Tracks"-element
|
2018-03-07 01:17:50 +01:00
|
|
|
for (auto &track : tracks()) {
|
2017-06-11 01:21:56 +02:00
|
|
|
try {
|
2018-03-05 17:49:29 +01:00
|
|
|
trackHeaderMaker.emplace_back(track->prepareMakingHeader(diag));
|
2018-03-07 01:17:50 +01:00
|
|
|
if (trackHeaderMaker.back().requiredSize() > 3) {
|
2017-06-11 01:21:56 +02:00
|
|
|
// a track header of 3 bytes size is empty and can be skipped
|
|
|
|
trackHeaderElementsSize += trackHeaderMaker.back().requiredSize();
|
|
|
|
}
|
2018-03-07 01:17:50 +01:00
|
|
|
} catch (const Failure &) {
|
2017-06-11 01:21:56 +02:00
|
|
|
}
|
|
|
|
}
|
2018-03-07 01:17:50 +01:00
|
|
|
trackHeaderSize
|
|
|
|
= trackHeaderElementsSize ? 4 + EbmlElement::calculateSizeDenotationLength(trackHeaderElementsSize) + trackHeaderElementsSize : 0;
|
2017-06-11 01:21:56 +02:00
|
|
|
|
2015-11-26 14:40:35 +01:00
|
|
|
// inspect layout of original file
|
|
|
|
// - number of segments
|
2015-12-22 17:00:14 +01:00
|
|
|
// - position of tags relative to the media data
|
2015-11-26 14:40:35 +01:00
|
|
|
try {
|
2018-03-07 01:17:50 +01:00
|
|
|
for (bool firstClusterFound = false, firstTagFound = false; level0Element; level0Element = level0Element->nextSibling()) {
|
2018-03-05 17:49:29 +01:00
|
|
|
level0Element->parse(diag);
|
2018-03-07 01:17:50 +01:00
|
|
|
switch (level0Element->id()) {
|
2015-11-26 14:40:35 +01:00
|
|
|
case MatroskaIds::Segment:
|
|
|
|
++lastSegmentIndex;
|
2018-03-07 01:17:50 +01:00
|
|
|
for (level1Element = level0Element->firstChild(); level1Element && !firstClusterFound && !firstTagFound;
|
|
|
|
level1Element = level1Element->nextSibling()) {
|
2018-03-05 17:49:29 +01:00
|
|
|
level1Element->parse(diag);
|
2018-03-07 01:17:50 +01:00
|
|
|
switch (level1Element->id()) {
|
2015-11-26 14:40:35 +01:00
|
|
|
case MatroskaIds::Tags:
|
|
|
|
case MatroskaIds::Attachments:
|
|
|
|
firstTagFound = true;
|
2015-11-11 11:52:32 +01:00
|
|
|
break;
|
|
|
|
case MatroskaIds::Cluster:
|
|
|
|
firstClusterFound = true;
|
|
|
|
}
|
|
|
|
}
|
2018-03-07 01:17:50 +01:00
|
|
|
if (firstTagFound) {
|
2015-11-26 14:40:35 +01:00
|
|
|
currentTagPos = ElementPosition::BeforeData;
|
2018-03-07 01:17:50 +01:00
|
|
|
} else if (firstClusterFound) {
|
2015-11-26 14:40:35 +01:00
|
|
|
currentTagPos = ElementPosition::AfterData;
|
|
|
|
}
|
2015-11-11 11:52:32 +01:00
|
|
|
}
|
2015-11-07 15:23:36 +01:00
|
|
|
}
|
2015-11-26 14:40:35 +01:00
|
|
|
|
|
|
|
// now the number of segments is known -> allocate segment specific data
|
|
|
|
segmentData.resize(lastSegmentIndex + 1);
|
|
|
|
|
|
|
|
// now the current tag/cue position might be known
|
2018-03-07 01:17:50 +01:00
|
|
|
if (newTagPos == ElementPosition::Keep) {
|
|
|
|
if ((newTagPos = currentTagPos) == ElementPosition::Keep) {
|
2015-11-26 14:40:35 +01:00
|
|
|
newTagPos = ElementPosition::BeforeData;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-07 01:17:50 +01:00
|
|
|
} catch (const Failure &) {
|
|
|
|
diag.emplace_back(DiagLevel::Critical,
|
|
|
|
"Unable to parse content in top-level element at " % numberToString(level0Element->startOffset()) + " of original file.", context);
|
2015-11-26 14:40:35 +01:00
|
|
|
throw;
|
2015-11-07 15:23:36 +01:00
|
|
|
}
|
2015-11-26 14:40:35 +01:00
|
|
|
|
2018-03-05 17:49:29 +01:00
|
|
|
progress.nextStepOrStop("Calculating offsets of elements before cluster ...");
|
2018-03-07 01:17:50 +01:00
|
|
|
calculateSegmentData:
|
2015-11-26 14:40:35 +01:00
|
|
|
// define variables to store sizes, offsets and other information required to make a header and "Segment"-elements
|
2015-12-21 00:04:56 +01:00
|
|
|
// -> current "pretent" write offset
|
2019-03-13 19:06:42 +01:00
|
|
|
std::uint64_t currentOffset = ebmlHeaderSize;
|
2015-12-21 00:04:56 +01:00
|
|
|
// -> current read offset (used to calculate positions)
|
2019-03-13 19:06:42 +01:00
|
|
|
std::uint64_t readOffset = 0;
|
2015-12-21 00:04:56 +01:00
|
|
|
// -> index of current element during iteration
|
2015-11-26 14:40:35 +01:00
|
|
|
unsigned int index;
|
|
|
|
|
|
|
|
// if rewriting is required always use the preferred tag/cue position
|
2018-03-07 01:17:50 +01:00
|
|
|
if (rewriteRequired) {
|
2015-11-26 14:40:35 +01:00
|
|
|
newTagPos = fileInfo().tagPosition();
|
2018-03-07 01:17:50 +01:00
|
|
|
if (newTagPos == ElementPosition::Keep) {
|
|
|
|
if ((newTagPos = currentTagPos) == ElementPosition::Keep) {
|
2015-11-26 14:40:35 +01:00
|
|
|
newTagPos = ElementPosition::BeforeData;
|
|
|
|
}
|
2015-11-07 15:23:36 +01:00
|
|
|
}
|
2015-11-26 14:40:35 +01:00
|
|
|
newCuesPos = fileInfo().indexPosition();
|
|
|
|
}
|
|
|
|
|
|
|
|
// calculate sizes and other information required to make segments
|
2018-03-07 01:17:50 +01:00
|
|
|
for (level0Element = firstElement(), currentPosition = newPadding = segmentIndex = 0; level0Element;
|
|
|
|
level0Element = level0Element->nextSibling()) {
|
|
|
|
switch (level0Element->id()) {
|
2015-11-26 14:40:35 +01:00
|
|
|
case EbmlIds::Header:
|
|
|
|
// header size has already been calculated
|
|
|
|
break;
|
|
|
|
|
|
|
|
case EbmlIds::Void:
|
|
|
|
case EbmlIds::Crc32:
|
|
|
|
// level 0 "Void"- and "Checksum"-elements are omitted
|
|
|
|
break;
|
|
|
|
|
|
|
|
case MatroskaIds::Segment: {
|
|
|
|
// get reference to the current segment data instance
|
|
|
|
SegmentData &segment = segmentData[segmentIndex];
|
|
|
|
|
|
|
|
// parse original "Cues"-element (if present)
|
2018-03-07 01:17:50 +01:00
|
|
|
if (!segment.cuesElement && (segment.cuesElement = level0Element->childById(MatroskaIds::Cues, diag))) {
|
2018-03-05 17:49:29 +01:00
|
|
|
segment.cuesUpdater.parse(segment.cuesElement, diag);
|
2015-11-26 14:40:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// get first "Cluster"-element
|
2018-03-07 01:17:50 +01:00
|
|
|
if (!segment.firstClusterElement) {
|
2018-03-05 17:49:29 +01:00
|
|
|
segment.firstClusterElement = level0Element->childById(MatroskaIds::Cluster, diag);
|
2015-11-26 14:40:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// determine current/new cue position
|
2018-03-07 01:17:50 +01:00
|
|
|
if (segment.cuesElement && segment.firstClusterElement) {
|
|
|
|
currentCuesPos = segment.cuesElement->startOffset() < segment.firstClusterElement->startOffset() ? ElementPosition::BeforeData
|
|
|
|
: ElementPosition::AfterData;
|
|
|
|
if (newCuesPos == ElementPosition::Keep) {
|
2015-11-26 14:40:35 +01:00
|
|
|
newCuesPos = currentCuesPos;
|
|
|
|
}
|
2018-03-07 01:17:50 +01:00
|
|
|
} else if (newCuesPos == ElementPosition::Keep) {
|
2015-11-26 14:40:35 +01:00
|
|
|
newCuesPos = ElementPosition::BeforeData;
|
|
|
|
}
|
|
|
|
|
|
|
|
// set start offset of the segment in the new file
|
|
|
|
segment.startOffset = currentOffset;
|
|
|
|
|
|
|
|
// check whether the segment has a CRC-32 element
|
|
|
|
segment.hasCrc32 = level0Element->firstChild() && level0Element->firstChild()->id() == EbmlIds::Crc32;
|
|
|
|
|
|
|
|
// precalculate the size of the segment
|
2018-03-07 01:17:50 +01:00
|
|
|
calculateSegmentSize:
|
2015-11-26 14:40:35 +01:00
|
|
|
|
|
|
|
// pretent writing "CRC-32"-element (which either present and 6 byte long or omitted)
|
|
|
|
segment.totalDataSize = segment.hasCrc32 ? 6 : 0;
|
|
|
|
|
|
|
|
// pretend writing "SeekHead"-element
|
|
|
|
segment.totalDataSize += segment.seekInfo.actualSize();
|
|
|
|
|
|
|
|
// pretend writing "SegmentInfo"-element
|
2018-03-07 01:17:50 +01:00
|
|
|
for (level1Element = level0Element->childById(MatroskaIds::SegmentInfo, diag), index = 0; level1Element;
|
|
|
|
level1Element = level1Element->siblingById(MatroskaIds::SegmentInfo, diag), ++index) {
|
2015-11-26 14:40:35 +01:00
|
|
|
// update offset in "SeekHead"-element
|
2018-03-07 01:17:50 +01:00
|
|
|
if (segment.seekInfo.push(index, MatroskaIds::SegmentInfo, currentPosition + segment.totalDataSize)) {
|
2015-11-26 14:40:35 +01:00
|
|
|
goto calculateSegmentSize;
|
|
|
|
} else {
|
|
|
|
// add size of "SegmentInfo"-element
|
|
|
|
// -> size of "MuxingApp"- and "WritingApp"-element
|
2018-03-20 21:41:05 +01:00
|
|
|
segment.infoDataSize = muxingAppElementTotalSize + writingAppElementTotalSize;
|
2015-11-26 14:40:35 +01:00
|
|
|
// -> add size of "Title"-element
|
2018-03-07 01:17:50 +01:00
|
|
|
if (segmentIndex < m_titles.size()) {
|
2015-11-26 14:40:35 +01:00
|
|
|
const auto &title = m_titles[segmentIndex];
|
2018-03-07 01:17:50 +01:00
|
|
|
if (!title.empty()) {
|
2015-11-26 14:40:35 +01:00
|
|
|
segment.infoDataSize += 2 + EbmlElement::calculateSizeDenotationLength(title.size()) + title.size();
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
2015-11-26 14:40:35 +01:00
|
|
|
}
|
2019-12-30 22:54:11 +01:00
|
|
|
// -> add size of other children
|
2018-03-07 01:17:50 +01:00
|
|
|
for (level2Element = level1Element->firstChild(); level2Element; level2Element = level2Element->nextSibling()) {
|
2018-03-05 17:49:29 +01:00
|
|
|
level2Element->parse(diag);
|
2018-03-07 01:17:50 +01:00
|
|
|
switch (level2Element->id()) {
|
2015-11-26 14:40:35 +01:00
|
|
|
case EbmlIds::Void: // skipped
|
|
|
|
case EbmlIds::Crc32: // skipped
|
|
|
|
case MatroskaIds::Title: // calculated separately
|
|
|
|
case MatroskaIds::MuxingApp: // calculated separately
|
|
|
|
case MatroskaIds::WrittingApp: // calculated separately
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
level2Element->makeBuffer();
|
|
|
|
segment.infoDataSize += level2Element->totalSize();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// -> calculate total size
|
|
|
|
segment.totalDataSize += 4 + EbmlElement::calculateSizeDenotationLength(segment.infoDataSize) + segment.infoDataSize;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-11 01:21:56 +02:00
|
|
|
// pretend writing "Tracks"-element
|
2018-03-07 01:17:50 +01:00
|
|
|
if (trackHeaderSize) {
|
2017-06-11 01:21:56 +02:00
|
|
|
// update offsets in "SeekHead"-element
|
2018-03-07 01:17:50 +01:00
|
|
|
if (segment.seekInfo.push(0, MatroskaIds::Tracks, currentPosition + segment.totalDataSize)) {
|
2017-06-11 01:21:56 +02:00
|
|
|
goto calculateSegmentSize;
|
|
|
|
} else {
|
|
|
|
// add size of "Tracks"-element
|
|
|
|
segment.totalDataSize += trackHeaderSize;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// pretend writing "Chapters"-element
|
2018-03-07 01:17:50 +01:00
|
|
|
for (level1Element = level0Element->childById(MatroskaIds::Chapters, diag), index = 0; level1Element;
|
|
|
|
level1Element = level1Element->siblingById(MatroskaIds::Chapters, diag), ++index) {
|
2017-06-11 01:21:56 +02:00
|
|
|
// update offset in "SeekHead"-element
|
2018-03-07 01:17:50 +01:00
|
|
|
if (segment.seekInfo.push(index, MatroskaIds::Chapters, currentPosition + segment.totalDataSize)) {
|
2017-06-11 01:21:56 +02:00
|
|
|
goto calculateSegmentSize;
|
|
|
|
} else {
|
|
|
|
// add size of element
|
|
|
|
level1Element->makeBuffer();
|
|
|
|
segment.totalDataSize += level1Element->totalSize();
|
2015-11-26 14:40:35 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// "Tags"- and "Attachments"-element are written in either the first or the last segment
|
|
|
|
// and either before "Cues"- and "Cluster"-elements or after these elements
|
|
|
|
// depending on the desired tag position (at the front/at the end)
|
2018-03-07 01:17:50 +01:00
|
|
|
if (newTagPos == ElementPosition::BeforeData && segmentIndex == 0) {
|
2015-11-26 14:40:35 +01:00
|
|
|
// pretend writing "Tags"-element
|
2018-03-07 01:17:50 +01:00
|
|
|
if (tagsSize) {
|
2015-11-26 14:40:35 +01:00
|
|
|
// update offsets in "SeekHead"-element
|
2018-03-07 01:17:50 +01:00
|
|
|
if (segment.seekInfo.push(0, MatroskaIds::Tags, currentPosition + segment.totalDataSize)) {
|
2015-11-26 14:40:35 +01:00
|
|
|
goto calculateSegmentSize;
|
|
|
|
} else {
|
|
|
|
// add size of "Tags"-element
|
|
|
|
segment.totalDataSize += tagsSize;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// pretend writing "Attachments"-element
|
2018-03-07 01:17:50 +01:00
|
|
|
if (attachmentsSize) {
|
2015-11-26 14:40:35 +01:00
|
|
|
// update offsets in "SeekHead"-element
|
2018-03-07 01:17:50 +01:00
|
|
|
if (segment.seekInfo.push(0, MatroskaIds::Attachments, currentPosition + segment.totalDataSize)) {
|
2015-11-26 14:40:35 +01:00
|
|
|
goto calculateSegmentSize;
|
|
|
|
} else {
|
|
|
|
// add size of "Attachments"-element
|
|
|
|
segment.totalDataSize += attachmentsSize;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
offset = segment.totalDataSize; // save current offset (offset before "Cues"-element)
|
|
|
|
|
|
|
|
// pretend writing "Cues"-element
|
2018-03-07 01:17:50 +01:00
|
|
|
if (newCuesPos == ElementPosition::BeforeData && segment.cuesElement) {
|
2015-11-26 14:40:35 +01:00
|
|
|
// update offset of "Cues"-element in "SeekHead"-element
|
2018-03-07 01:17:50 +01:00
|
|
|
if (segment.seekInfo.push(0, MatroskaIds::Cues, currentPosition + segment.totalDataSize)) {
|
2015-11-26 14:40:35 +01:00
|
|
|
goto calculateSegmentSize;
|
|
|
|
} else {
|
|
|
|
// add size of "Cues"-element
|
2018-03-05 17:49:29 +01:00
|
|
|
progress.updateStep("Calculating cluster offsets and index size ...");
|
2018-03-07 01:17:50 +01:00
|
|
|
addCuesElementSize:
|
2015-11-26 14:40:35 +01:00
|
|
|
segment.totalDataSize += segment.cuesUpdater.totalSize();
|
|
|
|
}
|
2017-09-21 23:30:01 +02:00
|
|
|
} else {
|
2018-03-05 17:49:29 +01:00
|
|
|
progress.updateStep("Calculating cluster offsets ...");
|
2015-11-26 14:40:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// decided whether it is necessary to rewrite the entire file (if not already rewriting)
|
2018-03-07 01:17:50 +01:00
|
|
|
if (!rewriteRequired) {
|
2017-10-09 21:16:11 +02:00
|
|
|
// find first "Cluster"-element
|
2018-03-07 01:17:50 +01:00
|
|
|
if ((level1Element = segment.firstClusterElement)) {
|
2017-10-09 21:16:11 +02:00
|
|
|
// just before the first "Cluster"-element
|
|
|
|
// -> calculate total offset (excluding size denotation and incomplete index)
|
|
|
|
totalOffset = currentOffset + 4 + segment.totalDataSize;
|
|
|
|
|
2018-03-07 01:17:50 +01:00
|
|
|
if (totalOffset <= segment.firstClusterElement->startOffset()) {
|
2017-10-09 21:16:11 +02:00
|
|
|
// the padding might be big enough, but
|
|
|
|
// - the segment might become bigger (subsequent tags and attachments)
|
|
|
|
// - the header size hasn't been taken into account yet
|
|
|
|
// - seek information for first cluster and subsequent tags and attachments hasn't been taken into account
|
|
|
|
|
|
|
|
// assume the size denotation length doesn't change -> use length from original file
|
2018-03-07 01:17:50 +01:00
|
|
|
if (level0Element->headerSize() <= 4 || level0Element->headerSize() > 12) {
|
2017-10-09 21:16:11 +02:00
|
|
|
// validate original header size
|
2018-03-05 17:49:29 +01:00
|
|
|
diag.emplace_back(DiagLevel::Critical, "Header size of \"Segment\"-element from original file is invalid.", context);
|
2017-10-09 21:16:11 +02:00
|
|
|
throw InvalidDataException();
|
|
|
|
}
|
2019-03-13 19:06:42 +01:00
|
|
|
segment.sizeDenotationLength = static_cast<std::uint8_t>(level0Element->headerSize() - 4u);
|
2015-11-26 14:40:35 +01:00
|
|
|
|
2018-03-07 01:17:50 +01:00
|
|
|
nonRewriteCalculations:
|
2017-10-09 21:16:11 +02:00
|
|
|
// pretend writing "Cluster"-elements assuming there is no rewrite required
|
|
|
|
// -> update offset in "SeakHead"-element
|
2018-03-07 01:17:50 +01:00
|
|
|
if (segment.seekInfo.push(
|
|
|
|
0, MatroskaIds::Cluster, level1Element->startOffset() - 4 - segment.sizeDenotationLength - ebmlHeaderSize)) {
|
2017-10-09 21:16:11 +02:00
|
|
|
goto calculateSegmentSize;
|
|
|
|
}
|
|
|
|
// -> update offset of "Cluster"-element in "Cues"-element and get end offset of last "Cluster"-element
|
|
|
|
bool cuesInvalidated = false;
|
2018-03-07 01:17:50 +01:00
|
|
|
for (index = 0; level1Element; level1Element = level1Element->siblingById(MatroskaIds::Cluster, diag), ++index) {
|
2017-10-09 21:16:11 +02:00
|
|
|
clusterReadOffset = level1Element->startOffset() - level0Element->dataOffset() + readOffset;
|
|
|
|
segment.clusterEndOffset = level1Element->endOffset();
|
2018-03-07 01:17:50 +01:00
|
|
|
if (segment.cuesElement
|
|
|
|
&& segment.cuesUpdater.updateOffsets(
|
2019-05-04 21:03:09 +02:00
|
|
|
clusterReadOffset, level1Element->startOffset() - 4 - segment.sizeDenotationLength - ebmlHeaderSize)
|
2018-03-07 01:17:50 +01:00
|
|
|
&& newCuesPos == ElementPosition::BeforeData) {
|
2017-10-09 21:16:11 +02:00
|
|
|
cuesInvalidated = true;
|
2015-11-26 14:40:35 +01:00
|
|
|
}
|
2017-10-09 21:16:11 +02:00
|
|
|
// check whether aborted (because this loop might take some seconds to process)
|
2018-03-05 17:49:29 +01:00
|
|
|
progress.stopIfAborted();
|
2017-10-09 21:16:11 +02:00
|
|
|
// update the progress percentage (using offset / file size should be accurate enough)
|
2018-03-07 01:17:50 +01:00
|
|
|
if (index % 50 == 0) {
|
2019-03-13 19:06:42 +01:00
|
|
|
progress.updateStepPercentage(static_cast<std::uint8_t>(level1Element->dataOffset() * 100 / fileInfo().size()));
|
2017-09-20 22:06:52 +02:00
|
|
|
}
|
2017-10-09 21:16:11 +02:00
|
|
|
}
|
2018-03-07 01:17:50 +01:00
|
|
|
if (cuesInvalidated) {
|
2017-10-09 21:16:11 +02:00
|
|
|
segment.totalDataSize = offset;
|
|
|
|
goto addCuesElementSize;
|
|
|
|
}
|
|
|
|
segment.totalDataSize = segment.clusterEndOffset - currentOffset - 4 - segment.sizeDenotationLength;
|
|
|
|
|
|
|
|
// pretend writing "Cues"-element
|
2018-03-05 17:49:29 +01:00
|
|
|
progress.updateStep("Calculating offsets of elements after cluster ...");
|
2018-03-07 01:17:50 +01:00
|
|
|
if (newCuesPos == ElementPosition::AfterData && segment.cuesElement) {
|
2017-10-09 21:16:11 +02:00
|
|
|
// update offset of "Cues"-element in "SeekHead"-element
|
2018-03-07 01:17:50 +01:00
|
|
|
if (segment.seekInfo.push(0, MatroskaIds::Cues, currentPosition + segment.totalDataSize)) {
|
2017-10-09 21:16:11 +02:00
|
|
|
goto calculateSegmentSize;
|
|
|
|
} else {
|
|
|
|
// add size of "Cues"-element
|
|
|
|
segment.totalDataSize += segment.cuesUpdater.totalSize();
|
|
|
|
}
|
|
|
|
}
|
2017-09-21 23:30:01 +02:00
|
|
|
|
2018-03-07 01:17:50 +01:00
|
|
|
if (newTagPos == ElementPosition::AfterData && segmentIndex == lastSegmentIndex) {
|
2017-10-09 21:16:11 +02:00
|
|
|
// pretend writing "Tags"-element
|
2018-03-07 01:17:50 +01:00
|
|
|
if (tagsSize) {
|
2017-10-09 21:16:11 +02:00
|
|
|
// update offsets in "SeekHead"-element
|
2018-03-07 01:17:50 +01:00
|
|
|
if (segment.seekInfo.push(0, MatroskaIds::Tags, currentPosition + segment.totalDataSize)) {
|
2015-11-26 14:40:35 +01:00
|
|
|
goto calculateSegmentSize;
|
|
|
|
} else {
|
2017-10-09 21:16:11 +02:00
|
|
|
// add size of "Tags"-element
|
|
|
|
segment.totalDataSize += tagsSize;
|
2015-11-26 14:40:35 +01:00
|
|
|
}
|
|
|
|
}
|
2017-10-09 21:16:11 +02:00
|
|
|
// pretend writing "Attachments"-element
|
2018-03-07 01:17:50 +01:00
|
|
|
if (attachmentsSize) {
|
2017-10-09 21:16:11 +02:00
|
|
|
// update offsets in "SeekHead"-element
|
2018-03-07 01:17:50 +01:00
|
|
|
if (segment.seekInfo.push(0, MatroskaIds::Attachments, currentPosition + segment.totalDataSize)) {
|
2017-10-09 21:16:11 +02:00
|
|
|
goto calculateSegmentSize;
|
|
|
|
} else {
|
|
|
|
// add size of "Attachments"-element
|
|
|
|
segment.totalDataSize += attachmentsSize;
|
2015-11-26 14:40:35 +01:00
|
|
|
}
|
|
|
|
}
|
2017-10-09 21:16:11 +02:00
|
|
|
}
|
2015-11-26 14:40:35 +01:00
|
|
|
|
2017-10-09 21:16:11 +02:00
|
|
|
// calculate total offset again (taking everything into account)
|
|
|
|
// -> check whether assumed size denotation was correct
|
2018-03-07 01:17:50 +01:00
|
|
|
if (segment.sizeDenotationLength != (sizeLength = EbmlElement::calculateSizeDenotationLength(segment.totalDataSize))) {
|
2017-10-09 21:16:11 +02:00
|
|
|
// assumption was wrong -> recalculate with new length
|
|
|
|
segment.sizeDenotationLength = sizeLength;
|
|
|
|
level1Element = segment.firstClusterElement;
|
|
|
|
goto nonRewriteCalculations;
|
|
|
|
}
|
2015-11-26 14:40:35 +01:00
|
|
|
|
2017-10-09 21:16:11 +02:00
|
|
|
totalOffset = currentOffset + 4 + sizeLength + offset;
|
|
|
|
// offset does not include size of "Cues"-element
|
2018-03-07 01:17:50 +01:00
|
|
|
if (newCuesPos == ElementPosition::BeforeData) {
|
2017-10-09 21:16:11 +02:00
|
|
|
totalOffset += segment.cuesUpdater.totalSize();
|
|
|
|
}
|
2018-03-07 01:17:50 +01:00
|
|
|
if (totalOffset <= segment.firstClusterElement->startOffset()) {
|
2017-10-09 21:16:11 +02:00
|
|
|
// calculate new padding
|
2018-03-07 01:17:50 +01:00
|
|
|
if (segment.newPadding != 1) {
|
2017-10-09 21:16:11 +02:00
|
|
|
// "Void"-element is at least 2 byte long -> can't add 1 byte padding
|
|
|
|
newPadding += (segment.newPadding = segment.firstClusterElement->startOffset() - totalOffset);
|
2015-11-26 14:40:35 +01:00
|
|
|
} else {
|
|
|
|
rewriteRequired = true;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
rewriteRequired = true;
|
|
|
|
}
|
2017-10-09 21:16:11 +02:00
|
|
|
} else {
|
|
|
|
rewriteRequired = true;
|
|
|
|
}
|
2015-11-26 14:40:35 +01:00
|
|
|
} else {
|
2018-03-05 17:49:29 +01:00
|
|
|
diag.emplace_back(DiagLevel::Warning, argsToString("There are no clusters in segment ", segmentIndex, "."), context);
|
2015-11-26 14:40:35 +01:00
|
|
|
}
|
|
|
|
|
2018-03-07 01:17:50 +01:00
|
|
|
if (rewriteRequired) {
|
|
|
|
if (newTagPos != ElementPosition::AfterData
|
|
|
|
&& (!fileInfo().forceTagPosition()
|
2019-05-04 21:03:09 +02:00
|
|
|
|| (fileInfo().tagPosition() == ElementPosition::Keep && currentTagPos == ElementPosition::Keep))) {
|
2015-11-26 14:40:35 +01:00
|
|
|
// rewriting might be avoided by writing the tags at the end
|
|
|
|
newTagPos = ElementPosition::AfterData;
|
|
|
|
rewriteRequired = false;
|
2018-03-07 01:17:50 +01:00
|
|
|
} else if (newCuesPos != ElementPosition::AfterData
|
|
|
|
&& (!fileInfo().forceIndexPosition()
|
2019-05-04 21:03:09 +02:00
|
|
|
|| (fileInfo().indexPosition() == ElementPosition::Keep && currentCuesPos == ElementPosition::Keep))) {
|
2015-11-26 14:40:35 +01:00
|
|
|
// rewriting might be avoided by writing the cues at the end
|
|
|
|
newCuesPos = ElementPosition::AfterData;
|
|
|
|
rewriteRequired = false;
|
|
|
|
}
|
|
|
|
// do calculations again for rewriting / changed element order
|
|
|
|
goto calculateSegmentData;
|
|
|
|
}
|
|
|
|
} else {
|
2017-09-21 23:30:01 +02:00
|
|
|
// if rewrite is required, pretend writing the remaining elements to compute total segment size
|
2015-11-26 14:40:35 +01:00
|
|
|
|
|
|
|
// pretend writing "Void"-element (only if there is at least one "Cluster"-element in the segment)
|
2018-03-07 01:17:50 +01:00
|
|
|
if (!segmentIndex && rewriteRequired && (level1Element = level0Element->childById(MatroskaIds::Cluster, diag))) {
|
2015-11-26 14:40:35 +01:00
|
|
|
// simply use the preferred padding
|
|
|
|
segment.totalDataSize += (segment.newPadding = newPadding = fileInfo().preferredPadding());
|
|
|
|
}
|
|
|
|
|
|
|
|
// pretend writing "Cluster"-element
|
|
|
|
segment.clusterSizes.clear();
|
2017-09-20 22:06:52 +02:00
|
|
|
bool cuesInvalidated = false;
|
2018-03-07 01:17:50 +01:00
|
|
|
for (index = 0; level1Element; level1Element = level1Element->siblingById(MatroskaIds::Cluster, diag), ++index) {
|
2015-11-26 14:40:35 +01:00
|
|
|
// update offset of "Cluster"-element in "Cues"-element
|
|
|
|
clusterReadOffset = level1Element->startOffset() - level0Element->dataOffset() + readOffset;
|
2018-03-07 01:17:50 +01:00
|
|
|
if (segment.cuesElement && segment.cuesUpdater.updateOffsets(clusterReadOffset, currentPosition + segment.totalDataSize)
|
|
|
|
&& newCuesPos == ElementPosition::BeforeData) {
|
2017-09-20 22:06:52 +02:00
|
|
|
cuesInvalidated = true;
|
2015-11-26 14:40:35 +01:00
|
|
|
} else {
|
2018-03-07 01:17:50 +01:00
|
|
|
if (index == 0 && segment.seekInfo.push(index, MatroskaIds::Cluster, currentPosition + segment.totalDataSize)) {
|
2015-11-26 14:40:35 +01:00
|
|
|
goto calculateSegmentSize;
|
|
|
|
} else {
|
|
|
|
// add size of "Cluster"-element
|
|
|
|
clusterSize = clusterReadSize = 0;
|
2018-03-07 01:17:50 +01:00
|
|
|
for (level2Element = level1Element->firstChild(); level2Element; level2Element = level2Element->nextSibling()) {
|
2018-03-05 17:49:29 +01:00
|
|
|
level2Element->parse(diag);
|
2018-03-07 01:17:50 +01:00
|
|
|
if (segment.cuesElement
|
|
|
|
&& segment.cuesUpdater.updateRelativeOffsets(clusterReadOffset, clusterReadSize, clusterSize)
|
|
|
|
&& newCuesPos == ElementPosition::BeforeData) {
|
2017-09-20 22:06:52 +02:00
|
|
|
cuesInvalidated = true;
|
2015-11-26 14:40:35 +01:00
|
|
|
}
|
2018-03-07 01:17:50 +01:00
|
|
|
switch (level2Element->id()) {
|
2015-11-26 14:40:35 +01:00
|
|
|
case EbmlIds::Void:
|
|
|
|
case EbmlIds::Crc32:
|
|
|
|
break;
|
|
|
|
case MatroskaIds::Position:
|
2021-03-20 21:26:25 +01:00
|
|
|
clusterSize += 1u + 1u + EbmlElement::calculateUIntegerLength(currentPosition + segment.totalDataSize);
|
2015-04-22 19:22:01 +02:00
|
|
|
break;
|
|
|
|
default:
|
2015-11-26 14:40:35 +01:00
|
|
|
clusterSize += level2Element->totalSize();
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
2015-11-26 14:40:35 +01:00
|
|
|
clusterReadSize += level2Element->totalSize();
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
2015-11-26 14:40:35 +01:00
|
|
|
segment.clusterSizes.push_back(clusterSize);
|
2021-03-20 21:26:25 +01:00
|
|
|
segment.totalDataSize += 4u + EbmlElement::calculateSizeDenotationLength(clusterSize) + clusterSize;
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
|
|
|
}
|
2017-10-09 21:00:52 +02:00
|
|
|
// check whether aborted (because this loop might take some seconds to process)
|
2018-03-05 17:49:29 +01:00
|
|
|
progress.stopIfAborted();
|
2017-09-21 23:30:01 +02:00
|
|
|
// update the progress percentage (using offset / file size should be accurate enough)
|
2018-03-07 01:17:50 +01:00
|
|
|
if ((index % 50 == 0) && fileInfo().size()) {
|
2019-03-13 19:06:42 +01:00
|
|
|
progress.updateStepPercentage(static_cast<std::uint8_t>(level1Element->dataOffset() * 100 / fileInfo().size()));
|
2017-09-21 23:30:01 +02:00
|
|
|
}
|
2017-10-09 21:00:52 +02:00
|
|
|
// TODO: reduce code duplication for aborting and progress updates
|
2015-11-26 14:40:35 +01:00
|
|
|
}
|
2017-09-20 22:06:52 +02:00
|
|
|
// check whether the total size of the "Cues"-element has been invalidated and recompute cluster if required
|
2018-03-07 01:17:50 +01:00
|
|
|
if (cuesInvalidated) {
|
2017-09-20 22:06:52 +02:00
|
|
|
// reset element size to previously saved offset of "Cues"-element
|
|
|
|
segment.totalDataSize = offset;
|
|
|
|
goto addCuesElementSize;
|
|
|
|
}
|
2015-11-26 14:40:35 +01:00
|
|
|
|
|
|
|
// pretend writing "Cues"-element
|
2018-03-05 17:49:29 +01:00
|
|
|
progress.updateStep("Calculating offsets of elements after cluster ...");
|
2018-03-07 01:17:50 +01:00
|
|
|
if (newCuesPos == ElementPosition::AfterData && segment.cuesElement) {
|
2015-11-26 14:40:35 +01:00
|
|
|
// update offset of "Cues"-element in "SeekHead"-element
|
2018-03-07 01:17:50 +01:00
|
|
|
if (segment.seekInfo.push(0, MatroskaIds::Cues, currentPosition + segment.totalDataSize)) {
|
2015-11-26 14:40:35 +01:00
|
|
|
goto calculateSegmentSize;
|
|
|
|
} else {
|
|
|
|
// add size of "Cues"-element
|
|
|
|
segment.totalDataSize += segment.cuesUpdater.totalSize();
|
2015-10-14 19:42:48 +02:00
|
|
|
}
|
2015-11-26 14:40:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// "Tags"- and "Attachments"-element are written in either the first or the last segment
|
|
|
|
// and either before "Cues"- and "Cluster"-elements or after these elements
|
|
|
|
// depending on the desired tag position (at the front/at the end)
|
2018-03-07 01:17:50 +01:00
|
|
|
if (newTagPos == ElementPosition::AfterData && segmentIndex == lastSegmentIndex) {
|
2015-11-26 14:40:35 +01:00
|
|
|
// pretend writing "Tags"-element
|
2018-03-07 01:17:50 +01:00
|
|
|
if (tagsSize) {
|
2015-11-26 14:40:35 +01:00
|
|
|
// update offsets in "SeekHead"-element
|
2018-03-07 01:17:50 +01:00
|
|
|
if (segment.seekInfo.push(0, MatroskaIds::Tags, currentPosition + segment.totalDataSize)) {
|
2015-11-07 15:23:36 +01:00
|
|
|
goto calculateSegmentSize;
|
|
|
|
} else {
|
2015-11-26 14:40:35 +01:00
|
|
|
// add size of "Tags"-element
|
|
|
|
segment.totalDataSize += tagsSize;
|
2015-11-07 15:23:36 +01:00
|
|
|
}
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
2015-11-26 14:40:35 +01:00
|
|
|
// pretend writing "Attachments"-element
|
2018-03-07 01:17:50 +01:00
|
|
|
if (attachmentsSize) {
|
2015-11-26 14:40:35 +01:00
|
|
|
// update offsets in "SeekHead"-element
|
2018-03-07 01:17:50 +01:00
|
|
|
if (segment.seekInfo.push(0, MatroskaIds::Attachments, currentPosition + segment.totalDataSize)) {
|
2015-11-26 14:40:35 +01:00
|
|
|
goto calculateSegmentSize;
|
2015-11-07 15:23:36 +01:00
|
|
|
} else {
|
2015-11-26 14:40:35 +01:00
|
|
|
// add size of "Attachments"-element
|
|
|
|
segment.totalDataSize += attachmentsSize;
|
2015-11-07 15:23:36 +01:00
|
|
|
}
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
2015-11-26 14:40:35 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// increase the current segment index
|
|
|
|
++segmentIndex;
|
|
|
|
|
|
|
|
// increase write offsets by the size of the segment which size has just been computed
|
|
|
|
segment.totalSize = 4 + EbmlElement::calculateSizeDenotationLength(segment.totalDataSize) + segment.totalDataSize;
|
|
|
|
currentPosition += segment.totalSize;
|
|
|
|
currentOffset += segment.totalSize;
|
|
|
|
|
2021-07-02 03:00:50 +02:00
|
|
|
// increase the read offset by the size of the segment read from the original file
|
2015-11-26 14:40:35 +01:00
|
|
|
readOffset += level0Element->totalSize();
|
|
|
|
|
|
|
|
break;
|
2018-03-07 01:17:50 +01:00
|
|
|
}
|
|
|
|
default:
|
2015-11-26 14:40:35 +01:00
|
|
|
// just copy any unknown top-level elements
|
2018-03-07 01:17:50 +01:00
|
|
|
diag.emplace_back(DiagLevel::Warning,
|
|
|
|
"The top-level element \"" % level0Element->idToString() + "\" of the original file is unknown and will just be copied.",
|
|
|
|
context);
|
2015-11-26 14:40:35 +01:00
|
|
|
currentOffset += level0Element->totalSize();
|
|
|
|
readOffset += level0Element->totalSize();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-07 01:17:50 +01:00
|
|
|
if (!rewriteRequired) {
|
2015-11-26 14:40:35 +01:00
|
|
|
// check whether the new padding is ok according to specifications
|
2018-03-07 01:17:50 +01:00
|
|
|
if ((rewriteRequired = (newPadding > fileInfo().maxPadding() || newPadding < fileInfo().minPadding()))) {
|
2015-11-26 14:40:35 +01:00
|
|
|
// need to recalculate segment data for rewrite
|
|
|
|
goto calculateSegmentData;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-30 23:53:53 +01:00
|
|
|
} catch (const OperationAbortedException &) {
|
|
|
|
diag.emplace_back(DiagLevel::Information, "Applying new tag information has been aborted.", context);
|
|
|
|
throw;
|
2018-03-07 01:17:50 +01:00
|
|
|
} catch (const Failure &) {
|
2018-03-05 17:49:29 +01:00
|
|
|
diag.emplace_back(DiagLevel::Critical, "Parsing the original file failed.", context);
|
2015-11-26 14:40:35 +01:00
|
|
|
throw;
|
2019-03-13 19:06:42 +01:00
|
|
|
} catch (const std::ios_base::failure &failure) {
|
|
|
|
diag.emplace_back(DiagLevel::Critical, argsToString("An IO error occurred when parsing the original file: ", failure.what()), context);
|
|
|
|
throw;
|
2015-11-26 14:40:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// setup stream(s) for writing
|
2015-12-21 00:04:56 +01:00
|
|
|
// -> update status
|
2018-03-05 17:49:29 +01:00
|
|
|
progress.nextStepOrStop("Preparing streams ...");
|
2015-12-22 23:54:35 +01:00
|
|
|
|
|
|
|
// -> define variables needed to handle output stream and backup stream (required when rewriting the file)
|
2021-09-11 23:05:22 +02:00
|
|
|
string originalPath = fileInfo().path(), backupPath;
|
2016-12-18 20:17:50 +01:00
|
|
|
NativeFileStream &outputStream = fileInfo().stream();
|
|
|
|
NativeFileStream backupStream; // create a stream to open the backup/original file for the case rewriting the file is required
|
2015-12-22 23:54:35 +01:00
|
|
|
BinaryWriter outputWriter(&outputStream);
|
|
|
|
char buff[8]; // buffer used to make size denotations
|
|
|
|
|
2018-03-07 01:17:50 +01:00
|
|
|
if (rewriteRequired) {
|
|
|
|
if (fileInfo().saveFilePath().empty()) {
|
2016-05-01 20:02:44 +02:00
|
|
|
// move current file to temp dir and reopen it as backupStream, recreate original file
|
|
|
|
try {
|
2021-09-11 23:05:22 +02:00
|
|
|
BackupHelper::createBackupFileCanonical(fileInfo().backupDirectory(), originalPath, backupPath, outputStream, backupStream);
|
2016-05-01 20:02:44 +02:00
|
|
|
// recreate original file, define buffer variables
|
2021-09-11 23:05:22 +02:00
|
|
|
outputStream.open(originalPath, ios_base::out | ios_base::binary | ios_base::trunc);
|
2019-03-13 19:06:42 +01:00
|
|
|
} catch (const std::ios_base::failure &failure) {
|
|
|
|
diag.emplace_back(
|
|
|
|
DiagLevel::Critical, argsToString("Creation of temporary file (to rewrite the original file) failed: ", failure.what()), context);
|
|
|
|
throw;
|
2016-05-01 20:02:44 +02:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// open the current file as backupStream and create a new outputStream at the specified "save file path"
|
|
|
|
try {
|
|
|
|
backupStream.exceptions(ios_base::badbit | ios_base::failbit);
|
2021-01-30 21:53:06 +01:00
|
|
|
backupStream.open(BasicFileInfo::pathForOpen(fileInfo().path()).data(), ios_base::in | ios_base::binary);
|
2016-05-01 20:02:44 +02:00
|
|
|
fileInfo().close();
|
2021-01-30 21:53:06 +01:00
|
|
|
outputStream.open(BasicFileInfo::pathForOpen(fileInfo().saveFilePath()).data(), ios_base::out | ios_base::binary | ios_base::trunc);
|
2019-03-13 19:06:42 +01:00
|
|
|
} catch (const std::ios_base::failure &failure) {
|
|
|
|
diag.emplace_back(DiagLevel::Critical, argsToString("Opening streams to write output file failed: ", failure.what()), context);
|
|
|
|
throw;
|
2016-05-01 20:02:44 +02:00
|
|
|
}
|
2015-11-26 14:40:35 +01:00
|
|
|
}
|
|
|
|
|
2016-05-01 20:02:44 +02:00
|
|
|
// set backup stream as associated input stream since we need the original elements to write the new file
|
|
|
|
setStream(backupStream);
|
|
|
|
|
|
|
|
// TODO: reduce code duplication
|
|
|
|
|
2015-11-26 14:40:35 +01:00
|
|
|
} else { // !rewriteRequired
|
|
|
|
// buffer currently assigned attachments
|
2018-03-07 01:17:50 +01:00
|
|
|
for (auto &maker : attachmentMaker) {
|
2018-03-05 17:49:29 +01:00
|
|
|
maker.bufferCurrentAttachments(diag);
|
2015-11-26 14:40:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// reopen original file to ensure it is opened for writing
|
|
|
|
try {
|
|
|
|
fileInfo().close();
|
|
|
|
outputStream.open(fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary);
|
2019-03-13 19:06:42 +01:00
|
|
|
} catch (const std::ios_base::failure &failure) {
|
|
|
|
diag.emplace_back(DiagLevel::Critical, argsToString("Opening the file with write permissions failed: ", failure.what()), context);
|
|
|
|
throw;
|
2015-11-26 14:40:35 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-12-21 00:04:56 +01:00
|
|
|
// start actual writing
|
2015-11-26 14:40:35 +01:00
|
|
|
try {
|
|
|
|
// write EBML header
|
2018-03-05 17:49:29 +01:00
|
|
|
progress.nextStepOrStop("Writing EBML header ...");
|
2015-11-26 14:40:35 +01:00
|
|
|
outputWriter.writeUInt32BE(EbmlIds::Header);
|
|
|
|
sizeLength = EbmlElement::makeSizeDenotation(ebmlHeaderDataSize, buff);
|
|
|
|
outputStream.write(buff, sizeLength);
|
|
|
|
EbmlElement::makeSimpleElement(outputStream, EbmlIds::Version, m_version);
|
|
|
|
EbmlElement::makeSimpleElement(outputStream, EbmlIds::ReadVersion, m_readVersion);
|
|
|
|
EbmlElement::makeSimpleElement(outputStream, EbmlIds::MaxIdLength, m_maxIdLength);
|
|
|
|
EbmlElement::makeSimpleElement(outputStream, EbmlIds::MaxSizeLength, m_maxSizeLength);
|
|
|
|
EbmlElement::makeSimpleElement(outputStream, EbmlIds::DocType, m_doctype);
|
|
|
|
EbmlElement::makeSimpleElement(outputStream, EbmlIds::DocTypeVersion, m_doctypeVersion);
|
|
|
|
EbmlElement::makeSimpleElement(outputStream, EbmlIds::DocTypeReadVersion, m_doctypeReadVersion);
|
|
|
|
|
|
|
|
// iterates through all level 0 elements of the original file
|
2018-03-07 01:17:50 +01:00
|
|
|
for (level0Element = firstElement(), segmentIndex = 0, currentPosition = 0; level0Element; level0Element = level0Element->nextSibling()) {
|
2015-11-26 14:40:35 +01:00
|
|
|
|
|
|
|
// write all level 0 elements of the original file
|
2018-03-07 01:17:50 +01:00
|
|
|
switch (level0Element->id()) {
|
2015-11-26 14:40:35 +01:00
|
|
|
case EbmlIds::Header:
|
|
|
|
// header has already been written -> skip it here
|
|
|
|
break;
|
|
|
|
|
|
|
|
case EbmlIds::Void:
|
|
|
|
case EbmlIds::Crc32:
|
|
|
|
// level 0 "Void"- and "Checksum"-elements are omitted
|
|
|
|
break;
|
|
|
|
|
|
|
|
case MatroskaIds::Segment: {
|
|
|
|
// get reference to the current segment data instance
|
|
|
|
SegmentData &segment = segmentData[segmentIndex];
|
|
|
|
|
|
|
|
// write "Segment"-element actually
|
2018-03-05 17:49:29 +01:00
|
|
|
progress.updateStep("Writing segment header ...");
|
2015-11-26 14:40:35 +01:00
|
|
|
outputWriter.writeUInt32BE(MatroskaIds::Segment);
|
|
|
|
sizeLength = EbmlElement::makeSizeDenotation(segment.totalDataSize, buff);
|
|
|
|
outputStream.write(buff, sizeLength);
|
2019-03-13 19:06:42 +01:00
|
|
|
segment.newDataOffset = offset = static_cast<std::uint64_t>(outputStream.tellp()); // store segment data offset here
|
2015-11-26 14:40:35 +01:00
|
|
|
|
|
|
|
// write CRC-32 element ...
|
2018-03-07 01:17:50 +01:00
|
|
|
if (segment.hasCrc32) {
|
2015-11-26 14:40:35 +01:00
|
|
|
// ... if the original element had a CRC-32 element
|
2018-06-02 22:56:08 +02:00
|
|
|
*buff = static_cast<char>(EbmlIds::Crc32);
|
|
|
|
*(buff + 1) = static_cast<char>(0x84); // length denotation: 4 byte
|
2015-11-26 14:40:35 +01:00
|
|
|
// set the value after writing the element
|
|
|
|
crc32Offsets.emplace_back(outputStream.tellp(), segment.totalDataSize);
|
|
|
|
outputStream.write(buff, 6);
|
|
|
|
}
|
|
|
|
|
|
|
|
// write "SeekHead"-element (except there is no seek information for the current segment)
|
2018-03-05 17:49:29 +01:00
|
|
|
segment.seekInfo.make(outputStream, diag);
|
2015-11-26 14:40:35 +01:00
|
|
|
|
|
|
|
// write "SegmentInfo"-element
|
2018-03-07 01:17:50 +01:00
|
|
|
for (level1Element = level0Element->childById(MatroskaIds::SegmentInfo, diag); level1Element;
|
|
|
|
level1Element = level1Element->siblingById(MatroskaIds::SegmentInfo, diag)) {
|
2015-11-26 14:40:35 +01:00
|
|
|
// -> write ID and size
|
|
|
|
outputWriter.writeUInt32BE(MatroskaIds::SegmentInfo);
|
|
|
|
sizeLength = EbmlElement::makeSizeDenotation(segment.infoDataSize, buff);
|
|
|
|
outputStream.write(buff, sizeLength);
|
2019-12-30 22:54:11 +01:00
|
|
|
// -> write children
|
2018-03-07 01:17:50 +01:00
|
|
|
for (level2Element = level1Element->firstChild(); level2Element; level2Element = level2Element->nextSibling()) {
|
|
|
|
switch (level2Element->id()) {
|
2015-11-26 14:40:35 +01:00
|
|
|
case EbmlIds::Void: // skipped
|
|
|
|
case EbmlIds::Crc32: // skipped
|
|
|
|
case MatroskaIds::Title: // written separately
|
|
|
|
case MatroskaIds::MuxingApp: // written separately
|
|
|
|
case MatroskaIds::WrittingApp: // written separately
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
level2Element->copyBuffer(outputStream);
|
2015-12-21 00:04:56 +01:00
|
|
|
level2Element->discardBuffer();
|
2015-11-07 15:23:36 +01:00
|
|
|
}
|
2015-11-26 14:40:35 +01:00
|
|
|
}
|
|
|
|
// -> write "Title"-element
|
2018-03-07 01:17:50 +01:00
|
|
|
if (segmentIndex < m_titles.size()) {
|
2015-11-26 14:40:35 +01:00
|
|
|
const auto &title = m_titles[segmentIndex];
|
2018-03-07 01:17:50 +01:00
|
|
|
if (!title.empty()) {
|
2015-11-26 14:40:35 +01:00
|
|
|
EbmlElement::makeSimpleElement(outputStream, MatroskaIds::Title, title);
|
2015-11-11 11:52:32 +01:00
|
|
|
}
|
2015-11-26 14:40:35 +01:00
|
|
|
}
|
|
|
|
// -> write "MuxingApp"- and "WritingApp"-element
|
2021-01-30 21:53:06 +01:00
|
|
|
EbmlElement::makeSimpleElement(outputStream, MatroskaIds::MuxingApp, muxingAppName);
|
2018-03-20 21:41:05 +01:00
|
|
|
EbmlElement::makeSimpleElement(outputStream, MatroskaIds::WrittingApp,
|
2021-01-30 21:53:06 +01:00
|
|
|
fileInfo().writingApplication().empty() ? muxingAppName : fileInfo().writingApplication());
|
2015-11-26 14:40:35 +01:00
|
|
|
}
|
|
|
|
|
2017-06-11 01:21:56 +02:00
|
|
|
// write "Tracks"-element
|
2018-03-07 01:17:50 +01:00
|
|
|
if (trackHeaderElementsSize) {
|
2017-06-11 01:21:56 +02:00
|
|
|
outputWriter.writeUInt32BE(MatroskaIds::Tracks);
|
|
|
|
sizeLength = EbmlElement::makeSizeDenotation(trackHeaderElementsSize, buff);
|
|
|
|
outputStream.write(buff, sizeLength);
|
2018-03-07 01:17:50 +01:00
|
|
|
for (auto &maker : trackHeaderMaker) {
|
2017-06-11 01:21:56 +02:00
|
|
|
maker.make(outputStream);
|
2015-11-26 14:40:35 +01:00
|
|
|
}
|
2017-06-11 01:21:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// write "Chapters"-element
|
2018-03-07 01:17:50 +01:00
|
|
|
for (level1Element = level0Element->childById(MatroskaIds::Chapters, diag); level1Element;
|
|
|
|
level1Element = level1Element->siblingById(MatroskaIds::Chapters, diag)) {
|
2017-06-11 01:21:56 +02:00
|
|
|
level1Element->copyBuffer(outputStream);
|
|
|
|
level1Element->discardBuffer();
|
2015-11-26 14:40:35 +01:00
|
|
|
}
|
|
|
|
|
2018-03-07 01:17:50 +01:00
|
|
|
if (newTagPos == ElementPosition::BeforeData && segmentIndex == 0) {
|
2015-11-26 14:40:35 +01:00
|
|
|
// write "Tags"-element
|
2018-03-07 01:17:50 +01:00
|
|
|
if (tagsSize) {
|
2015-11-26 14:40:35 +01:00
|
|
|
outputWriter.writeUInt32BE(MatroskaIds::Tags);
|
|
|
|
sizeLength = EbmlElement::makeSizeDenotation(tagElementsSize, buff);
|
2015-04-22 19:22:01 +02:00
|
|
|
outputStream.write(buff, sizeLength);
|
2018-03-07 01:17:50 +01:00
|
|
|
for (auto &maker : tagMaker) {
|
2015-11-26 14:40:35 +01:00
|
|
|
maker.make(outputStream);
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
2015-11-26 14:40:35 +01:00
|
|
|
}
|
|
|
|
// write "Attachments"-element
|
2018-03-07 01:17:50 +01:00
|
|
|
if (attachmentsSize) {
|
2015-11-26 14:40:35 +01:00
|
|
|
outputWriter.writeUInt32BE(MatroskaIds::Attachments);
|
|
|
|
sizeLength = EbmlElement::makeSizeDenotation(attachedFileElementsSize, buff);
|
|
|
|
outputStream.write(buff, sizeLength);
|
2018-03-07 01:17:50 +01:00
|
|
|
for (auto &maker : attachmentMaker) {
|
2018-03-05 17:49:29 +01:00
|
|
|
maker.make(outputStream, diag);
|
2015-11-07 15:23:36 +01:00
|
|
|
}
|
2015-11-26 14:40:35 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// write "Cues"-element
|
2018-03-07 01:17:50 +01:00
|
|
|
if (newCuesPos == ElementPosition::BeforeData && segment.cuesElement) {
|
2018-03-05 17:49:29 +01:00
|
|
|
segment.cuesUpdater.make(outputStream, diag);
|
2015-11-26 14:40:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// write padding / "Void"-element
|
2018-03-07 01:17:50 +01:00
|
|
|
if (segment.newPadding) {
|
2015-12-21 00:04:56 +01:00
|
|
|
// calculate length
|
2019-03-13 19:06:42 +01:00
|
|
|
std::uint64_t voidLength;
|
2018-03-07 01:17:50 +01:00
|
|
|
if (segment.newPadding < 64) {
|
2015-11-26 14:40:35 +01:00
|
|
|
sizeLength = 1;
|
2021-03-20 21:26:25 +01:00
|
|
|
*buff = static_cast<char>(voidLength = segment.newPadding - 2) | static_cast<char>(0x80);
|
2015-11-26 14:40:35 +01:00
|
|
|
} else {
|
|
|
|
sizeLength = 8;
|
2019-03-13 19:06:42 +01:00
|
|
|
BE::getBytes(static_cast<std::uint64_t>((voidLength = segment.newPadding - 9) | 0x100000000000000), buff);
|
2015-11-26 14:40:35 +01:00
|
|
|
}
|
2015-12-21 00:04:56 +01:00
|
|
|
// write header
|
2015-11-26 14:40:35 +01:00
|
|
|
outputWriter.writeByte(EbmlIds::Void);
|
|
|
|
outputStream.write(buff, sizeLength);
|
2015-12-21 00:04:56 +01:00
|
|
|
// write zeroes
|
2018-03-07 01:17:50 +01:00
|
|
|
for (; voidLength; --voidLength) {
|
2015-11-26 14:40:35 +01:00
|
|
|
outputStream.put(0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-12-22 17:00:14 +01:00
|
|
|
// write media data / "Cluster"-elements
|
2018-03-05 17:49:29 +01:00
|
|
|
level1Element = level0Element->childById(MatroskaIds::Cluster, diag);
|
2018-03-07 01:17:50 +01:00
|
|
|
if (rewriteRequired) {
|
2015-11-26 14:40:35 +01:00
|
|
|
// update status, check whether the operation has been aborted
|
2019-03-13 19:06:42 +01:00
|
|
|
progress.nextStepOrStop("Writing cluster ...",
|
|
|
|
static_cast<std::uint8_t>((static_cast<std::uint64_t>(outputStream.tellp()) - offset) * 100 / segment.totalDataSize));
|
2015-11-26 14:40:35 +01:00
|
|
|
// write "Cluster"-element
|
2017-09-21 23:30:01 +02:00
|
|
|
auto clusterSizesIterator = segment.clusterSizes.cbegin();
|
|
|
|
unsigned int index = 0;
|
2018-03-07 01:17:50 +01:00
|
|
|
for (; level1Element; level1Element = level1Element->siblingById(MatroskaIds::Cluster, diag), ++clusterSizesIterator, ++index) {
|
2015-11-26 14:40:35 +01:00
|
|
|
// calculate position of cluster in segment
|
2019-03-13 19:06:42 +01:00
|
|
|
clusterSize = currentPosition + (static_cast<std::uint64_t>(outputStream.tellp()) - offset);
|
2015-11-26 14:40:35 +01:00
|
|
|
// write header; checking whether clusterSizesIterator is valid shouldn't be necessary
|
|
|
|
outputWriter.writeUInt32BE(MatroskaIds::Cluster);
|
|
|
|
sizeLength = EbmlElement::makeSizeDenotation(*clusterSizesIterator, buff);
|
|
|
|
outputStream.write(buff, sizeLength);
|
2019-12-30 22:54:11 +01:00
|
|
|
// write children
|
2018-03-07 01:17:50 +01:00
|
|
|
for (level2Element = level1Element->firstChild(); level2Element; level2Element = level2Element->nextSibling()) {
|
|
|
|
switch (level2Element->id()) {
|
2015-11-26 14:40:35 +01:00
|
|
|
case EbmlIds::Void:
|
|
|
|
case EbmlIds::Crc32:
|
|
|
|
break;
|
|
|
|
case MatroskaIds::Position:
|
|
|
|
EbmlElement::makeSimpleElement(outputStream, MatroskaIds::Position, clusterSize);
|
|
|
|
break;
|
|
|
|
default:
|
2018-03-05 17:49:29 +01:00
|
|
|
level2Element->copyEntirely(outputStream, diag, nullptr);
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
|
|
|
}
|
2015-11-26 14:40:35 +01:00
|
|
|
// update percentage, check whether the operation has been aborted
|
2018-03-05 17:49:29 +01:00
|
|
|
progress.stopIfAborted();
|
2018-03-07 01:17:50 +01:00
|
|
|
if (index % 50 == 0) {
|
|
|
|
progress.updateStepPercentage(
|
2019-03-13 19:06:42 +01:00
|
|
|
static_cast<std::uint8_t>((static_cast<std::uint64_t>(outputStream.tellp()) - offset) * 100 / segment.totalDataSize));
|
2015-11-07 15:23:36 +01:00
|
|
|
}
|
2015-11-26 14:40:35 +01:00
|
|
|
}
|
|
|
|
} else {
|
2015-12-22 17:00:14 +01:00
|
|
|
// can't just skip existing "Cluster"-elements: "Position"-elements must be updated
|
2021-07-02 03:00:50 +02:00
|
|
|
progress.nextStepOrStop("Updating cluster ...",
|
2019-03-13 19:06:42 +01:00
|
|
|
static_cast<std::uint8_t>((static_cast<std::uint64_t>(outputStream.tellp()) - offset) * 100 / segment.totalDataSize));
|
2018-03-07 01:17:50 +01:00
|
|
|
for (; level1Element; level1Element = level1Element->nextSibling()) {
|
|
|
|
for (level2Element = level1Element->firstChild(); level2Element; level2Element = level2Element->nextSibling()) {
|
|
|
|
switch (level2Element->id()) {
|
2015-12-22 17:00:14 +01:00
|
|
|
case MatroskaIds::Position:
|
|
|
|
// calculate new position
|
2018-06-02 22:56:08 +02:00
|
|
|
sizeLength = EbmlElement::makeUInteger(level1Element->startOffset() - segmentData.front().newDataOffset, buff,
|
2019-03-13 19:06:42 +01:00
|
|
|
level2Element->dataSize() > 8 ? 8 : static_cast<std::uint8_t>(level2Element->dataSize()));
|
2015-12-22 17:00:14 +01:00
|
|
|
// new position can only applied if it doesn't need more bytes than the previous position
|
2018-03-07 01:17:50 +01:00
|
|
|
if (level2Element->dataSize() < sizeLength) {
|
2015-12-22 17:00:14 +01:00
|
|
|
// can't update position -> void position elements ("Position"-elements seem a bit useless anyways)
|
2018-06-02 22:56:08 +02:00
|
|
|
outputStream.seekp(static_cast<streamoff>(level2Element->startOffset()));
|
2018-02-05 00:59:44 +01:00
|
|
|
outputStream.put(static_cast<char>(EbmlIds::Void));
|
2015-12-22 17:00:14 +01:00
|
|
|
} else {
|
|
|
|
// update position
|
2018-06-02 22:56:08 +02:00
|
|
|
outputStream.seekp(static_cast<streamoff>(level2Element->dataOffset()));
|
2015-12-22 17:00:14 +01:00
|
|
|
outputStream.write(buff, sizeLength);
|
|
|
|
}
|
|
|
|
break;
|
2018-03-07 01:17:50 +01:00
|
|
|
default:;
|
2015-12-22 17:00:14 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-11-26 14:40:35 +01:00
|
|
|
// skip existing "Cluster"-elements
|
2018-06-02 22:56:08 +02:00
|
|
|
outputStream.seekp(static_cast<streamoff>(segment.clusterEndOffset));
|
2015-11-26 14:40:35 +01:00
|
|
|
}
|
|
|
|
|
2018-03-05 17:49:29 +01:00
|
|
|
progress.updateStep("Writing segment tail ...");
|
2017-09-21 23:30:01 +02:00
|
|
|
|
2015-11-26 14:40:35 +01:00
|
|
|
// write "Cues"-element
|
2018-03-07 01:17:50 +01:00
|
|
|
if (newCuesPos == ElementPosition::AfterData && segment.cuesElement) {
|
2018-03-05 17:49:29 +01:00
|
|
|
segment.cuesUpdater.make(outputStream, diag);
|
2015-11-26 14:40:35 +01:00
|
|
|
}
|
|
|
|
|
2018-03-07 01:17:50 +01:00
|
|
|
if (newTagPos == ElementPosition::AfterData && segmentIndex == lastSegmentIndex) {
|
2015-11-26 14:40:35 +01:00
|
|
|
// write "Tags"-element
|
2018-03-07 01:17:50 +01:00
|
|
|
if (tagsSize) {
|
2015-11-26 14:40:35 +01:00
|
|
|
outputWriter.writeUInt32BE(MatroskaIds::Tags);
|
|
|
|
sizeLength = EbmlElement::makeSizeDenotation(tagElementsSize, buff);
|
|
|
|
outputStream.write(buff, sizeLength);
|
2018-03-07 01:17:50 +01:00
|
|
|
for (auto &maker : tagMaker) {
|
2015-11-26 14:40:35 +01:00
|
|
|
maker.make(outputStream);
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
2015-11-26 14:40:35 +01:00
|
|
|
}
|
|
|
|
// write "Attachments"-element
|
2018-03-07 01:17:50 +01:00
|
|
|
if (attachmentsSize) {
|
2015-11-26 14:40:35 +01:00
|
|
|
outputWriter.writeUInt32BE(MatroskaIds::Attachments);
|
|
|
|
sizeLength = EbmlElement::makeSizeDenotation(attachedFileElementsSize, buff);
|
|
|
|
outputStream.write(buff, sizeLength);
|
2018-03-07 01:17:50 +01:00
|
|
|
for (auto &maker : attachmentMaker) {
|
2018-03-05 17:49:29 +01:00
|
|
|
maker.make(outputStream, diag);
|
2015-11-07 15:23:36 +01:00
|
|
|
}
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
|
|
|
}
|
2015-11-26 14:40:35 +01:00
|
|
|
|
|
|
|
// increase the current segment index
|
|
|
|
++segmentIndex;
|
|
|
|
|
|
|
|
// increase write offsets by the size of the segment which has just been written
|
|
|
|
currentPosition += segment.totalSize;
|
|
|
|
|
|
|
|
break;
|
2018-03-07 01:17:50 +01:00
|
|
|
}
|
|
|
|
default:
|
2015-11-26 14:40:35 +01:00
|
|
|
// just copy any unknown top-level elements
|
2018-03-05 17:49:29 +01:00
|
|
|
level0Element->copyEntirely(outputStream, diag, nullptr);
|
2015-11-26 14:40:35 +01:00
|
|
|
currentPosition += level0Element->totalSize();
|
2015-11-07 15:23:36 +01:00
|
|
|
}
|
2015-11-26 14:40:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// reparse what is written so far
|
2018-03-05 17:49:29 +01:00
|
|
|
progress.updateStep("Reparsing output file ...");
|
2018-03-07 01:17:50 +01:00
|
|
|
if (rewriteRequired) {
|
2015-12-22 17:00:14 +01:00
|
|
|
// report new size
|
2019-03-13 19:06:42 +01:00
|
|
|
fileInfo().reportSizeChanged(static_cast<std::uint64_t>(outputStream.tellp()));
|
2016-05-01 20:02:44 +02:00
|
|
|
|
|
|
|
// "save as path" is now the regular path
|
2018-03-07 01:17:50 +01:00
|
|
|
if (!fileInfo().saveFilePath().empty()) {
|
2016-05-01 20:02:44 +02:00
|
|
|
fileInfo().reportPathChanged(fileInfo().saveFilePath());
|
|
|
|
fileInfo().setSaveFilePath(string());
|
|
|
|
}
|
|
|
|
|
2015-12-22 17:00:14 +01:00
|
|
|
// the outputStream needs to be reopened to be able to read again
|
|
|
|
outputStream.close();
|
2015-11-07 15:23:36 +01:00
|
|
|
outputStream.open(fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary);
|
|
|
|
setStream(outputStream);
|
2015-12-21 00:04:56 +01:00
|
|
|
} else {
|
2019-03-13 19:06:42 +01:00
|
|
|
const auto newSize = static_cast<std::uint64_t>(outputStream.tellp());
|
2018-03-07 01:17:50 +01:00
|
|
|
if (newSize < fileInfo().size()) {
|
2015-12-22 17:00:14 +01:00
|
|
|
// file is smaller after the modification -> truncate
|
|
|
|
// -> close stream before truncating
|
|
|
|
outputStream.close();
|
|
|
|
// -> truncate file
|
2018-03-07 01:17:50 +01:00
|
|
|
if (truncate(fileInfo().path().c_str(), static_cast<iostream::off_type>(newSize)) == 0) {
|
2015-12-22 17:00:14 +01:00
|
|
|
fileInfo().reportSizeChanged(newSize);
|
|
|
|
} else {
|
2018-03-05 17:49:29 +01:00
|
|
|
diag.emplace_back(DiagLevel::Critical, "Unable to truncate the file.", context);
|
2015-12-22 17:00:14 +01:00
|
|
|
}
|
|
|
|
// -> reopen the stream again
|
|
|
|
outputStream.open(fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary);
|
|
|
|
} else {
|
|
|
|
// file is longer after the modification -> just report new size
|
|
|
|
fileInfo().reportSizeChanged(newSize);
|
|
|
|
}
|
2015-11-26 14:40:35 +01:00
|
|
|
}
|
|
|
|
reset();
|
|
|
|
try {
|
2021-02-04 23:21:50 +01:00
|
|
|
parseHeader(diag, progress);
|
|
|
|
} catch (const OperationAbortedException &) {
|
|
|
|
throw;
|
2018-03-07 01:17:50 +01:00
|
|
|
} catch (const Failure &) {
|
2018-03-05 17:49:29 +01:00
|
|
|
diag.emplace_back(DiagLevel::Critical, "Unable to reparse the header of the new file.", context);
|
2015-11-26 14:40:35 +01:00
|
|
|
throw;
|
|
|
|
}
|
|
|
|
|
|
|
|
// update CRC-32 checksums
|
2018-03-07 01:17:50 +01:00
|
|
|
if (!crc32Offsets.empty()) {
|
2018-03-05 17:49:29 +01:00
|
|
|
progress.updateStep("Updating CRC-32 checksums ...");
|
2018-03-07 01:17:50 +01:00
|
|
|
for (const auto &crc32Offset : crc32Offsets) {
|
2018-06-02 22:56:08 +02:00
|
|
|
outputStream.seekg(static_cast<streamoff>(get<0>(crc32Offset) + 6));
|
|
|
|
outputStream.seekp(static_cast<streamoff>(get<0>(crc32Offset) + 2));
|
2015-11-26 14:40:35 +01:00
|
|
|
writer().writeUInt32LE(reader().readCrc32(get<1>(crc32Offset) - 6));
|
2015-11-07 15:23:36 +01:00
|
|
|
}
|
2015-11-26 14:40:35 +01:00
|
|
|
}
|
|
|
|
|
2019-12-15 19:43:16 +01:00
|
|
|
// prevent deferring final write operations (to catch and handle possible errors here)
|
2015-11-26 14:40:35 +01:00
|
|
|
outputStream.flush();
|
|
|
|
|
2018-07-23 14:44:06 +02:00
|
|
|
// handle errors (which might have been occurred after renaming/creating backup file)
|
2018-03-07 01:17:50 +01:00
|
|
|
} catch (...) {
|
2021-09-11 23:05:22 +02:00
|
|
|
BackupHelper::handleFailureAfterFileModifiedCanonical(fileInfo(), originalPath, backupPath, outputStream, backupStream, diag, context);
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-07 01:17:50 +01:00
|
|
|
} // namespace TagParser
|