improved handling of "SegmentInfo"-element
This commit is contained in:
parent
7aea0e2a50
commit
4b13bac99c
|
@ -22,3 +22,4 @@ It also depends on zlib.
|
||||||
## TODO
|
## TODO
|
||||||
- Use padding to prevent rewriting the entire file to save tags.
|
- Use padding to prevent rewriting the entire file to save tags.
|
||||||
- Support more tag formats (EXIF, PDF metadata, ...).
|
- Support more tag formats (EXIF, PDF metadata, ...).
|
||||||
|
- Do tests with Matroska files which have multiple segments.
|
||||||
|
|
|
@ -412,7 +412,6 @@ size_t AbstractContainer::attachmentCount() const
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief Discards all parsing results.
|
* \brief Discards all parsing results.
|
||||||
*/
|
*/
|
||||||
|
@ -429,6 +428,7 @@ void AbstractContainer::reset()
|
||||||
m_doctypeVersion = 0;
|
m_doctypeVersion = 0;
|
||||||
m_doctypeReadVersion = 0;
|
m_doctypeReadVersion = 0;
|
||||||
m_timeScale = 0;
|
m_timeScale = 0;
|
||||||
|
m_titles.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Media
|
} // namespace Media
|
||||||
|
|
|
@ -74,7 +74,8 @@ public:
|
||||||
const std::string &documentType() const;
|
const std::string &documentType() const;
|
||||||
uint64 doctypeVersion() const;
|
uint64 doctypeVersion() const;
|
||||||
uint64 doctypeReadVersion() const;
|
uint64 doctypeReadVersion() const;
|
||||||
const std::string &title() const;
|
const std::vector<std::string> &titles() const;
|
||||||
|
void setTitle(const std::string &title, std::size_t segmentIndex = 0);
|
||||||
ChronoUtilities::TimeSpan duration() const;
|
ChronoUtilities::TimeSpan duration() const;
|
||||||
ChronoUtilities::DateTime creationTime() const;
|
ChronoUtilities::DateTime creationTime() const;
|
||||||
ChronoUtilities::DateTime modificationTime() const;
|
ChronoUtilities::DateTime modificationTime() const;
|
||||||
|
@ -97,7 +98,7 @@ protected:
|
||||||
std::string m_doctype;
|
std::string m_doctype;
|
||||||
uint64 m_doctypeVersion;
|
uint64 m_doctypeVersion;
|
||||||
uint64 m_doctypeReadVersion;
|
uint64 m_doctypeReadVersion;
|
||||||
std::string m_title;
|
std::vector<std::string> m_titles;
|
||||||
ChronoUtilities::TimeSpan m_duration;
|
ChronoUtilities::TimeSpan m_duration;
|
||||||
ChronoUtilities::DateTime m_creationTime;
|
ChronoUtilities::DateTime m_creationTime;
|
||||||
ChronoUtilities::DateTime m_modificationTime;
|
ChronoUtilities::DateTime m_modificationTime;
|
||||||
|
@ -243,12 +244,28 @@ inline uint64 AbstractContainer::doctypeReadVersion() const
|
||||||
return m_doctypeReadVersion;
|
return m_doctypeReadVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief Returns the title if known; otherwise returns an empty string.
|
* \brief Returns the title(s) of the file.
|
||||||
|
* \remarks
|
||||||
|
* - If the container does not support titles an empty vector will be returned.
|
||||||
|
* - If there are multiple segments, the title of each segment is returned.
|
||||||
|
* \sa setTitle()
|
||||||
*/
|
*/
|
||||||
inline const std::string &AbstractContainer::title() const
|
inline const std::vector<std::string> &AbstractContainer::titles() const
|
||||||
{
|
{
|
||||||
return m_title;
|
return m_titles;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Sets the title for the specified segment.
|
||||||
|
* \remarks The title is ignored if it is not supported by the concrete container format.
|
||||||
|
* \throws Throws out_of_range if the segment does not exist.
|
||||||
|
* \sa titles()
|
||||||
|
*/
|
||||||
|
inline void AbstractContainer::setTitle(const std::string &title, std::size_t segmentIndex)
|
||||||
|
{
|
||||||
|
m_titles.at(segmentIndex) = title;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
|
|
@ -355,7 +355,7 @@ byte EbmlElement::makeUInteger(uint64 value, char *buff)
|
||||||
* \brief Makes a simple EBML element.
|
* \brief Makes a simple EBML element.
|
||||||
* \param stream Specifies the stream to write the data to.
|
* \param stream Specifies the stream to write the data to.
|
||||||
* \param id Specifies the element ID.
|
* \param id Specifies the element ID.
|
||||||
* \param content Specifies the value of the element which is a unsigned integer (max. 64-bit).
|
* \param content Specifies the value of the element as unsigned integer.
|
||||||
*/
|
*/
|
||||||
void EbmlElement::makeSimpleElement(ostream &stream, identifierType id, uint64 content)
|
void EbmlElement::makeSimpleElement(ostream &stream, identifierType id, uint64 content)
|
||||||
{
|
{
|
||||||
|
@ -373,7 +373,7 @@ void EbmlElement::makeSimpleElement(ostream &stream, identifierType id, uint64 c
|
||||||
* \brief Makes a simple EBML element.
|
* \brief Makes a simple EBML element.
|
||||||
* \param stream Specifies the stream to write the data to.
|
* \param stream Specifies the stream to write the data to.
|
||||||
* \param id Specifies the element ID.
|
* \param id Specifies the element ID.
|
||||||
* \param content Specifies the string value of the element.
|
* \param content Specifies the value of the element as string.
|
||||||
*/
|
*/
|
||||||
void EbmlElement::makeSimpleElement(ostream &stream, GenericFileElement::identifierType id, const string &content)
|
void EbmlElement::makeSimpleElement(ostream &stream, GenericFileElement::identifierType id, const string &content)
|
||||||
{
|
{
|
||||||
|
@ -385,6 +385,23 @@ void EbmlElement::makeSimpleElement(ostream &stream, GenericFileElement::identif
|
||||||
stream.write(content.c_str(), content.size());
|
stream.write(content.c_str(), content.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Makes a simple EBML element.
|
||||||
|
* \param stream Specifies the stream to write the data to.
|
||||||
|
* \param id Specifies the element ID.
|
||||||
|
* \param data Specifies the data of the element.
|
||||||
|
* \param dataSize Specifies the size of \a data.
|
||||||
|
*/
|
||||||
|
void EbmlElement::makeSimpleElement(ostream &stream, GenericFileElement::identifierType id, const char *data, std::size_t dataSize)
|
||||||
|
{
|
||||||
|
char buff1[8];
|
||||||
|
byte sizeLength = EbmlElement::makeId(id, buff1);
|
||||||
|
stream.write(buff1, sizeLength);
|
||||||
|
sizeLength = EbmlElement::makeSizeDenotation(dataSize, buff1);
|
||||||
|
stream.write(buff1, sizeLength);
|
||||||
|
stream.write(data, dataSize);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -70,6 +70,7 @@ public:
|
||||||
static byte makeUInteger(uint64 value, char *buff);
|
static byte makeUInteger(uint64 value, char *buff);
|
||||||
static void makeSimpleElement(std::ostream &stream, identifierType id, uint64 content);
|
static void makeSimpleElement(std::ostream &stream, identifierType id, uint64 content);
|
||||||
static void makeSimpleElement(std::ostream &stream, identifierType id, const std::string &content);
|
static void makeSimpleElement(std::ostream &stream, identifierType id, const std::string &content);
|
||||||
|
static void makeSimpleElement(std::ostream &stream, GenericFileElement::identifierType id, const char *data, std::size_t dataSize);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
EbmlElement(EbmlElement &parent, uint64 startOffset);
|
EbmlElement(EbmlElement &parent, uint64 startOffset);
|
||||||
|
|
|
@ -24,6 +24,10 @@ using namespace ChronoUtilities;
|
||||||
|
|
||||||
namespace Media {
|
namespace Media {
|
||||||
|
|
||||||
|
constexpr const char appInfo[] = APP_NAME " v" APP_VERSION;
|
||||||
|
constexpr uint64 appInfoElementDataSize = sizeof(appInfo) - 1;
|
||||||
|
constexpr uint64 appInfoElementTotalSize = 2 + 1 + appInfoElementDataSize;
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \class Media::MatroskaContainer
|
* \class Media::MatroskaContainer
|
||||||
* \brief Implementation of GenericContainer<MediaFileInfo, MatroskaTag, MatroskaTrack, EbmlElement>.
|
* \brief Implementation of GenericContainer<MediaFileInfo, MatroskaTag, MatroskaTrack, EbmlElement>.
|
||||||
|
@ -523,11 +527,13 @@ void MatroskaContainer::parseSegmentInfo()
|
||||||
EbmlElement *subElement = element->firstChild();
|
EbmlElement *subElement = element->firstChild();
|
||||||
float64 rawDuration = 0.0;
|
float64 rawDuration = 0.0;
|
||||||
uint64 timeScale = 0;
|
uint64 timeScale = 0;
|
||||||
|
bool hasTitle = false;
|
||||||
while(subElement) {
|
while(subElement) {
|
||||||
subElement->parse();
|
subElement->parse();
|
||||||
switch(subElement->id()) {
|
switch(subElement->id()) {
|
||||||
case MatroskaIds::Title:
|
case MatroskaIds::Title:
|
||||||
m_title = subElement->readString();
|
m_titles.emplace_back(subElement->readString());
|
||||||
|
hasTitle = true;
|
||||||
break;
|
break;
|
||||||
case MatroskaIds::Duration:
|
case MatroskaIds::Duration:
|
||||||
rawDuration = subElement->readFloat();
|
rawDuration = subElement->readFloat();
|
||||||
|
@ -538,6 +544,11 @@ void MatroskaContainer::parseSegmentInfo()
|
||||||
}
|
}
|
||||||
subElement = subElement->nextSibling();
|
subElement = subElement->nextSibling();
|
||||||
}
|
}
|
||||||
|
if(!hasTitle) {
|
||||||
|
// add empty string as title for segment if no
|
||||||
|
// "Title"-element has been specified
|
||||||
|
m_titles.emplace_back();
|
||||||
|
}
|
||||||
if(rawDuration > 0.0 && timeScale > 0) {
|
if(rawDuration > 0.0 && timeScale > 0) {
|
||||||
m_duration += TimeSpan::fromSeconds(rawDuration * timeScale / 1000000000);
|
m_duration += TimeSpan::fromSeconds(rawDuration * timeScale / 1000000000);
|
||||||
}
|
}
|
||||||
|
@ -742,12 +753,14 @@ void MatroskaContainer::internalMakeFile()
|
||||||
EbmlElement::makeSimpleElement(outputStream, EbmlIds::DocTypeReadVersion, m_doctypeReadVersion);
|
EbmlElement::makeSimpleElement(outputStream, EbmlIds::DocTypeReadVersion, m_doctypeReadVersion);
|
||||||
// write segments
|
// write segments
|
||||||
EbmlElement *level1Element, *level2Element;
|
EbmlElement *level1Element, *level2Element;
|
||||||
|
uint64 segmentInfoElementDataSize;
|
||||||
MatroskaSeekInfo seekInfo;
|
MatroskaSeekInfo seekInfo;
|
||||||
MatroskaCuePositionUpdater cuesUpdater;
|
MatroskaCuePositionUpdater cuesUpdater;
|
||||||
vector<MatroskaTagMaker> tagMaker;
|
vector<MatroskaTagMaker> tagMaker;
|
||||||
uint64 tagElementsSize, tagsSize;
|
uint64 tagElementsSize, tagsSize;
|
||||||
vector<MatroskaAttachmentMaker> attachmentMaker;
|
vector<MatroskaAttachmentMaker> attachmentMaker;
|
||||||
uint64 attachedFileElementsSize, attachmentsSize;
|
uint64 attachedFileElementsSize, attachmentsSize;
|
||||||
|
unsigned int segmentIndex = 0;
|
||||||
unsigned int index;
|
unsigned int index;
|
||||||
try {
|
try {
|
||||||
for(; level0Element; level0Element = level0Element->nextSibling()) {
|
for(; level0Element; level0Element = level0Element->nextSibling()) {
|
||||||
|
@ -821,7 +834,42 @@ void MatroskaContainer::internalMakeFile()
|
||||||
// calculate size of "SeekHead"-element
|
// calculate size of "SeekHead"-element
|
||||||
elementSize += seekInfo.actualSize();
|
elementSize += seekInfo.actualSize();
|
||||||
// pretend writing elements to find out the offsets and the total segment size
|
// pretend writing elements to find out the offsets and the total segment size
|
||||||
for(auto id : initializer_list<EbmlElement::identifierType>{MatroskaIds::SegmentInfo, MatroskaIds::Tracks, MatroskaIds::Chapters}) {
|
// pretend writing "SegmentInfo"-element
|
||||||
|
for(level1Element = level0Element->childById(MatroskaIds::SegmentInfo), index = 0; level1Element; level1Element = level1Element->siblingById(MatroskaIds::SegmentInfo), ++index) {
|
||||||
|
// update offset in "SeekHead"-element
|
||||||
|
if(seekInfo.push(index, MatroskaIds::SegmentInfo, currentOffset + elementSize)) {
|
||||||
|
goto calculateSegmentSize;
|
||||||
|
} else {
|
||||||
|
// add size of "SegmentInfo"-element
|
||||||
|
// -> size of "MuxingApp"- and "WritingApp"-element
|
||||||
|
segmentInfoElementDataSize = 2 * appInfoElementTotalSize;
|
||||||
|
// -> add size of "Title"-element
|
||||||
|
if(segmentIndex < m_titles.size()) {
|
||||||
|
const auto &title = m_titles[segmentIndex];
|
||||||
|
if(!title.empty()) {
|
||||||
|
segmentInfoElementDataSize += 2 + EbmlElement::calculateSizeDenotationLength(title.size()) + title.size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// -> add size of other childs
|
||||||
|
for(level2Element = level1Element->firstChild(); level2Element; level2Element = level2Element->nextSibling()) {
|
||||||
|
level2Element->parse();
|
||||||
|
switch(level2Element->id()) {
|
||||||
|
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:
|
||||||
|
segmentInfoElementDataSize += level2Element->totalSize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// -> calculate total size
|
||||||
|
elementSize += 4 + EbmlElement::calculateSizeDenotationLength(segmentInfoElementDataSize) + segmentInfoElementDataSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// pretend writing "Tracks"- and "Chapters"-element
|
||||||
|
for(const auto id : initializer_list<EbmlElement::identifierType>{MatroskaIds::Tracks, MatroskaIds::Chapters}) {
|
||||||
for(level1Element = level0Element->childById(id), index = 0; level1Element; level1Element = level1Element->siblingById(id), ++index) {
|
for(level1Element = level0Element->childById(id), index = 0; level1Element; level1Element = level1Element->siblingById(id), ++index) {
|
||||||
// update offset in "SeekHead"-element
|
// update offset in "SeekHead"-element
|
||||||
if(seekInfo.push(index, id, currentOffset + elementSize)) {
|
if(seekInfo.push(index, id, currentOffset + elementSize)) {
|
||||||
|
@ -922,9 +970,39 @@ void MatroskaContainer::internalMakeFile()
|
||||||
seekInfo.invalidateNotifications();
|
seekInfo.invalidateNotifications();
|
||||||
seekInfo.make(outputStream);
|
seekInfo.make(outputStream);
|
||||||
addNotifications(seekInfo);
|
addNotifications(seekInfo);
|
||||||
// write other elements
|
// write "SegmentInfo"-element
|
||||||
for(auto id : initializer_list<EbmlElement::identifierType>{MatroskaIds::SegmentInfo, MatroskaIds::Tracks, MatroskaIds::Chapters}) {
|
for(level1Element = level0Element->childById(MatroskaIds::SegmentInfo); level1Element; level1Element = level1Element->siblingById(MatroskaIds::SegmentInfo)) {
|
||||||
for(level1Element = level0Element->childById(id), index = 0; level1Element; level1Element = level1Element->siblingById(id), ++index) {
|
// -> write ID and size
|
||||||
|
outputWriter.writeUInt32BE(MatroskaIds::SegmentInfo);
|
||||||
|
sizeLength = EbmlElement::makeSizeDenotation(segmentInfoElementDataSize, buff);
|
||||||
|
outputStream.write(buff, sizeLength);
|
||||||
|
// -> write childs
|
||||||
|
for(level2Element = level1Element->firstChild(); level2Element; level2Element = level2Element->nextSibling()) {
|
||||||
|
switch(level2Element->id()) {
|
||||||
|
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->copyEntirely(outputStream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// -> write "Title"-element
|
||||||
|
if(segmentIndex < m_titles.size()) {
|
||||||
|
const auto &title = m_titles[segmentIndex];
|
||||||
|
if(!title.empty()) {
|
||||||
|
EbmlElement::makeSimpleElement(outputStream, MatroskaIds::Title, title);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// -> write "MuxingApp"- and "WritingApp"-element
|
||||||
|
EbmlElement::makeSimpleElement(outputStream, MatroskaIds::MuxingApp, appInfo, appInfoElementDataSize);
|
||||||
|
EbmlElement::makeSimpleElement(outputStream, MatroskaIds::WrittingApp, appInfo, appInfoElementDataSize);
|
||||||
|
}
|
||||||
|
// write "Tracks"- and "Chapters"-element
|
||||||
|
for(const auto id : initializer_list<EbmlElement::identifierType>{MatroskaIds::Tracks, MatroskaIds::Chapters}) {
|
||||||
|
for(level1Element = level0Element->childById(id); level1Element; level1Element = level1Element->siblingById(id)) {
|
||||||
level1Element->copyEntirely(outputStream);
|
level1Element->copyEntirely(outputStream);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -965,8 +1043,8 @@ void MatroskaContainer::internalMakeFile()
|
||||||
updateStatus("Writing segment data ...", static_cast<double>(static_cast<uint64>(outputStream.tellp()) - offset) / elementSize);
|
updateStatus("Writing segment data ...", static_cast<double>(static_cast<uint64>(outputStream.tellp()) - offset) / elementSize);
|
||||||
}
|
}
|
||||||
// write "Cluster"-element
|
// write "Cluster"-element
|
||||||
for(level1Element = level0Element->childById(MatroskaIds::Cluster), index = 0, clusterSizesIterator = clusterSizes.cbegin();
|
for(level1Element = level0Element->childById(MatroskaIds::Cluster), clusterSizesIterator = clusterSizes.cbegin();
|
||||||
level1Element; level1Element = level1Element->siblingById(MatroskaIds::Cluster), ++index, ++clusterSizesIterator) {
|
level1Element; level1Element = level1Element->siblingById(MatroskaIds::Cluster), ++clusterSizesIterator) {
|
||||||
// calculate position of cluster in segment
|
// calculate position of cluster in segment
|
||||||
clusterSize = currentOffset + (static_cast<uint64>(outputStream.tellp()) - offset);
|
clusterSize = currentOffset + (static_cast<uint64>(outputStream.tellp()) - offset);
|
||||||
// write header; checking whether clusterSizesIterator is valid shouldn't be necessary
|
// write header; checking whether clusterSizesIterator is valid shouldn't be necessary
|
||||||
|
@ -993,6 +1071,7 @@ 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
++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
|
||||||
break;
|
break;
|
||||||
|
|
Loading…
Reference in New Issue