improved handling of "SegmentInfo"-element
This commit is contained in:
parent
7aea0e2a50
commit
4b13bac99c
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/*!
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue