improved handling of "SegmentInfo"-element

This commit is contained in:
Martchus 2015-10-14 19:42:48 +02:00
parent 7aea0e2a50
commit 4b13bac99c
6 changed files with 130 additions and 15 deletions

View File

@ -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.

View File

@ -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

View File

@ -74,7 +74,8 @@ public:
const std::string &documentType() const;
uint64 doctypeVersion() 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::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<std::string> 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<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;
}
/*!

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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<MediaFileInfo, MatroskaTag, MatroskaTrack, EbmlElement>.
@ -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<MatroskaTagMaker> tagMaker;
uint64 tagElementsSize, tagsSize;
vector<MatroskaAttachmentMaker> 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<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) {
// 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<EbmlElement::identifierType>{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<EbmlElement::identifierType>{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<double>(static_cast<uint64>(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<uint64>(outputStream.tellp()) - offset);
// 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);
}
}
++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;