added properties to control position of tags when writing files
This commit is contained in:
parent
6563a0c854
commit
e7bd2185d5
|
@ -703,6 +703,52 @@ void MatroskaContainer::internalMakeFile()
|
||||||
addNotification(NotificationType::Critical, "No EBML elements could be found.", context);
|
addNotification(NotificationType::Critical, "No EBML elements could be found.", context);
|
||||||
throw InvalidDataException();
|
throw InvalidDataException();
|
||||||
}
|
}
|
||||||
|
// check whether a rewrite is required
|
||||||
|
try {
|
||||||
|
// calculate size of tags
|
||||||
|
vector<MatroskaTagMaker> tagMaker;
|
||||||
|
uint64 tagElementsSize = 0;
|
||||||
|
for(auto &tag : tags()) {
|
||||||
|
tag->invalidateNotifications();
|
||||||
|
try {
|
||||||
|
tagMaker.emplace_back(tag->prepareMaking());
|
||||||
|
if(tagMaker.back().requiredSize() > 3) {
|
||||||
|
// a tag of 3 bytes size is empty and can be skipped
|
||||||
|
tagElementsSize += tagMaker.back().requiredSize();
|
||||||
|
}
|
||||||
|
} catch(Failure &) {
|
||||||
|
// nothing to do because notifications will be added anyways
|
||||||
|
}
|
||||||
|
addNotifications(*tag);
|
||||||
|
}
|
||||||
|
uint64 tagsSize = tagElementsSize ? 4 + EbmlElement::calculateSizeDenotationLength(tagElementsSize) + tagElementsSize : 0;
|
||||||
|
// calculate size of attachments
|
||||||
|
vector<MatroskaAttachmentMaker> attachmentMaker;
|
||||||
|
uint64 attachedFileElementsSize = 0;
|
||||||
|
for(auto &attachment : m_attachments) {
|
||||||
|
if(!attachment->isIgnored()) {
|
||||||
|
attachment->invalidateNotifications();
|
||||||
|
try {
|
||||||
|
attachmentMaker.emplace_back(attachment->prepareMaking());
|
||||||
|
if(attachmentMaker.back().requiredSize() > 3) {
|
||||||
|
// an attachment of 3 bytes size is empty and can be skipped
|
||||||
|
attachedFileElementsSize += attachmentMaker.back().requiredSize();
|
||||||
|
}
|
||||||
|
} catch(Failure &) {
|
||||||
|
// nothing to do because notifications will be added anyways
|
||||||
|
}
|
||||||
|
addNotifications(*attachment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
uint64 attachmentsSize = attachedFileElementsSize ? 4 + EbmlElement::calculateSizeDenotationLength(attachedFileElementsSize) + attachedFileElementsSize : 0;
|
||||||
|
// check the number of segments to be written
|
||||||
|
unsigned int lastSegmentIndex = static_cast<unsigned int>(-1);
|
||||||
|
for(; level0Element; level0Element = level0Element->nextSibling()) {
|
||||||
|
level0Element->parse();
|
||||||
|
if(level0Element->id() == MatroskaIds::Segment) {
|
||||||
|
++lastSegmentIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
// prepare rewriting the file
|
// prepare rewriting the file
|
||||||
updateStatus("Preparing for rewriting Matroska/EBML file ...");
|
updateStatus("Preparing for rewriting Matroska/EBML file ...");
|
||||||
fileInfo().close(); // ensure the file is close before renaming it
|
fileInfo().close(); // ensure the file is close before renaming it
|
||||||
|
@ -756,15 +802,10 @@ void MatroskaContainer::internalMakeFile()
|
||||||
uint64 segmentInfoElementDataSize;
|
uint64 segmentInfoElementDataSize;
|
||||||
MatroskaSeekInfo seekInfo;
|
MatroskaSeekInfo seekInfo;
|
||||||
MatroskaCuePositionUpdater cuesUpdater;
|
MatroskaCuePositionUpdater cuesUpdater;
|
||||||
vector<MatroskaTagMaker> tagMaker;
|
|
||||||
uint64 tagElementsSize, tagsSize;
|
|
||||||
vector<MatroskaAttachmentMaker> attachmentMaker;
|
|
||||||
uint64 attachedFileElementsSize, attachmentsSize;
|
|
||||||
unsigned int segmentIndex = 0;
|
unsigned int segmentIndex = 0;
|
||||||
unsigned int index;
|
unsigned int index;
|
||||||
try {
|
try {
|
||||||
for(; level0Element; level0Element = level0Element->nextSibling()) {
|
for(level0Element = firstElement(); level0Element; level0Element = level0Element->nextSibling()) {
|
||||||
level0Element->parse();
|
|
||||||
switch(level0Element->id()) {
|
switch(level0Element->id()) {
|
||||||
case EbmlIds::Header:
|
case EbmlIds::Header:
|
||||||
break; // header is already written; skip header here
|
break; // header is already written; skip header here
|
||||||
|
@ -777,40 +818,6 @@ void MatroskaContainer::internalMakeFile()
|
||||||
// prepare writing tags
|
// prepare writing tags
|
||||||
// ensure seek info contains no old entries
|
// ensure seek info contains no old entries
|
||||||
seekInfo.clear();
|
seekInfo.clear();
|
||||||
// calculate size of tags
|
|
||||||
tagElementsSize = 0;
|
|
||||||
for(auto &tag : tags()) {
|
|
||||||
tag->invalidateNotifications();
|
|
||||||
try {
|
|
||||||
tagMaker.emplace_back(tag->prepareMaking());
|
|
||||||
if(tagMaker.back().requiredSize() > 3) {
|
|
||||||
// a tag of 3 bytes size is empty and can be skipped
|
|
||||||
tagElementsSize += tagMaker.back().requiredSize();
|
|
||||||
}
|
|
||||||
} catch(Failure &) {
|
|
||||||
// nothing to do because notifications will be added anyways
|
|
||||||
}
|
|
||||||
addNotifications(*tag);
|
|
||||||
}
|
|
||||||
tagsSize = tagElementsSize ? 4 + EbmlElement::calculateSizeDenotationLength(tagElementsSize) + tagElementsSize : 0;
|
|
||||||
// calculate size of attachments
|
|
||||||
attachedFileElementsSize = 0;
|
|
||||||
for(auto &attachment : m_attachments) {
|
|
||||||
if(!attachment->isIgnored()) {
|
|
||||||
attachment->invalidateNotifications();
|
|
||||||
try {
|
|
||||||
attachmentMaker.emplace_back(attachment->prepareMaking());
|
|
||||||
if(attachmentMaker.back().requiredSize() > 3) {
|
|
||||||
// an attachment of 3 bytes size is empty and can be skipped
|
|
||||||
attachedFileElementsSize += attachmentMaker.back().requiredSize();
|
|
||||||
}
|
|
||||||
} catch(Failure &) {
|
|
||||||
// nothing to do because notifications will be added anyways
|
|
||||||
}
|
|
||||||
addNotifications(*attachment);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
attachmentsSize = attachedFileElementsSize ? 4 + EbmlElement::calculateSizeDenotationLength(attachedFileElementsSize) + attachedFileElementsSize : 0;
|
|
||||||
// parse cues
|
// parse cues
|
||||||
cuesUpdater.invalidateNotifications();
|
cuesUpdater.invalidateNotifications();
|
||||||
if((level1Element = level0Element->childById(MatroskaIds::Cues))) {
|
if((level1Element = level0Element->childById(MatroskaIds::Cues))) {
|
||||||
|
@ -880,6 +887,20 @@ void MatroskaContainer::internalMakeFile()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// all "Tags"- and "Attachments"-elements 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)
|
||||||
|
if(fileInfo().tagPosition() == TagPosition::BeforeData && segmentIndex == 0) {
|
||||||
|
// pretend writing "Tags"-element
|
||||||
|
if(tagsSize) {
|
||||||
|
// update offsets in "SeekHead"-element
|
||||||
|
if(seekInfo.push(0, MatroskaIds::Tags, currentOffset + elementSize)) {
|
||||||
|
goto calculateSegmentSize;
|
||||||
|
} else {
|
||||||
|
// add size of "Tags"-element
|
||||||
|
elementSize += tagsSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
// pretend writing "Attachments"-element
|
// pretend writing "Attachments"-element
|
||||||
if(attachmentsSize) {
|
if(attachmentsSize) {
|
||||||
// update offsets in "SeekHead"-element
|
// update offsets in "SeekHead"-element
|
||||||
|
@ -890,15 +911,6 @@ void MatroskaContainer::internalMakeFile()
|
||||||
elementSize += attachmentsSize;
|
elementSize += attachmentsSize;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// pretend writing "Tags"-element
|
|
||||||
if(tagsSize) {
|
|
||||||
// update offsets in "SeekHead"-element
|
|
||||||
if(seekInfo.push(0, MatroskaIds::Tags, currentOffset + elementSize)) {
|
|
||||||
goto calculateSegmentSize;
|
|
||||||
} else {
|
|
||||||
// add size of "Tags"-element
|
|
||||||
elementSize += tagsSize;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// pretend writing "Cues"-element
|
// pretend writing "Cues"-element
|
||||||
if(cuesPresent) {
|
if(cuesPresent) {
|
||||||
|
@ -951,6 +963,28 @@ void MatroskaContainer::internalMakeFile()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if(fileInfo().tagPosition() == TagPosition::AfterData && segmentIndex == lastSegmentIndex) {
|
||||||
|
// pretend writing "Tags"-element
|
||||||
|
if(tagsSize) {
|
||||||
|
// update offsets in "SeekHead"-element
|
||||||
|
if(seekInfo.push(0, MatroskaIds::Tags, currentOffset + elementSize)) {
|
||||||
|
goto calculateSegmentSize;
|
||||||
|
} else {
|
||||||
|
// add size of "Tags"-element
|
||||||
|
elementSize += tagsSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// pretend writing "Attachments"-element
|
||||||
|
if(attachmentsSize) {
|
||||||
|
// update offsets in "SeekHead"-element
|
||||||
|
if(seekInfo.push(0, MatroskaIds::Attachments, currentOffset + elementSize)) {
|
||||||
|
goto calculateSegmentSize;
|
||||||
|
} else {
|
||||||
|
// add size of "Attachments"-element
|
||||||
|
elementSize += attachmentsSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
// write "Segment"-element actually
|
// write "Segment"-element actually
|
||||||
updateStatus("Writing segment header ...");
|
updateStatus("Writing segment header ...");
|
||||||
outputWriter.writeUInt32BE(MatroskaIds::Segment);
|
outputWriter.writeUInt32BE(MatroskaIds::Segment);
|
||||||
|
@ -1006,6 +1040,17 @@ void MatroskaContainer::internalMakeFile()
|
||||||
level1Element->copyEntirely(outputStream);
|
level1Element->copyEntirely(outputStream);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if(fileInfo().tagPosition() == TagPosition::BeforeData && segmentIndex == 0) {
|
||||||
|
// write "Tags"-element
|
||||||
|
if(tagsSize) {
|
||||||
|
outputWriter.writeUInt32BE(MatroskaIds::Tags);
|
||||||
|
sizeLength = EbmlElement::makeSizeDenotation(tagElementsSize, buff);
|
||||||
|
outputStream.write(buff, sizeLength);
|
||||||
|
for(auto &maker : tagMaker) {
|
||||||
|
maker.make(outputStream);
|
||||||
|
}
|
||||||
|
// no need to add notifications; this has been done when creating the make
|
||||||
|
}
|
||||||
// write "Attachments"-element
|
// write "Attachments"-element
|
||||||
if(attachmentsSize) {
|
if(attachmentsSize) {
|
||||||
outputWriter.writeUInt32BE(MatroskaIds::Attachments);
|
outputWriter.writeUInt32BE(MatroskaIds::Attachments);
|
||||||
|
@ -1016,15 +1061,6 @@ void MatroskaContainer::internalMakeFile()
|
||||||
}
|
}
|
||||||
// no need to add notifications; this has been done when creating the make
|
// no need to add notifications; this has been done when creating the make
|
||||||
}
|
}
|
||||||
// write "Tags"-element
|
|
||||||
if(tagsSize) {
|
|
||||||
outputWriter.writeUInt32BE(MatroskaIds::Tags);
|
|
||||||
sizeLength = EbmlElement::makeSizeDenotation(tagElementsSize, buff);
|
|
||||||
outputStream.write(buff, sizeLength);
|
|
||||||
for(auto &maker : tagMaker) {
|
|
||||||
maker.make(outputStream);
|
|
||||||
}
|
|
||||||
// no need to add notifications; this has been done when creating the make
|
|
||||||
}
|
}
|
||||||
// write "Cues"-element
|
// write "Cues"-element
|
||||||
if(cuesPresent) {
|
if(cuesPresent) {
|
||||||
|
@ -1071,6 +1107,28 @@ void MatroskaContainer::internalMakeFile()
|
||||||
updatePercentage(static_cast<double>(static_cast<uint64>(outputStream.tellp()) - offset) / elementSize);
|
updatePercentage(static_cast<double>(static_cast<uint64>(outputStream.tellp()) - offset) / elementSize);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if(fileInfo().tagPosition() == TagPosition::AfterData && segmentIndex == lastSegmentIndex) {
|
||||||
|
// write "Tags"-element
|
||||||
|
if(tagsSize) {
|
||||||
|
outputWriter.writeUInt32BE(MatroskaIds::Tags);
|
||||||
|
sizeLength = EbmlElement::makeSizeDenotation(tagElementsSize, buff);
|
||||||
|
outputStream.write(buff, sizeLength);
|
||||||
|
for(auto &maker : tagMaker) {
|
||||||
|
maker.make(outputStream);
|
||||||
|
}
|
||||||
|
// no need to add notifications; this has been done when creating the make
|
||||||
|
}
|
||||||
|
// write "Attachments"-element
|
||||||
|
if(attachmentsSize) {
|
||||||
|
outputWriter.writeUInt32BE(MatroskaIds::Attachments);
|
||||||
|
sizeLength = EbmlElement::makeSizeDenotation(attachedFileElementsSize, buff);
|
||||||
|
outputStream.write(buff, sizeLength);
|
||||||
|
for(auto &maker : attachmentMaker) {
|
||||||
|
maker.make(outputStream);
|
||||||
|
}
|
||||||
|
// no need to add notifications; this has been done when creating the make
|
||||||
|
}
|
||||||
|
}
|
||||||
++segmentIndex; // increase the current segment index
|
++segmentIndex; // increase the current segment index
|
||||||
currentOffset += 4 + sizeLength + elementSize; // increase current write offset by the size of the segment which has just been written
|
currentOffset += 4 + sizeLength + elementSize; // increase current write offset by the size of the segment which has just been written
|
||||||
readOffset = level0Element->totalSize(); // increase the read offset by the size of the segment read from the orignial file
|
readOffset = level0Element->totalSize(); // increase the read offset by the size of the segment read from the orignial file
|
||||||
|
@ -1110,6 +1168,7 @@ void MatroskaContainer::internalMakeFile()
|
||||||
updatePercentage(100.0);
|
updatePercentage(100.0);
|
||||||
// flush output stream
|
// flush output stream
|
||||||
outputStream.flush();
|
outputStream.flush();
|
||||||
|
// handle errors which occured after renaming/creating backup file
|
||||||
} catch(OperationAbortedException &) {
|
} catch(OperationAbortedException &) {
|
||||||
setStream(outputStream);
|
setStream(outputStream);
|
||||||
reset();
|
reset();
|
||||||
|
@ -1129,6 +1188,14 @@ void MatroskaContainer::internalMakeFile()
|
||||||
BackupHelper::restoreOriginalFileFromBackupFile(fileInfo().path(), backupPath, outputStream, backupStream);
|
BackupHelper::restoreOriginalFileFromBackupFile(fileInfo().path(), backupPath, outputStream, backupStream);
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
// handle errors which occured before renaming/creating backup file
|
||||||
|
} catch(Failure &) {
|
||||||
|
addNotification(NotificationType::Critical, "Parsing the original file failed.", context);
|
||||||
|
throw;
|
||||||
|
} catch(ios_base::failure &) {
|
||||||
|
addNotification(NotificationType::Critical, "An IO error occured when parsing the original file.", context);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,13 +53,12 @@ namespace Media {
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \class Media::MediaFileInfo
|
* \class Media::MediaFileInfo
|
||||||
* \brief The MediaFileInfo class extends the BasicFileInfo class.
|
* \brief The MediaFileInfo class allows to read and write tag information providing
|
||||||
|
* a container/tag format independent interface.
|
||||||
*
|
*
|
||||||
* The MediaFileInfo class allows to read and edit meta information
|
* It also provides some technical information such as contained streams.
|
||||||
* of MP3 files with ID3 tag and MP4 files with iTunes tag. It also
|
*
|
||||||
* provides some technical information about these types of files such
|
* For examples see "cli/mainfeatures.cpp" of the tageditor repository.
|
||||||
* as contained streams.
|
|
||||||
* A bunch of other file types (see Media::ContainerFormat) can be recognized.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
@ -74,7 +73,11 @@ MediaFileInfo::MediaFileInfo() :
|
||||||
m_tagsParsingStatus(ParsingStatus::NotParsedYet),
|
m_tagsParsingStatus(ParsingStatus::NotParsedYet),
|
||||||
m_chaptersParsingStatus(ParsingStatus::NotParsedYet),
|
m_chaptersParsingStatus(ParsingStatus::NotParsedYet),
|
||||||
m_attachmentsParsingStatus(ParsingStatus::NotParsedYet),
|
m_attachmentsParsingStatus(ParsingStatus::NotParsedYet),
|
||||||
m_forceFullParse(MEDIAINFO_CPP_FORCE_FULL_PARSE)
|
m_forceFullParse(MEDIAINFO_CPP_FORCE_FULL_PARSE),
|
||||||
|
m_minPadding(0),
|
||||||
|
m_maxPadding(0),
|
||||||
|
m_tagPosition(TagPosition::BeforeData),
|
||||||
|
m_forceTagPosition(true)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
@ -92,7 +95,11 @@ MediaFileInfo::MediaFileInfo(const string &path) :
|
||||||
m_tagsParsingStatus(ParsingStatus::NotParsedYet),
|
m_tagsParsingStatus(ParsingStatus::NotParsedYet),
|
||||||
m_chaptersParsingStatus(ParsingStatus::NotParsedYet),
|
m_chaptersParsingStatus(ParsingStatus::NotParsedYet),
|
||||||
m_attachmentsParsingStatus(ParsingStatus::NotParsedYet),
|
m_attachmentsParsingStatus(ParsingStatus::NotParsedYet),
|
||||||
m_forceFullParse(MEDIAINFO_CPP_FORCE_FULL_PARSE)
|
m_forceFullParse(MEDIAINFO_CPP_FORCE_FULL_PARSE),
|
||||||
|
m_minPadding(0),
|
||||||
|
m_maxPadding(0),
|
||||||
|
m_tagPosition(TagPosition::BeforeData),
|
||||||
|
m_forceTagPosition(true)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
@ -181,7 +188,7 @@ startParsingSignature:
|
||||||
addNotifications(notifications);
|
addNotifications(notifications);
|
||||||
break;
|
break;
|
||||||
} case ContainerFormat::Ebml: {
|
} case ContainerFormat::Ebml: {
|
||||||
unique_ptr<MatroskaContainer> container = make_unique<MatroskaContainer>(*this, m_containerOffset);
|
auto container = make_unique<MatroskaContainer>(*this, m_containerOffset);
|
||||||
NotificationList notifications;
|
NotificationList notifications;
|
||||||
try {
|
try {
|
||||||
container->parseHeader();
|
container->parseHeader();
|
||||||
|
|
111
mediafileinfo.h
111
mediafileinfo.h
|
@ -40,6 +40,12 @@ enum class TagUsage
|
||||||
Never /**< tags of the type are never used; a possibly existing tag of the type is removed */
|
Never /**< tags of the type are never used; a possibly existing tag of the type is removed */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class TagPosition
|
||||||
|
{
|
||||||
|
BeforeData,
|
||||||
|
AfterData
|
||||||
|
};
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief The ParsingStatus enum specifies whether a certain part of the file (tracks, tags, ...) has
|
* \brief The ParsingStatus enum specifies whether a certain part of the file (tracks, tags, ...) has
|
||||||
* been parsed yet and if what the parsing result is.
|
* been parsed yet and if what the parsing result is.
|
||||||
|
@ -135,6 +141,14 @@ public:
|
||||||
// methods to get, set object behaviour
|
// methods to get, set object behaviour
|
||||||
bool isForcingFullParse() const;
|
bool isForcingFullParse() const;
|
||||||
void setForceFullParse(bool forceFullParse);
|
void setForceFullParse(bool forceFullParse);
|
||||||
|
size_t minPadding() const;
|
||||||
|
void setMinPadding(size_t minPadding);
|
||||||
|
size_t maxPadding() const;
|
||||||
|
void setMaxPadding(size_t maxPadding);
|
||||||
|
TagPosition tagPosition() const;
|
||||||
|
void setTagPosition(TagPosition tagPosition);
|
||||||
|
bool forceTagPosition() const;
|
||||||
|
void setForceTagPosition(bool forceTagPosition);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void invalidated();
|
virtual void invalidated();
|
||||||
|
@ -164,6 +178,10 @@ private:
|
||||||
ParsingStatus m_attachmentsParsingStatus;
|
ParsingStatus m_attachmentsParsingStatus;
|
||||||
// fields specifying object behaviour
|
// fields specifying object behaviour
|
||||||
bool m_forceFullParse;
|
bool m_forceFullParse;
|
||||||
|
size_t m_minPadding;
|
||||||
|
size_t m_maxPadding;
|
||||||
|
TagPosition m_tagPosition;
|
||||||
|
bool m_forceTagPosition;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
@ -362,6 +380,99 @@ inline void MediaFileInfo::setForceFullParse(bool forceFullParse)
|
||||||
m_forceFullParse = forceFullParse;
|
m_forceFullParse = forceFullParse;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Returns the minimum padding to be written before the data blocks when applying changes.
|
||||||
|
*
|
||||||
|
* Padding in front of the file allows adding additional fields afterwards whithout needing
|
||||||
|
* to rewrite the entire file or to put tag information at the end of the file.
|
||||||
|
*
|
||||||
|
* \sa maxPadding()
|
||||||
|
* \sa tagPosition()
|
||||||
|
* \sa setMinPadding()
|
||||||
|
*/
|
||||||
|
inline size_t MediaFileInfo::minPadding() const
|
||||||
|
{
|
||||||
|
return m_minPadding;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Sets the minimum padding to be written before the data blocks when applying changes.
|
||||||
|
* \remarks This value might be ignored if not supported by the container/tag format or the corresponding implementation.
|
||||||
|
* \sa minPadding()
|
||||||
|
*/
|
||||||
|
inline void MediaFileInfo::setMinPadding(size_t minPadding)
|
||||||
|
{
|
||||||
|
m_minPadding = minPadding;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Returns the maximum padding to be written before the data blocks when applying changes.
|
||||||
|
*
|
||||||
|
* Padding in front of the file allows adding additional fields afterwards whithout needing
|
||||||
|
* to rewrite the entire file or to put tag information at the end of the file.
|
||||||
|
*
|
||||||
|
* \sa minPadding()
|
||||||
|
* \sa tagPosition()
|
||||||
|
* \sa setMaxPadding()
|
||||||
|
*/
|
||||||
|
inline size_t MediaFileInfo::maxPadding() const
|
||||||
|
{
|
||||||
|
return m_maxPadding;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Sets the maximum padding to be written before the data blocks when applying changes.
|
||||||
|
* \remarks This value might be ignored if not supported by the container/tag format or the corresponding implementation.
|
||||||
|
* \sa maxPadding()
|
||||||
|
*/
|
||||||
|
inline void MediaFileInfo::setMaxPadding(size_t maxPadding)
|
||||||
|
{
|
||||||
|
m_maxPadding = maxPadding;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Returns the position (in the output file) where the tag information is written when applying changes.
|
||||||
|
* \sa setTagPosition()
|
||||||
|
*/
|
||||||
|
inline TagPosition MediaFileInfo::tagPosition() const
|
||||||
|
{
|
||||||
|
return m_tagPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Sets the position (in the output file) where the tag information is written when applying changes.
|
||||||
|
*
|
||||||
|
* \remarks
|
||||||
|
* - If putting the tags at another position would prevent rewriting the entire file the specified position
|
||||||
|
* might not be used if forceTagPosition() is false.
|
||||||
|
* - However if the specified position is not supported by the container/tag format or by the implementation
|
||||||
|
* for the format it is ignored (even if forceTagPosition() is true).
|
||||||
|
*/
|
||||||
|
inline void MediaFileInfo::setTagPosition(TagPosition tagPosition)
|
||||||
|
{
|
||||||
|
m_tagPosition = tagPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Returns whether tagPosition() is forced.
|
||||||
|
* \sa setForceTagPosition()
|
||||||
|
* \sa tagPosition(), setTagPosition()
|
||||||
|
*/
|
||||||
|
inline bool MediaFileInfo::forceTagPosition() const
|
||||||
|
{
|
||||||
|
return m_forceTagPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Sets whether tagPosition() is forced.
|
||||||
|
* \sa forceTagPosition()
|
||||||
|
* \sa tagPosition(), setTagPosition()
|
||||||
|
*/
|
||||||
|
inline void MediaFileInfo::setForceTagPosition(bool forceTagPosition)
|
||||||
|
{
|
||||||
|
m_forceTagPosition = forceTagPosition;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // MEDIAINFO_H
|
#endif // MEDIAINFO_H
|
||||||
|
|
|
@ -78,7 +78,7 @@ void Mp4Atom::internalParse()
|
||||||
}
|
}
|
||||||
m_id = reader().readUInt32BE();
|
m_id = reader().readUInt32BE();
|
||||||
m_idLength = 4;
|
m_idLength = 4;
|
||||||
if(dataSize() == 1) { // atom denotes 64-bit size
|
if(m_dataSize == 1) { // atom denotes 64-bit size
|
||||||
m_dataSize = reader().readUInt64BE();
|
m_dataSize = reader().readUInt64BE();
|
||||||
m_sizeLength = 12; // 4 bytes indicate long size denotation + 8 bytes for actual size denotation
|
m_sizeLength = 12; // 4 bytes indicate long size denotation + 8 bytes for actual size denotation
|
||||||
if(dataSize() < 16 && m_dataSize != 1) {
|
if(dataSize() < 16 && m_dataSize != 1) {
|
||||||
|
@ -116,24 +116,37 @@ void Mp4Atom::internalParse()
|
||||||
* \brief This function helps to write the atom size after writing an atom to a stream.
|
* \brief This function helps to write the atom size after writing an atom to a stream.
|
||||||
* \param stream Specifies the stream.
|
* \param stream Specifies the stream.
|
||||||
* \param startOffset Specifies the start offset of the atom.
|
* \param startOffset Specifies the start offset of the atom.
|
||||||
* \param denote64BitSize Specifies whether the atom denotes its size with a 64-bit unsigned integer.
|
|
||||||
*
|
*
|
||||||
* This function seeks back to the start offset and writes the difference between the
|
* This function seeks back to the start offset and writes the difference between the
|
||||||
* previous offset and the start offset as 32-bit unsigned integer to the \a stream.
|
* previous offset and the start offset as 32-bit unsigned integer to the \a stream.
|
||||||
* Then it seeks back to the previous offset.
|
* Then it seeks back to the previous offset.
|
||||||
*/
|
*/
|
||||||
void Mp4Atom::seekBackAndWriteAtomSize(std::ostream &stream, const ostream::pos_type &startOffset, bool denote64BitSize)
|
void Mp4Atom::seekBackAndWriteAtomSize(std::ostream &stream, const ostream::pos_type &startOffset)
|
||||||
{
|
{
|
||||||
ostream::pos_type currentOffset = stream.tellp();
|
ostream::pos_type currentOffset = stream.tellp();
|
||||||
stream.seekp(startOffset);
|
stream.seekp(startOffset);
|
||||||
BinaryWriter writer(&stream);
|
BinaryWriter writer(&stream);
|
||||||
if(denote64BitSize) {
|
writer.writeUInt32BE(currentOffset - startOffset);
|
||||||
writer.writeUInt32BE(0);
|
stream.seekp(currentOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief This function helps to write the atom size after writing an atom to a stream.
|
||||||
|
* \param stream Specifies the stream.
|
||||||
|
* \param startOffset Specifies the start offset of the atom.
|
||||||
|
*
|
||||||
|
* This function seeks back to the start offset and writes the difference between the
|
||||||
|
* previous offset and the start offset as 64-bit unsigned integer to the \a stream.
|
||||||
|
* Then it seeks back to the previous offset.
|
||||||
|
*/
|
||||||
|
void Mp4Atom::seekBackAndWriteAtomSize64(std::ostream &stream, const ostream::pos_type &startOffset)
|
||||||
|
{
|
||||||
|
ostream::pos_type currentOffset = stream.tellp();
|
||||||
|
stream.seekp(startOffset);
|
||||||
|
BinaryWriter writer(&stream);
|
||||||
|
writer.writeUInt32BE(1);
|
||||||
stream.seekp(4, ios_base::cur);
|
stream.seekp(4, ios_base::cur);
|
||||||
writer.writeUInt64BE(currentOffset - startOffset);
|
writer.writeUInt64BE(currentOffset - startOffset);
|
||||||
} else {
|
|
||||||
writer.writeUInt32BE(currentOffset - startOffset);
|
|
||||||
}
|
|
||||||
stream.seekp(currentOffset);
|
stream.seekp(currentOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -66,7 +66,8 @@ public:
|
||||||
bool isPadding() const;
|
bool isPadding() const;
|
||||||
uint64 firstChildOffset() const;
|
uint64 firstChildOffset() const;
|
||||||
|
|
||||||
static void seekBackAndWriteAtomSize(std::ostream &stream, const std::ostream::pos_type &startOffset, bool denote64BitSize = false);
|
static void seekBackAndWriteAtomSize(std::ostream &stream, const std::ostream::pos_type &startOffset);
|
||||||
|
static void seekBackAndWriteAtomSize64(std::ostream &stream, const std::ostream::pos_type &startOffset);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Mp4Atom(containerType& container, uint64 startOffset, uint64 maxSize);
|
Mp4Atom(containerType& container, uint64 startOffset, uint64 maxSize);
|
||||||
|
|
|
@ -211,17 +211,17 @@ void Mp4Container::internalMakeFile()
|
||||||
setStream(backupStream);
|
setStream(backupStream);
|
||||||
// recreate original file
|
// recreate original file
|
||||||
outputStream.open(fileInfo().path(), ios_base::out | ios_base::binary | ios_base::trunc);
|
outputStream.open(fileInfo().path(), ios_base::out | ios_base::binary | ios_base::trunc);
|
||||||
// collect needed atoms
|
// collect needed atoms from the original file
|
||||||
Mp4Atom *ftypAtom, *pdinAtom, *moovAtom;
|
Mp4Atom *ftypAtom, *pdinAtom, *moovAtom;
|
||||||
try {
|
try {
|
||||||
ftypAtom = firstElement()->siblingById(Mp4AtomIds::FileType, true); // mandatory
|
ftypAtom = firstElement()->siblingById(Mp4AtomIds::FileType, true); // mandatory
|
||||||
if(!ftypAtom) { // throw error if missing
|
if(!ftypAtom) { // throw error if missing
|
||||||
addNotification(NotificationType::Critical, "Mandatory ftyp atom not found.", context);
|
addNotification(NotificationType::Critical, "Mandatory \"ftyp\"-atom not found.", context);
|
||||||
}
|
}
|
||||||
pdinAtom = firstElement()->siblingById(Mp4AtomIds::ProgressiveDownloadInformation, true); // not mandatory
|
pdinAtom = firstElement()->siblingById(Mp4AtomIds::ProgressiveDownloadInformation, true); // not mandatory
|
||||||
moovAtom = firstElement()->siblingById(Mp4AtomIds::Movie, true); // mandatory
|
moovAtom = firstElement()->siblingById(Mp4AtomIds::Movie, true); // mandatory
|
||||||
if(!moovAtom) { // throw error if missing
|
if(!moovAtom) { // throw error if missing
|
||||||
addNotification(NotificationType::Critical, "Mandatory moov atom not found.", context);
|
addNotification(NotificationType::Critical, "Mandatory \"moov\"-atom not found.", context);
|
||||||
throw InvalidDataException();
|
throw InvalidDataException();
|
||||||
}
|
}
|
||||||
} catch (Failure &) {
|
} catch (Failure &) {
|
||||||
|
@ -231,20 +231,29 @@ void Mp4Container::internalMakeFile()
|
||||||
if(m_tags.size() > 1) {
|
if(m_tags.size() > 1) {
|
||||||
addNotification(NotificationType::Warning, "There are multiple MP4-tags assigned. Only the first one will be attached to the file.", context);
|
addNotification(NotificationType::Warning, "There are multiple MP4-tags assigned. Only the first one will be attached to the file.", context);
|
||||||
}
|
}
|
||||||
// write all top-level atoms, header boxes be placed first
|
// write all top-level atoms
|
||||||
updateStatus("Writing header ...");
|
updateStatus("Writing header ...");
|
||||||
|
// write "ftype"-atom
|
||||||
ftypAtom->copyEntirely(outputStream);
|
ftypAtom->copyEntirely(outputStream);
|
||||||
|
// write "pdin"-atom ("progressive download information")
|
||||||
if(pdinAtom) {
|
if(pdinAtom) {
|
||||||
pdinAtom->copyEntirely(outputStream);
|
pdinAtom->copyEntirely(outputStream);
|
||||||
}
|
}
|
||||||
|
// prepare for writing the actual data
|
||||||
|
vector<tuple<istream *, vector<uint64>, vector<uint64> > > trackInfos; // used when writing chunk-by-chunk
|
||||||
|
uint64 totalChunkCount = 0; // used when writing chunk-by-chunk
|
||||||
|
vector<int64> origMdatOffsets; // used when simply copying mdat
|
||||||
|
vector<int64> newMdatOffsets; // used when simply copying mdat
|
||||||
|
auto trackCount = tracks().size();
|
||||||
|
for(byte pass = 0; pass != 2; ++pass) {
|
||||||
|
if(fileInfo().tagPosition() == (pass ? TagPosition::AfterData : TagPosition::BeforeData)) {
|
||||||
|
// write "moov"-atom (contains track and tag information)
|
||||||
ostream::pos_type newMoovOffset = outputStream.tellp();
|
ostream::pos_type newMoovOffset = outputStream.tellp();
|
||||||
Mp4Atom *udtaAtom = nullptr;
|
Mp4Atom *udtaAtom = nullptr;
|
||||||
uint64 newUdtaOffset = 0u;
|
uint64 newUdtaOffset = 0u;
|
||||||
if(isAborted()) {
|
// -> write child atoms manually, because the child "udta" has to be altered/ignored
|
||||||
throw OperationAbortedException();
|
|
||||||
}
|
|
||||||
moovAtom->copyWithoutChilds(outputStream);
|
moovAtom->copyWithoutChilds(outputStream);
|
||||||
for(Mp4Atom *moovChildAtom = moovAtom->firstChild(); moovChildAtom; moovChildAtom = moovChildAtom->nextSibling()) { // write child atoms manually, because the child udta has to be altered/ignored
|
for(Mp4Atom *moovChildAtom = moovAtom->firstChild(); moovChildAtom; moovChildAtom = moovChildAtom->nextSibling()) {
|
||||||
try {
|
try {
|
||||||
moovChildAtom->parse();
|
moovChildAtom->parse();
|
||||||
} catch(Failure &) {
|
} catch(Failure &) {
|
||||||
|
@ -252,12 +261,14 @@ void Mp4Container::internalMakeFile()
|
||||||
throw InvalidDataException();
|
throw InvalidDataException();
|
||||||
}
|
}
|
||||||
if(moovChildAtom->id() == Mp4AtomIds::UserData) {
|
if(moovChildAtom->id() == Mp4AtomIds::UserData) {
|
||||||
// found a udta (user data) atom which childs hold tag infromation
|
// found a "udta" (user data) atom which child "meta" holds tag information
|
||||||
if(!udtaAtom) {
|
if(!udtaAtom) {
|
||||||
udtaAtom = moovChildAtom;
|
udtaAtom = moovChildAtom;
|
||||||
// check if the udta atom needs to be written
|
// check whether the "udta"-atom needs to be written
|
||||||
bool writeUdtaAtom = !m_tags.empty(); // it has to be written only when a MP4 tag is assigned
|
// it has to be written only when an MP4 tag is assigned
|
||||||
if(!writeUdtaAtom) { // or when there is at least one child except the meta atom in the original file
|
bool writeUdtaAtom = !m_tags.empty();
|
||||||
|
// or when there is at least one child except the meta atom in the original file
|
||||||
|
if(!writeUdtaAtom) {
|
||||||
try {
|
try {
|
||||||
for(Mp4Atom *udtaChildAtom = udtaAtom->firstChild(); udtaChildAtom; udtaChildAtom = udtaChildAtom->nextSibling()) {
|
for(Mp4Atom *udtaChildAtom = udtaAtom->firstChild(); udtaChildAtom; udtaChildAtom = udtaChildAtom->nextSibling()) {
|
||||||
udtaChildAtom->parse();
|
udtaChildAtom->parse();
|
||||||
|
@ -268,7 +279,7 @@ void Mp4Container::internalMakeFile()
|
||||||
}
|
}
|
||||||
} catch(Failure &) {
|
} catch(Failure &) {
|
||||||
addNotification(NotificationType::Warning,
|
addNotification(NotificationType::Warning,
|
||||||
"Unable to parse childs of udta atom of original file. These invalid/unknown atoms will be ignored.", context);
|
"Unable to parse childs of \"udta\"-atom atom of original file. These invalid/unknown atoms will be ignored.", context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(writeUdtaAtom) {
|
if(writeUdtaAtom) {
|
||||||
|
@ -284,30 +295,31 @@ void Mp4Container::internalMakeFile()
|
||||||
}
|
}
|
||||||
addNotifications(*m_tags.front());
|
addNotifications(*m_tags.front());
|
||||||
}
|
}
|
||||||
// write rest of the child atoms of udta atom
|
// write rest of the child atoms of "udta"-atom
|
||||||
try {
|
try {
|
||||||
for(Mp4Atom *udtaChildAtom = udtaAtom->firstChild(); udtaChildAtom; udtaChildAtom = udtaChildAtom->nextSibling()) {
|
for(Mp4Atom *udtaChildAtom = udtaAtom->firstChild(); udtaChildAtom; udtaChildAtom = udtaChildAtom->nextSibling()) {
|
||||||
udtaChildAtom->parse();
|
udtaChildAtom->parse();
|
||||||
if(udtaChildAtom->id() != Mp4AtomIds::Meta) { // skip meta atoms here of course
|
// skip "meta"-atoms here of course
|
||||||
|
if(udtaChildAtom->id() != Mp4AtomIds::Meta) {
|
||||||
udtaChildAtom->copyEntirely(outputStream);
|
udtaChildAtom->copyEntirely(outputStream);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch(Failure &) {
|
} catch(Failure &) {
|
||||||
addNotification(NotificationType::Warning,
|
addNotification(NotificationType::Warning,
|
||||||
"Unable to parse childs of udta atom of original file. These will be ignored.", context);
|
"Unable to parse childs of \"udta\"-atom of original file. These will be ignored.", context);
|
||||||
}
|
}
|
||||||
// write correct size of udta atom
|
// write correct size of udta atom
|
||||||
Mp4Atom::seekBackAndWriteAtomSize(outputStream, newUdtaOffset);
|
Mp4Atom::seekBackAndWriteAtomSize(outputStream, newUdtaOffset);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
addNotification(NotificationType::Warning, "The source file has multiple udta atoms. Surplus atoms will be ignored.", context);
|
addNotification(NotificationType::Warning, "The source file has multiple \"udta\"-atoms. Surplus atoms will be ignored.", context);
|
||||||
}
|
}
|
||||||
} else if(!writeChunkByChunk || moovChildAtom->id() != Mp4AtomIds::Track) {
|
} else if(!writeChunkByChunk || moovChildAtom->id() != Mp4AtomIds::Track) {
|
||||||
// copy trak atoms only when not writing the data chunk-by-chunk
|
// copy "trak"-atoms only when not writing the data chunk-by-chunk
|
||||||
moovChildAtom->copyEntirely(outputStream);
|
moovChildAtom->copyEntirely(outputStream);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// the original file has no udta atom but there is tag information to be written
|
// -> the original file has no udta atom but there is tag information to be written
|
||||||
if(!udtaAtom && !m_tags.empty()) {
|
if(!udtaAtom && !m_tags.empty()) {
|
||||||
updateStatus("Writing tag information ...");
|
updateStatus("Writing tag information ...");
|
||||||
newUdtaOffset = outputStream.tellp();
|
newUdtaOffset = outputStream.tellp();
|
||||||
|
@ -320,27 +332,19 @@ void Mp4Container::internalMakeFile()
|
||||||
Mp4Atom::seekBackAndWriteAtomSize(outputStream, newUdtaOffset);
|
Mp4Atom::seekBackAndWriteAtomSize(outputStream, newUdtaOffset);
|
||||||
} catch(Failure &) {
|
} catch(Failure &) {
|
||||||
addNotification(NotificationType::Warning, "Unable to write meta atom (of assigned mp4 tag).", context);
|
addNotification(NotificationType::Warning, "Unable to write meta atom (of assigned mp4 tag).", context);
|
||||||
outputStream.seekp(-8, ios_base::cur);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// write trak atoms for each currently assigned track, this is only required when writing data chunk-by-chunk
|
// -> write trak atoms for each currently assigned track (this is only required when writing data chunk-by-chunk)
|
||||||
if(writeChunkByChunk) {
|
if(writeChunkByChunk) {
|
||||||
updateStatus("Writing meta information for the tracks ...");
|
updateStatus("Writing meta information for the tracks ...");
|
||||||
for(auto &track : tracks()) {
|
for(auto &track : tracks()) {
|
||||||
if(isAborted()) {
|
|
||||||
throw OperationAbortedException();
|
|
||||||
}
|
|
||||||
track->setOutputStream(outputStream);
|
track->setOutputStream(outputStream);
|
||||||
track->makeTrack();
|
track->makeTrack();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Mp4Atom::seekBackAndWriteAtomSize(outputStream, newMoovOffset);
|
Mp4Atom::seekBackAndWriteAtomSize(outputStream, newMoovOffset);
|
||||||
// prepare for writing the actual data
|
} else {
|
||||||
vector<tuple<istream *, vector<uint64>, vector<uint64> > > trackInfos; // used when writing chunk-by-chunk
|
// write other atoms and "mdat"-atom (holds actual data)
|
||||||
uint64 totalChunkCount; // used when writing chunk-by-chunk
|
|
||||||
vector<int64> origMdatOffsets; // used when simply copying mdat
|
|
||||||
vector<int64> newMdatOffsets; // used when simply copying mdat
|
|
||||||
// write other atoms
|
|
||||||
for(Mp4Atom *otherTopLevelAtom = firstElement(); otherTopLevelAtom; otherTopLevelAtom = otherTopLevelAtom->nextSibling()) {
|
for(Mp4Atom *otherTopLevelAtom = firstElement(); otherTopLevelAtom; otherTopLevelAtom = otherTopLevelAtom->nextSibling()) {
|
||||||
if(isAborted()) {
|
if(isAborted()) {
|
||||||
throw OperationAbortedException();
|
throw OperationAbortedException();
|
||||||
|
@ -373,11 +377,14 @@ void Mp4Container::internalMakeFile()
|
||||||
if(writeChunkByChunk) {
|
if(writeChunkByChunk) {
|
||||||
// get the chunk offset and the chunk size table from the old file to be able to write single chunks later ...
|
// get the chunk offset and the chunk size table from the old file to be able to write single chunks later ...
|
||||||
updateStatus("Reading chunk offsets and sizes from the original file ...");
|
updateStatus("Reading chunk offsets and sizes from the original file ...");
|
||||||
trackInfos.reserve(tracks().size());
|
trackInfos.reserve(trackCount);
|
||||||
totalChunkCount = 0;
|
|
||||||
for(auto &track : tracks()) {
|
for(auto &track : tracks()) {
|
||||||
|
if(isAborted()) {
|
||||||
|
throw OperationAbortedException();
|
||||||
|
}
|
||||||
|
// ensure the track reads from the original file
|
||||||
if(&track->inputStream() == &outputStream) {
|
if(&track->inputStream() == &outputStream) {
|
||||||
track->setInputStream(backupStream); // ensure the track reads from the original file
|
track->setInputStream(backupStream);
|
||||||
}
|
}
|
||||||
trackInfos.emplace_back(&track->inputStream(), track->readChunkOffsets(), track->readChunkSizes());
|
trackInfos.emplace_back(&track->inputStream(), track->readChunkOffsets(), track->readChunkSizes());
|
||||||
const vector<uint64> &chunkOffsetTable = get<1>(trackInfos.back());
|
const vector<uint64> &chunkOffsetTable = get<1>(trackInfos.back());
|
||||||
|
@ -387,13 +394,49 @@ void Mp4Container::internalMakeFile()
|
||||||
addNotification(NotificationType::Critical, "Chunks of track " + numberToString<uint64, string>(track->id()) + " could not be parsed correctly.", context);
|
addNotification(NotificationType::Critical, "Chunks of track " + numberToString<uint64, string>(track->id()) + " could not be parsed correctly.", context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
// writing single chunks is needed when tracks have been added or removed
|
||||||
|
updateStatus("Writing chunks to mdat atom ...");
|
||||||
|
//outputStream.seekp(0, ios_base::end);
|
||||||
|
ostream::pos_type newMdatOffset = outputStream.tellp();
|
||||||
|
writer().writeUInt32BE(1); // denote 64 bit size
|
||||||
|
outputWriter.writeUInt32BE(Mp4AtomIds::MediaData);
|
||||||
|
outputWriter.writeUInt64BE(0); // write size of mdat atom later
|
||||||
|
CopyHelper<0x2000> copyHelper;
|
||||||
|
uint64 chunkIndex = 0;
|
||||||
|
uint64 totalChunksCopied = 0;
|
||||||
|
bool chunksCopied;
|
||||||
|
do {
|
||||||
if(isAborted()) {
|
if(isAborted()) {
|
||||||
throw OperationAbortedException();
|
throw OperationAbortedException();
|
||||||
}
|
}
|
||||||
// reparse what is written so far
|
chunksCopied = false;
|
||||||
|
for(size_t trackIndex = 0; trackIndex < trackCount; ++trackIndex) {
|
||||||
|
//auto &track = tracks()[trackIndex];
|
||||||
|
auto &trackInfo = trackInfos[trackIndex];
|
||||||
|
istream &sourceStream = *get<0>(trackInfo);
|
||||||
|
vector<uint64> &chunkOffsetTable = get<1>(trackInfo);
|
||||||
|
const vector<uint64> &chunkSizesTable = get<2>(trackInfo);
|
||||||
|
if(chunkIndex < chunkOffsetTable.size() && chunkIndex < chunkSizesTable.size()) {
|
||||||
|
sourceStream.seekg(chunkOffsetTable[chunkIndex]);
|
||||||
|
//outputStream.seekp(0, ios_base::end);
|
||||||
|
chunkOffsetTable[chunkIndex] = outputStream.tellp();
|
||||||
|
copyHelper.copy(sourceStream, outputStream, chunkSizesTable[chunkIndex]);
|
||||||
|
//track->updateChunkOffset(chunkIndex, chunkOffset);
|
||||||
|
chunksCopied = true;
|
||||||
|
++totalChunksCopied;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
++chunkIndex;
|
||||||
|
updatePercentage(static_cast<double>(totalChunksCopied) / totalChunkCount);
|
||||||
|
} while(chunksCopied);
|
||||||
|
//outputStream.seekp(0, ios_base::end);
|
||||||
|
Mp4Atom::seekBackAndWriteAtomSize64(outputStream, newMdatOffset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// reparse new file
|
||||||
updateStatus("Reparsing output file ...");
|
updateStatus("Reparsing output file ...");
|
||||||
outputStream.close(); // the outputStream needs to be reopened to be able to read again
|
outputStream.close(); // outputStream needs to be reopened to be able to read again
|
||||||
outputStream.open(fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary);
|
outputStream.open(fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary);
|
||||||
setStream(outputStream);
|
setStream(outputStream);
|
||||||
m_headerParsed = false;
|
m_headerParsed = false;
|
||||||
|
@ -406,54 +449,27 @@ void Mp4Container::internalMakeFile()
|
||||||
addNotification(NotificationType::Critical, "Unable to reparse the header of the new file.", context);
|
addNotification(NotificationType::Critical, "Unable to reparse the header of the new file.", context);
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
// update chunk offsets in the "stco"-atom of each track
|
||||||
|
if(trackCount != tracks().size()) {
|
||||||
|
stringstream error;
|
||||||
|
error << "Unable to update chunk offsets (\"stco\"-atom): Number of tracks in the output file (" << tracks().size()
|
||||||
|
<< ") differs from the number of tracks in the original file (" << trackCount << ").";
|
||||||
|
addNotification(NotificationType::Critical, error.str(), context);
|
||||||
|
throw Failure();
|
||||||
|
}
|
||||||
if(writeChunkByChunk) {
|
if(writeChunkByChunk) {
|
||||||
// checking parsed tracks
|
updateStatus("Updating chunk offset table for each track ...");
|
||||||
size_t trackCount = tracks().size();
|
|
||||||
if(trackCount != trackInfos.size()) {
|
|
||||||
if(trackCount > trackInfos.size()) {
|
|
||||||
trackCount = trackInfos.size();
|
|
||||||
}
|
|
||||||
addNotification(NotificationType::Critical, "The track meta data could not be written correctly. Trying to write the chunk data anyways.", context);
|
|
||||||
}
|
|
||||||
// writing single chunks is needed when tracks have been added or removed
|
|
||||||
updateStatus("Writing chunks to mdat atom ...");
|
|
||||||
outputStream.seekp(0, ios_base::end);
|
|
||||||
ostream::pos_type newMdatOffset = outputStream.tellp();
|
|
||||||
writer().writeUInt32BE(0); // denote 64 bit size
|
|
||||||
writer().writeUInt32BE(Mp4AtomIds::MediaData);
|
|
||||||
writer().writeUInt64BE(0); // write size of mdat atom later
|
|
||||||
CopyHelper<0x2000> copyHelper;
|
|
||||||
uint64 chunkIndex = 0;
|
|
||||||
uint64 totalChunksCopied = 0;
|
|
||||||
bool chunksCopied;
|
|
||||||
do {
|
|
||||||
if(isAborted()) {
|
|
||||||
throw OperationAbortedException();
|
|
||||||
}
|
|
||||||
chunksCopied = false;
|
|
||||||
for(size_t trackIndex = 0; trackIndex < trackCount; ++trackIndex) {
|
for(size_t trackIndex = 0; trackIndex < trackCount; ++trackIndex) {
|
||||||
auto &track = tracks()[trackIndex];
|
auto &track = tracks()[trackIndex];
|
||||||
auto &trackInfo = trackInfos[trackIndex];
|
auto &chunkOffsetTable = get<1>(trackInfos[trackIndex]);
|
||||||
istream &sourceStream = *get<0>(trackInfo);
|
if(track->chunkCount() == chunkOffsetTable.size()) {
|
||||||
const vector<uint64> &chunkOffsetTable = get<1>(trackInfo);
|
track->updateChunkOffsets(chunkOffsetTable);
|
||||||
const vector<uint64> &chunkSizesTable = get<2>(trackInfo);
|
} else {
|
||||||
if(chunkIndex < chunkOffsetTable.size() && chunkIndex < chunkSizesTable.size()) {
|
addNotification(NotificationType::Critical, "Unable to update chunk offsets of track " + numberToString(trackIndex + 1) + ": Number of chunks in the output file differs from the number of chunks in the orignal file.", context);
|
||||||
sourceStream.seekg(chunkOffsetTable[chunkIndex]);
|
throw Failure();
|
||||||
outputStream.seekp(0, ios_base::end);
|
}
|
||||||
uint64 chunkOffset = outputStream.tellp();
|
}
|
||||||
copyHelper.copy(sourceStream, outputStream, chunkSizesTable[chunkIndex]);
|
|
||||||
track->updateChunkOffset(chunkIndex, chunkOffset);
|
|
||||||
chunksCopied = true;
|
|
||||||
++totalChunksCopied;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
++chunkIndex;
|
|
||||||
updatePercentage(static_cast<double>(totalChunksCopied) / totalChunkCount);
|
|
||||||
} while(chunksCopied);
|
|
||||||
outputStream.seekp(0, ios_base::end);
|
|
||||||
Mp4Atom::seekBackAndWriteAtomSize(outputStream, newMdatOffset, true);
|
|
||||||
} else {
|
} else {
|
||||||
// correct mdat offsets in the stco atom of each track when we've just copied the mdat atom
|
|
||||||
updateOffsets(origMdatOffsets, newMdatOffsets);
|
updateOffsets(origMdatOffsets, newMdatOffsets);
|
||||||
}
|
}
|
||||||
updatePercentage(100.0);
|
updatePercentage(100.0);
|
||||||
|
@ -494,7 +510,7 @@ void Mp4Container::internalMakeFile()
|
||||||
* \param oldMdatOffsets Specifies a vector holding the old offsets of the "mdat"-atoms.
|
* \param oldMdatOffsets Specifies a vector holding the old offsets of the "mdat"-atoms.
|
||||||
* \param newMdatOffsets Specifies a vector holding the new offsets of the "mdat"-atoms.
|
* \param newMdatOffsets Specifies a vector holding the new offsets of the "mdat"-atoms.
|
||||||
*
|
*
|
||||||
* Internally uses Mp4Track::updateOffsets(). Offsets stored in the "tfhd"-atom are also
|
* Uses internally Mp4Track::updateOffsets(). Offsets stored in the "tfhd"-atom are also
|
||||||
* updated (this is not tested yet since I don't have files using this atom).
|
* updated (this is not tested yet since I don't have files using this atom).
|
||||||
*
|
*
|
||||||
* \throws Throws std::ios_base::failure when an IO error occurs.
|
* \throws Throws std::ios_base::failure when an IO error occurs.
|
||||||
|
@ -510,7 +526,7 @@ void Mp4Container::updateOffsets(const std::vector<int64> &oldMdatOffsets, const
|
||||||
addNotification(NotificationType::Critical, "No MP4 atoms could be found.", context);
|
addNotification(NotificationType::Critical, "No MP4 atoms could be found.", context);
|
||||||
throw InvalidDataException();
|
throw InvalidDataException();
|
||||||
}
|
}
|
||||||
// update "base-data-offset-present" of tfhd atom (NOT tested properly)
|
// update "base-data-offset-present" of "tfhd"-atom (NOT tested properly)
|
||||||
try {
|
try {
|
||||||
for(Mp4Atom *moofAtom = firstElement()->siblingById(Mp4AtomIds::MovieFragment, false);
|
for(Mp4Atom *moofAtom = firstElement()->siblingById(Mp4AtomIds::MovieFragment, false);
|
||||||
moofAtom; moofAtom = moofAtom->siblingById(Mp4AtomIds::MovieFragment, false)) {
|
moofAtom; moofAtom = moofAtom->siblingById(Mp4AtomIds::MovieFragment, false)) {
|
||||||
|
@ -574,14 +590,16 @@ void Mp4Container::updateOffsets(const std::vector<int64> &oldMdatOffsets, const
|
||||||
try {
|
try {
|
||||||
track->parseHeader();
|
track->parseHeader();
|
||||||
} catch(Failure &) {
|
} catch(Failure &) {
|
||||||
addNotification(NotificationType::Warning, "The chunk offsets of track " + track->name() + " couldn't be updated because the track seems to be invalid. The newly written file seems to be damaged.", context);
|
addNotification(NotificationType::Warning, "The chunk offsets of track " + track->name() + " couldn't be updated because the track seems to be invalid..", context);
|
||||||
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(track->isHeaderValid()) {
|
if(track->isHeaderValid()) {
|
||||||
try {
|
try {
|
||||||
track->updateChunkOffsets(oldMdatOffsets, newMdatOffsets);
|
track->updateChunkOffsets(oldMdatOffsets, newMdatOffsets);
|
||||||
} catch(Failure &) {
|
} catch(Failure &) {
|
||||||
addNotification(NotificationType::Warning, "The chunk offsets of track " + track->name() + " couldn't be updated. The altered file is damaged now, restore the backup!", context);
|
addNotification(NotificationType::Warning, "The chunk offsets of track " + track->name() + " couldn't be updated.", context);
|
||||||
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -290,6 +290,9 @@ uint32 mpeg4SamplingFrequencyTable[] = {
|
||||||
24000, 22050, 16000, 12000, 11025, 8000, 7350
|
24000, 22050, 16000, 12000, 11025, 8000, 7350
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Encapsulates all supported MPEG-4 channel configurations.
|
||||||
|
*/
|
||||||
namespace Mpeg4ChannelConfigs {
|
namespace Mpeg4ChannelConfigs {
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
|
|
@ -780,8 +780,8 @@ std::unique_ptr<Mpeg4VideoSpecificConfig> Mp4Track::parseVideoSpecificConfig(Sta
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief Updates the chunk offsets of the track. This is necessary when the mdat atom (which contains
|
* \brief Updates the chunk offsets of the track. This is necessary when the "mdat"-atom
|
||||||
* the actual chunk data) is moved.
|
* (which contains the actual chunk data) is moved.
|
||||||
* \param oldMdatOffsets Specifies a vector holding the old offsets of the "mdat"-atoms.
|
* \param oldMdatOffsets Specifies a vector holding the old offsets of the "mdat"-atoms.
|
||||||
* \param newMdatOffsets Specifies a vector holding the new offsets of the "mdat"-atoms.
|
* \param newMdatOffsets Specifies a vector holding the new offsets of the "mdat"-atoms.
|
||||||
*
|
*
|
||||||
|
@ -849,10 +849,53 @@ void Mp4Track::updateChunkOffsets(const vector<int64> &oldMdatOffsets, const vec
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Updates the chunk offsets of the track. This is necessary when the "mdat"-atom
|
||||||
|
* (which contains the actual chunk data) is moved.
|
||||||
|
* \param chunkOffsets Specifies the new chunk offset table.
|
||||||
|
*
|
||||||
|
* \throws Throws InvalidDataException when
|
||||||
|
* - there is no stream assigned.
|
||||||
|
* - the header has been considered as invalid when parsing the header information.
|
||||||
|
* - the size of \a chunkOffsets does not match chunkCount().
|
||||||
|
* - there is no atom holding these offsets.
|
||||||
|
* - the ID of the atom holding these offsets is not "stco" or "co64".
|
||||||
|
*/
|
||||||
|
void Mp4Track::updateChunkOffsets(const std::vector<uint64> &chunkOffsets)
|
||||||
|
{
|
||||||
|
if(!isHeaderValid() || !m_ostream || !m_istream || !m_stcoAtom) {
|
||||||
|
throw InvalidDataException();
|
||||||
|
}
|
||||||
|
if(chunkOffsets.size() != chunkCount()) {
|
||||||
|
throw InvalidDataException();
|
||||||
|
}
|
||||||
|
m_ostream->seekp(m_stcoAtom->dataOffset() + 8);
|
||||||
|
switch(m_stcoAtom->id()) {
|
||||||
|
case Mp4AtomIds::ChunkOffset:
|
||||||
|
for(auto offset : chunkOffsets) {
|
||||||
|
m_writer.writeUInt32BE(offset);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Mp4AtomIds::ChunkOffset64:
|
||||||
|
for(auto offset : chunkOffsets) {
|
||||||
|
m_writer.writeUInt64BE(offset);
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw InvalidDataException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief Updates a particular chunk offset.
|
* \brief Updates a particular chunk offset.
|
||||||
* \param chunkIndex Specifies the index of the chunk offset to be updated.
|
* \param chunkIndex Specifies the index of the chunk offset to be updated.
|
||||||
* \param offset Specifies the new chunk offset.
|
* \param offset Specifies the new chunk offset.
|
||||||
|
* \remarks This method seems to be obsolete.
|
||||||
|
* \throws Throws InvalidDataException when
|
||||||
|
* - there is no stream assigned.
|
||||||
|
* - the header has been considered as invalid when parsing the header information.
|
||||||
|
* - \a chunkIndex is not less than chunkCount().
|
||||||
|
* - there is no atom holding these offsets.
|
||||||
|
* - the ID of the atom holding these offsets is not "stco" or "co64".
|
||||||
*/
|
*/
|
||||||
void Mp4Track::updateChunkOffset(uint32 chunkIndex, uint64 offset)
|
void Mp4Track::updateChunkOffset(uint32 chunkIndex, uint64 offset)
|
||||||
{
|
{
|
||||||
|
@ -1006,7 +1049,7 @@ void Mp4Track::makeMedia()
|
||||||
// write minf atom
|
// write minf atom
|
||||||
makeMediaInfo();
|
makeMediaInfo();
|
||||||
// write size (of mdia atom)
|
// write size (of mdia atom)
|
||||||
Mp4Atom::seekBackAndWriteAtomSize(outputStream(), mdiaStartOffset, false);
|
Mp4Atom::seekBackAndWriteAtomSize(outputStream(), mdiaStartOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
@ -1060,7 +1103,7 @@ void Mp4Track::makeMediaInfo()
|
||||||
// write stbl atom
|
// write stbl atom
|
||||||
makeSampleTable();
|
makeSampleTable();
|
||||||
// write size (of minf atom)
|
// write size (of minf atom)
|
||||||
Mp4Atom::seekBackAndWriteAtomSize(outputStream(), minfStartOffset, false);
|
Mp4Atom::seekBackAndWriteAtomSize(outputStream(), minfStartOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
@ -1123,7 +1166,7 @@ void Mp4Track::makeSampleTable()
|
||||||
// write subs atom (sub-sample information)
|
// write subs atom (sub-sample information)
|
||||||
|
|
||||||
// write size (of stbl atom)
|
// write size (of stbl atom)
|
||||||
Mp4Atom::seekBackAndWriteAtomSize(outputStream(), stblStartOffset, false);
|
Mp4Atom::seekBackAndWriteAtomSize(outputStream(), stblStartOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Mp4Track::internalParseHeader()
|
void Mp4Track::internalParseHeader()
|
||||||
|
|
|
@ -156,6 +156,7 @@ public:
|
||||||
|
|
||||||
// methods to update chunk offsets
|
// methods to update chunk offsets
|
||||||
void updateChunkOffsets(const std::vector<int64> &oldMdatOffsets, const std::vector<int64> &newMdatOffsets);
|
void updateChunkOffsets(const std::vector<int64> &oldMdatOffsets, const std::vector<int64> &newMdatOffsets);
|
||||||
|
void updateChunkOffsets(const std::vector<uint64> &chunkOffsets);
|
||||||
void updateChunkOffset(uint32 chunkIndex, uint64 offset);
|
void updateChunkOffset(uint32 chunkIndex, uint64 offset);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
Loading…
Reference in New Issue