From 4b13bac99cc1a2f3ddca32b6737aa3e3da7af1c2 Mon Sep 17 00:00:00 2001 From: Martchus Date: Wed, 14 Oct 2015 19:42:48 +0200 Subject: [PATCH] improved handling of "SegmentInfo"-element --- README.md | 1 + abstractcontainer.cpp | 2 +- abstractcontainer.h | 27 ++++++++-- matroska/ebmlelement.cpp | 21 +++++++- matroska/ebmlelement.h | 1 + matroska/matroskacontainer.cpp | 93 +++++++++++++++++++++++++++++++--- 6 files changed, 130 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index f4442b9..1c60076 100644 --- a/README.md +++ b/README.md @@ -22,3 +22,4 @@ It also depends on zlib. ## TODO - Use padding to prevent rewriting the entire file to save tags. - Support more tag formats (EXIF, PDF metadata, ...). +- Do tests with Matroska files which have multiple segments. diff --git a/abstractcontainer.cpp b/abstractcontainer.cpp index 8c0b8c3..0f485b3 100644 --- a/abstractcontainer.cpp +++ b/abstractcontainer.cpp @@ -412,7 +412,6 @@ size_t AbstractContainer::attachmentCount() const { return 0; } - /*! * \brief Discards all parsing results. */ @@ -429,6 +428,7 @@ void AbstractContainer::reset() m_doctypeVersion = 0; m_doctypeReadVersion = 0; m_timeScale = 0; + m_titles.clear(); } } // namespace Media diff --git a/abstractcontainer.h b/abstractcontainer.h index 3e2c763..cbc7995 100644 --- a/abstractcontainer.h +++ b/abstractcontainer.h @@ -74,7 +74,8 @@ public: const std::string &documentType() const; uint64 doctypeVersion() const; uint64 doctypeReadVersion() const; - const std::string &title() const; + const std::vector &titles() const; + void setTitle(const std::string &title, std::size_t segmentIndex = 0); ChronoUtilities::TimeSpan duration() const; ChronoUtilities::DateTime creationTime() const; ChronoUtilities::DateTime modificationTime() const; @@ -97,7 +98,7 @@ protected: std::string m_doctype; uint64 m_doctypeVersion; uint64 m_doctypeReadVersion; - std::string m_title; + std::vector m_titles; ChronoUtilities::TimeSpan m_duration; ChronoUtilities::DateTime m_creationTime; ChronoUtilities::DateTime m_modificationTime; @@ -243,12 +244,28 @@ inline uint64 AbstractContainer::doctypeReadVersion() const 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 &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; } /*! diff --git a/matroska/ebmlelement.cpp b/matroska/ebmlelement.cpp index d26d2f6..a810d29 100644 --- a/matroska/ebmlelement.cpp +++ b/matroska/ebmlelement.cpp @@ -355,7 +355,7 @@ byte EbmlElement::makeUInteger(uint64 value, char *buff) * \brief Makes a simple EBML element. * \param stream Specifies the stream to write the data to. * \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) { @@ -373,7 +373,7 @@ void EbmlElement::makeSimpleElement(ostream &stream, identifierType id, uint64 c * \brief Makes a simple EBML element. * \param stream Specifies the stream to write the data to. * \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) { @@ -385,6 +385,23 @@ void EbmlElement::makeSimpleElement(ostream &stream, GenericFileElement::identif 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); +} + } diff --git a/matroska/ebmlelement.h b/matroska/ebmlelement.h index 99fe40b..f6edc45 100644 --- a/matroska/ebmlelement.h +++ b/matroska/ebmlelement.h @@ -70,6 +70,7 @@ public: 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, const std::string &content); + static void makeSimpleElement(std::ostream &stream, GenericFileElement::identifierType id, const char *data, std::size_t dataSize); protected: EbmlElement(EbmlElement &parent, uint64 startOffset); diff --git a/matroska/matroskacontainer.cpp b/matroska/matroskacontainer.cpp index fa9b347..40c1e4d 100644 --- a/matroska/matroskacontainer.cpp +++ b/matroska/matroskacontainer.cpp @@ -24,6 +24,10 @@ using namespace ChronoUtilities; 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 * \brief Implementation of GenericContainer. @@ -523,11 +527,13 @@ void MatroskaContainer::parseSegmentInfo() EbmlElement *subElement = element->firstChild(); float64 rawDuration = 0.0; uint64 timeScale = 0; + bool hasTitle = false; while(subElement) { subElement->parse(); switch(subElement->id()) { case MatroskaIds::Title: - m_title = subElement->readString(); + m_titles.emplace_back(subElement->readString()); + hasTitle = true; break; case MatroskaIds::Duration: rawDuration = subElement->readFloat(); @@ -538,6 +544,11 @@ void MatroskaContainer::parseSegmentInfo() } 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) { m_duration += TimeSpan::fromSeconds(rawDuration * timeScale / 1000000000); } @@ -742,12 +753,14 @@ void MatroskaContainer::internalMakeFile() EbmlElement::makeSimpleElement(outputStream, EbmlIds::DocTypeReadVersion, m_doctypeReadVersion); // write segments EbmlElement *level1Element, *level2Element; + uint64 segmentInfoElementDataSize; MatroskaSeekInfo seekInfo; MatroskaCuePositionUpdater cuesUpdater; vector tagMaker; uint64 tagElementsSize, tagsSize; vector attachmentMaker; uint64 attachedFileElementsSize, attachmentsSize; + unsigned int segmentIndex = 0; unsigned int index; try { for(; level0Element; level0Element = level0Element->nextSibling()) { @@ -821,7 +834,42 @@ void MatroskaContainer::internalMakeFile() // calculate size of "SeekHead"-element elementSize += seekInfo.actualSize(); // pretend writing elements to find out the offsets and the total segment size - for(auto id : initializer_list{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{MatroskaIds::Tracks, MatroskaIds::Chapters}) { for(level1Element = level0Element->childById(id), index = 0; level1Element; level1Element = level1Element->siblingById(id), ++index) { // update offset in "SeekHead"-element if(seekInfo.push(index, id, currentOffset + elementSize)) { @@ -922,9 +970,39 @@ void MatroskaContainer::internalMakeFile() seekInfo.invalidateNotifications(); seekInfo.make(outputStream); addNotifications(seekInfo); - // write other elements - for(auto id : initializer_list{MatroskaIds::SegmentInfo, MatroskaIds::Tracks, MatroskaIds::Chapters}) { - for(level1Element = level0Element->childById(id), index = 0; level1Element; level1Element = level1Element->siblingById(id), ++index) { + // write "SegmentInfo"-element + for(level1Element = level0Element->childById(MatroskaIds::SegmentInfo); level1Element; level1Element = level1Element->siblingById(MatroskaIds::SegmentInfo)) { + // -> 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{MatroskaIds::Tracks, MatroskaIds::Chapters}) { + for(level1Element = level0Element->childById(id); level1Element; level1Element = level1Element->siblingById(id)) { level1Element->copyEntirely(outputStream); } } @@ -965,8 +1043,8 @@ void MatroskaContainer::internalMakeFile() updateStatus("Writing segment data ...", static_cast(static_cast(outputStream.tellp()) - offset) / elementSize); } // write "Cluster"-element - for(level1Element = level0Element->childById(MatroskaIds::Cluster), index = 0, clusterSizesIterator = clusterSizes.cbegin(); - level1Element; level1Element = level1Element->siblingById(MatroskaIds::Cluster), ++index, ++clusterSizesIterator) { + for(level1Element = level0Element->childById(MatroskaIds::Cluster), clusterSizesIterator = clusterSizes.cbegin(); + level1Element; level1Element = level1Element->siblingById(MatroskaIds::Cluster), ++clusterSizesIterator) { // calculate position of cluster in segment clusterSize = currentOffset + (static_cast(outputStream.tellp()) - offset); // write header; checking whether clusterSizesIterator is valid shouldn't be necessary @@ -993,6 +1071,7 @@ void MatroskaContainer::internalMakeFile() updatePercentage(static_cast(static_cast(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 readOffset = level0Element->totalSize(); // increase the read offset by the size of the segment read from the orignial file break;