2015-09-06 19:57:33 +02:00
# include "./matroskacontainer.h"
# include "./ebmlid.h"
# include "./matroskaid.h"
# include "./matroskacues.h"
# include "./matroskaeditionentry.h"
# include "./matroskaseekinfo.h"
# include "../mediafileinfo.h"
# include "../exceptions.h"
# include "../backuphelper.h"
2015-04-22 19:22:01 +02:00
# include <c++utilities/conversion/stringconversion.h>
# include <c++utilities/misc/memory.h>
# include <functional>
# include <initializer_list>
# include <unordered_set>
using namespace std ;
using namespace std : : placeholders ;
using namespace IoUtilities ;
using namespace ConversionUtilities ;
using namespace ChronoUtilities ;
namespace Media {
2015-10-14 19:42:48 +02:00
constexpr const char appInfo [ ] = APP_NAME " v " APP_VERSION ;
constexpr uint64 appInfoElementDataSize = sizeof ( appInfo ) - 1 ;
constexpr uint64 appInfoElementTotalSize = 2 + 1 + appInfoElementDataSize ;
2015-04-22 19:22:01 +02:00
/*!
* \ class Media : : MatroskaContainer
* \ brief Implementation of GenericContainer < MediaFileInfo , MatroskaTag , MatroskaTrack , EbmlElement > .
*/
uint64 MatroskaContainer : : m_maxFullParseSize = 0x3200000 ;
/*!
* \ brief Constructs a new container for the specified \ a fileInfo at the specified \ a startOffset .
*/
MatroskaContainer : : MatroskaContainer ( MediaFileInfo & fileInfo , uint64 startOffset ) :
GenericContainer < MediaFileInfo , MatroskaTag , MatroskaTrack , EbmlElement > ( fileInfo , startOffset ) ,
m_maxIdLength ( 4 ) ,
m_maxSizeLength ( 8 )
{
m_version = 1 ;
m_readVersion = 1 ;
m_doctype = " matroska " ;
m_doctypeVersion = 1 ;
m_doctypeReadVersion = 1 ;
}
/*!
* \ brief Destroys the container .
*/
MatroskaContainer : : ~ MatroskaContainer ( )
{ }
2015-10-06 22:39:18 +02:00
void MatroskaContainer : : reset ( )
{
GenericContainer < MediaFileInfo , MatroskaTag , MatroskaTrack , EbmlElement > : : reset ( ) ;
m_maxIdLength = 4 ;
m_maxSizeLength = 8 ;
m_version = 1 ;
m_readVersion = 1 ;
m_doctype = " matroska " ;
m_doctypeVersion = 1 ;
m_doctypeReadVersion = 1 ;
m_tracksElements . clear ( ) ;
m_segmentInfoElements . clear ( ) ;
m_tagsElements . clear ( ) ;
m_chaptersElements . clear ( ) ;
m_attachmentsElements . clear ( ) ;
m_seekInfos . clear ( ) ;
m_editionEntries . clear ( ) ;
m_attachments . clear ( ) ;
}
2015-04-22 19:22:01 +02:00
/*!
* \ brief Validates the file index ( cue entries ) .
* \ remarks Checks only for cluster positions and missing , unknown or surplus elements .
*/
void MatroskaContainer : : validateIndex ( )
{
static const string context ( " validating Matroska file index (cues) " ) ;
bool cuesElementsFound = false ;
if ( m_firstElement ) {
unordered_set < int > ids ;
bool cueTimeFound = false , cueTrackPositionsFound = false ;
unique_ptr < EbmlElement > clusterElement ;
uint64 pos , prevClusterSize = 0 , currentOffset = 0 ;
// iterate throught all segments
for ( EbmlElement * segmentElement = m_firstElement - > siblingById ( MatroskaIds : : Segment ) ; segmentElement ; segmentElement = segmentElement - > siblingById ( MatroskaIds : : Segment ) ) {
segmentElement - > parse ( ) ;
// iterate throught all child elements of the segment (only "Cues"- and "Cluster"-elements are relevant for this method)
for ( EbmlElement * segmentChildElement = segmentElement - > firstChild ( ) ; segmentChildElement ; segmentChildElement = segmentChildElement - > nextSibling ( ) ) {
segmentChildElement - > parse ( ) ;
switch ( segmentChildElement - > id ( ) ) {
case EbmlIds : : Void :
case EbmlIds : : Crc32 :
break ;
case MatroskaIds : : Cues :
cuesElementsFound = true ;
// parse childs of "Cues"-element ("CuePoint"-elements)
for ( EbmlElement * cuePointElement = segmentChildElement - > firstChild ( ) ; cuePointElement ; cuePointElement = cuePointElement - > nextSibling ( ) ) {
cuePointElement - > parse ( ) ;
cueTimeFound = cueTrackPositionsFound = false ; // to validate quantity of these elements
switch ( cuePointElement - > id ( ) ) {
case EbmlIds : : Void :
case EbmlIds : : Crc32 :
break ;
case MatroskaIds : : CuePoint :
// parse childs of "CuePoint"-element
for ( EbmlElement * cuePointChildElement = cuePointElement - > firstChild ( ) ; cuePointChildElement ; cuePointChildElement = cuePointChildElement - > nextSibling ( ) ) {
cuePointChildElement - > parse ( ) ;
switch ( cuePointChildElement - > id ( ) ) {
case MatroskaIds : : CueTime :
// validate uniqueness
if ( cueTimeFound ) {
addNotification ( NotificationType : : Warning , " \" CuePoint \" -element contains multiple \" CueTime \" elements. " , context ) ;
} else {
cueTimeFound = true ;
}
break ;
case MatroskaIds : : CueTrackPositions :
cueTrackPositionsFound = true ;
ids . clear ( ) ;
clusterElement . reset ( ) ;
for ( EbmlElement * subElement = cuePointChildElement - > firstChild ( ) ; subElement ; subElement = subElement - > nextSibling ( ) ) {
subElement - > parse ( ) ;
switch ( subElement - > id ( ) ) {
case MatroskaIds : : CueTrack :
case MatroskaIds : : CueClusterPosition :
case MatroskaIds : : CueRelativePosition :
case MatroskaIds : : CueDuration :
case MatroskaIds : : CueBlockNumber :
case MatroskaIds : : CueCodecState :
// validate uniqueness
if ( ids . count ( subElement - > id ( ) ) ) {
addNotification ( NotificationType : : Warning , " \" CueTrackPositions \" -element contains multiple \" " + subElement - > idToString ( ) + " \" elements. " , context ) ;
} else {
ids . insert ( subElement - > id ( ) ) ;
}
break ;
case EbmlIds : : Crc32 :
case EbmlIds : : Void :
case MatroskaIds : : CueReference :
break ;
default :
addNotification ( NotificationType : : Warning , " \" CueTrackPositions \" -element contains unknown element \" " + subElement - > idToString ( ) + " \" . " , context ) ;
}
switch ( subElement - > id ( ) ) {
case EbmlIds : : Void :
case EbmlIds : : Crc32 :
case MatroskaIds : : CueTrack :
break ;
case MatroskaIds : : CueClusterPosition :
// validate "Cluster" position denoted by "CueClusterPosition"-element
clusterElement = make_unique < EbmlElement > ( * this , segmentElement - > dataOffset ( ) + subElement - > readUInteger ( ) - currentOffset ) ;
try {
clusterElement - > parse ( ) ;
if ( clusterElement - > id ( ) ! = MatroskaIds : : Cluster ) {
addNotification ( NotificationType : : Critical , " \" CueClusterPosition \" element at " + numberToString ( subElement - > startOffset ( ) ) + " does not point to \" Cluster \" -element (points to " + numberToString ( clusterElement - > startOffset ( ) ) + " ). " , context ) ;
}
} catch ( Failure & ) {
addNotifications ( context , * clusterElement ) ;
}
break ;
case MatroskaIds : : CueRelativePosition :
// read "Block" position denoted by "CueRelativePosition"-element (validate later since the "Cluster"-element is needed to validate)
pos = subElement - > readUInteger ( ) ;
break ;
case MatroskaIds : : CueDuration :
break ;
case MatroskaIds : : CueBlockNumber :
break ;
case MatroskaIds : : CueCodecState :
break ;
case MatroskaIds : : CueReference :
break ;
default :
;
}
}
// validate existence of mandatory elements
if ( ! ids . count ( MatroskaIds : : CueTrack ) ) {
addNotification ( NotificationType : : Warning , " \" CueTrackPositions \" -element does not contain mandatory element \" CueTrack \" . " , context ) ;
}
if ( ! clusterElement ) {
addNotification ( NotificationType : : Warning , " \" CueTrackPositions \" -element does not contain mandatory element \" CueClusterPosition \" . " , context ) ;
} else {
if ( ids . count ( MatroskaIds : : CueRelativePosition ) ) {
// validate "Block" position denoted by "CueRelativePosition"-element
EbmlElement referenceElement ( * this , clusterElement - > dataOffset ( ) + pos ) ;
try {
referenceElement . parse ( ) ;
switch ( referenceElement . id ( ) ) {
case MatroskaIds : : SimpleBlock :
case MatroskaIds : : Block :
2015-05-06 22:13:08 +02:00
case MatroskaIds : : BlockGroup :
2015-04-22 19:22:01 +02:00
break ;
default :
2015-05-06 22:13:08 +02:00
addNotification ( NotificationType : : Critical , " \" CueRelativePosition \" element does not point to \" Block \" -, \" BlockGroup \" , or \" SimpleBlock \" -element (points to " + numberToString ( referenceElement . startOffset ( ) ) + " ). " , context ) ;
2015-04-22 19:22:01 +02:00
}
} catch ( Failure & ) {
addNotifications ( context , referenceElement ) ;
}
}
}
break ;
case EbmlIds : : Crc32 :
case EbmlIds : : Void :
break ;
default :
addNotification ( NotificationType : : Warning , " \" CuePoint \" -element contains unknown element \" " + cuePointElement - > idToString ( ) + " \" . " , context ) ;
}
}
// validate existence of mandatory elements
if ( ! cueTimeFound ) {
addNotification ( NotificationType : : Warning , " \" CuePoint \" -element does not contain mandatory element \" CueTime \" . " , context ) ;
}
if ( ! cueTrackPositionsFound ) {
addNotification ( NotificationType : : Warning , " \" CuePoint \" -element does not contain mandatory element \" CueClusterPosition \" . " , context ) ;
}
break ;
default :
;
}
}
break ;
case MatroskaIds : : Cluster :
// parse childs of "Cluster"-element
for ( EbmlElement * clusterElementChild = segmentChildElement - > firstChild ( ) ; clusterElementChild ; clusterElementChild = clusterElementChild - > nextSibling ( ) ) {
clusterElementChild - > parse ( ) ;
switch ( clusterElementChild - > id ( ) ) {
case EbmlIds : : Void :
case EbmlIds : : Crc32 :
break ;
case MatroskaIds : : Position :
// validate position
if ( ( pos = clusterElementChild - > readUInteger ( ) ) > 0 & & ( segmentChildElement - > startOffset ( ) - segmentElement - > dataOffset ( ) + currentOffset ) ! = pos ) {
addNotification ( NotificationType : : Critical , " \" Position \" -element at " + numberToString ( clusterElementChild - > startOffset ( ) ) + " points to " + numberToString ( pos ) + " which is not the offset of the containing \" Cluster \" -element. " , context ) ;
}
break ;
case MatroskaIds : : PrevSize :
// validate prev size
if ( clusterElementChild - > readUInteger ( ) ! = prevClusterSize ) {
addNotification ( NotificationType : : Critical , " \" PrevSize \" -element at " + numberToString ( clusterElementChild - > startOffset ( ) ) + " has invalid value. " , context ) ;
}
break ;
default :
;
}
}
prevClusterSize = segmentChildElement - > totalSize ( ) ;
break ;
default :
;
}
}
currentOffset + = segmentElement - > totalSize ( ) ;
}
}
// add a warning when no index could be found
if ( ! cuesElementsFound ) {
addNotification ( NotificationType : : Warning , " No \" Cues \" -elements (index) found. " , context ) ;
}
}
/*!
* \ brief Returns an indication whether \ a offset equals the start offset of \ a element .
*/
bool sameOffset ( uint64 offset , const EbmlElement * element ) {
return element - > startOffset ( ) = = offset ;
}
/*!
* \ brief Returns whether none of the specified \ a elements have the specified \ a offset .
*
* This method is used when gathering elements to avaoid adding the same element twice .
*/
inline bool excludesOffset ( const vector < EbmlElement * > & elements , uint64 offset )
{
return find_if ( elements . cbegin ( ) , elements . cend ( ) , std : : bind ( sameOffset , offset , _1 ) ) = = elements . cend ( ) ;
}
MatroskaChapter * MatroskaContainer : : chapter ( std : : size_t index )
{
size_t currentIndex = 0 ;
for ( auto & entry : m_editionEntries ) {
currentIndex + = entry - > chapters ( ) . size ( ) ;
if ( index < currentIndex ) {
return entry - > chapters ( ) [ index ] . get ( ) ;
}
}
return nullptr ;
}
size_t MatroskaContainer : : chapterCount ( ) const
{
size_t count = 0 ;
for ( const auto & entry : m_editionEntries ) {
count + = entry - > chapters ( ) . size ( ) ;
}
return count ;
}
MatroskaAttachment * MatroskaContainer : : createAttachment ( )
{
// generate unique ID
srand ( time ( nullptr ) ) ;
byte tries = 0 ;
uint64 attachmentId ;
generateRandomId :
attachmentId = rand ( ) ;
if ( tries < 0xFF ) {
for ( const auto & attachment : m_attachments ) {
if ( attachmentId = = attachment - > id ( ) ) {
+ + tries ;
goto generateRandomId ;
}
}
}
// create new attachment, set ID
m_attachments . emplace_back ( make_unique < MatroskaAttachment > ( ) ) ;
auto & attachment = m_attachments . back ( ) ;
attachment - > setId ( attachmentId ) ;
return attachment . get ( ) ;
}
void MatroskaContainer : : internalParseHeader ( )
{
static const string context ( " parsing header of Matroska container " ) ;
// reset old results
m_firstElement = make_unique < EbmlElement > ( * this , startOffset ( ) ) ;
m_additionalElements . clear ( ) ;
m_tracksElements . clear ( ) ;
m_segmentInfoElements . clear ( ) ;
m_tagsElements . clear ( ) ;
m_seekInfos . clear ( ) ;
uint64 currentOffset = 0 ;
vector < MatroskaSeekInfo > : : size_type seekInfosIndex = 0 ;
// loop through all top level elements
for ( EbmlElement * topLevelElement = m_firstElement . get ( ) ; topLevelElement ; topLevelElement = topLevelElement - > nextSibling ( ) ) {
topLevelElement - > parse ( ) ;
switch ( topLevelElement - > id ( ) ) {
case EbmlIds : : Header :
for ( EbmlElement * subElement = topLevelElement - > firstChild ( ) ; subElement ; subElement = subElement - > nextSibling ( ) ) {
try {
subElement - > parse ( ) ;
} catch ( Failure & ) {
addNotification ( NotificationType : : Critical , " Unable to parse all childs of EBML header element. " , context ) ;
break ;
}
switch ( subElement - > id ( ) ) {
case EbmlIds : : Version :
m_version = subElement - > readUInteger ( ) ;
break ;
case EbmlIds : : ReadVersion :
m_readVersion = subElement - > readUInteger ( ) ;
break ;
case EbmlIds : : DocType :
m_doctype = subElement - > readString ( ) ;
break ;
case EbmlIds : : DocTypeVersion :
m_doctypeVersion = subElement - > readUInteger ( ) ;
break ;
case EbmlIds : : DocTypeReadVersion :
m_doctypeReadVersion = subElement - > readUInteger ( ) ;
break ;
case EbmlIds : : MaxIdLength :
m_maxIdLength = subElement - > readUInteger ( ) ;
if ( m_maxIdLength > EbmlElement : : maximumIdLengthSupported ( ) ) {
addNotification ( NotificationType : : Critical , " Maximum EBML element ID length greather then "
+ numberToString < uint32 > ( EbmlElement : : maximumIdLengthSupported ( ) )
+ " bytes is not supported. " , context ) ;
throw InvalidDataException ( ) ;
}
break ;
case EbmlIds : : MaxSizeLength :
m_maxSizeLength = subElement - > readUInteger ( ) ;
if ( m_maxSizeLength > EbmlElement : : maximumSizeLengthSupported ( ) ) {
addNotification ( NotificationType : : Critical , " Maximum EBML element size length greather then "
+ numberToString < uint32 > ( EbmlElement : : maximumSizeLengthSupported ( ) )
+ " bytes is not supported. " , context ) ;
throw InvalidDataException ( ) ;
}
break ;
}
}
break ;
case MatroskaIds : : Segment :
for ( EbmlElement * subElement = topLevelElement - > firstChild ( ) ; subElement ; subElement = subElement - > nextSibling ( ) ) {
try {
subElement - > parse ( ) ;
} catch ( Failure & ) {
addNotification ( NotificationType : : Critical , " Unable to parse all childs of EBML segment element. " , context ) ;
break ;
}
switch ( subElement - > id ( ) ) {
case MatroskaIds : : SeekHead :
m_seekInfos . emplace_back ( make_unique < MatroskaSeekInfo > ( ) ) ;
m_seekInfos . back ( ) - > parse ( subElement ) ;
addNotifications ( * m_seekInfos . back ( ) ) ;
break ;
case MatroskaIds : : Tracks :
if ( excludesOffset ( m_tracksElements , subElement - > startOffset ( ) ) ) {
m_tracksElements . push_back ( subElement ) ;
}
break ;
case MatroskaIds : : SegmentInfo :
if ( excludesOffset ( m_segmentInfoElements , subElement - > startOffset ( ) ) ) {
m_segmentInfoElements . push_back ( subElement ) ;
}
break ;
case MatroskaIds : : Tags :
if ( excludesOffset ( m_tagsElements , subElement - > startOffset ( ) ) ) {
m_tagsElements . push_back ( subElement ) ;
}
break ;
case MatroskaIds : : Chapters :
if ( excludesOffset ( m_chaptersElements , subElement - > startOffset ( ) ) ) {
m_chaptersElements . push_back ( subElement ) ;
}
break ;
case MatroskaIds : : Attachments :
if ( excludesOffset ( m_attachmentsElements , subElement - > startOffset ( ) ) ) {
m_attachmentsElements . push_back ( subElement ) ;
}
break ;
case MatroskaIds : : Cluster :
// cluster reached
// stop here if all relevant information has been gathered
for ( auto i = m_seekInfos . cbegin ( ) + seekInfosIndex , end = m_seekInfos . cend ( ) ; i ! = end ; + + i , + + seekInfosIndex ) {
for ( const auto & infoPair : ( * i ) - > info ( ) ) {
uint64 offset = currentOffset + topLevelElement - > dataOffset ( ) + infoPair . second ;
if ( offset > = fileInfo ( ) . size ( ) ) {
addNotification ( NotificationType : : Critical , " Offset ( " + numberToString ( offset ) + " ) denoted by \" SeekHead \" element is invalid. " , context ) ;
} else {
auto element = make_unique < EbmlElement > ( * this , offset ) ;
try {
element - > parse ( ) ;
if ( element - > id ( ) ! = infoPair . first ) {
addNotification ( NotificationType : : Critical , " ID of element " + element - > idToString ( ) + " at " + numberToString ( offset ) + " does not match the ID denoted in the \" SeekHead \" element (0x " + numberToString ( infoPair . first , 16 ) + " ). " , context ) ;
}
switch ( element - > id ( ) ) {
case MatroskaIds : : SegmentInfo :
if ( excludesOffset ( m_segmentInfoElements , offset ) ) {
m_additionalElements . emplace_back ( move ( element ) ) ;
m_segmentInfoElements . emplace_back ( m_additionalElements . back ( ) . get ( ) ) ;
}
break ;
case MatroskaIds : : Tracks :
if ( excludesOffset ( m_tracksElements , offset ) ) {
m_additionalElements . emplace_back ( move ( element ) ) ;
m_tracksElements . emplace_back ( m_additionalElements . back ( ) . get ( ) ) ;
}
break ;
case MatroskaIds : : Tags :
if ( excludesOffset ( m_tagsElements , offset ) ) {
m_additionalElements . emplace_back ( move ( element ) ) ;
m_tagsElements . emplace_back ( m_additionalElements . back ( ) . get ( ) ) ;
}
break ;
case MatroskaIds : : Chapters :
if ( excludesOffset ( m_chaptersElements , offset ) ) {
m_additionalElements . emplace_back ( move ( element ) ) ;
m_chaptersElements . emplace_back ( m_additionalElements . back ( ) . get ( ) ) ;
}
break ;
case MatroskaIds : : Attachments :
if ( excludesOffset ( m_attachmentsElements , offset ) ) {
m_additionalElements . emplace_back ( move ( element ) ) ;
m_attachmentsElements . emplace_back ( m_additionalElements . back ( ) . get ( ) ) ;
}
break ;
default :
;
}
} catch ( Failure & ) {
addNotification ( NotificationType : : Critical , " Can not parse element at " + numberToString ( offset ) + " (denoted using \" SeekHead \" element). " , context ) ;
}
}
}
}
// not checking if m_tagsElements is empty avoids long parsing times when loading big files
// but also has the disadvantage that the parser relies on the presence of a SeekHead element
// (which is not mandatory) to detect tags at the end of the segment
if ( ( ( ! m_tracksElements . empty ( ) & & ! m_tagsElements . empty ( ) ) | | fileInfo ( ) . size ( ) > m_maxFullParseSize ) & & ! m_segmentInfoElements . empty ( ) ) {
goto finish ;
}
break ;
}
}
currentOffset + = topLevelElement - > totalSize ( ) ;
break ;
default :
;
}
}
// finally parse the "Info"-element and fetch "EditionEntry"-elements
finish :
try {
parseSegmentInfo ( ) ;
} catch ( Failure & ) {
addNotification ( NotificationType : : Critical , " Unable to parse EBML (segment) \" Info \" -element. " , context ) ;
}
}
/*!
* \ brief Parses the ( segment ) " Info " - element .
*
* This private method is called when parsing the header .
*
* \ throws Throws std : : ios_base : : failure when an IO error occurs .
* \ throws Throws Media : : Failure or a derived exception when a parsing
* error occurs .
*/
void MatroskaContainer : : parseSegmentInfo ( )
{
if ( m_segmentInfoElements . empty ( ) ) {
throw NoDataFoundException ( ) ;
}
m_duration = TimeSpan ( ) ;
for ( EbmlElement * element : m_segmentInfoElements ) {
element - > parse ( ) ;
EbmlElement * subElement = element - > firstChild ( ) ;
float64 rawDuration = 0.0 ;
uint64 timeScale = 0 ;
2015-10-14 19:42:48 +02:00
bool hasTitle = false ;
2015-04-22 19:22:01 +02:00
while ( subElement ) {
subElement - > parse ( ) ;
switch ( subElement - > id ( ) ) {
case MatroskaIds : : Title :
2015-10-14 19:42:48 +02:00
m_titles . emplace_back ( subElement - > readString ( ) ) ;
hasTitle = true ;
2015-04-22 19:22:01 +02:00
break ;
case MatroskaIds : : Duration :
rawDuration = subElement - > readFloat ( ) ;
break ;
case MatroskaIds : : TimeCodeScale :
timeScale = subElement - > readUInteger ( ) ;
break ;
}
subElement = subElement - > nextSibling ( ) ;
}
2015-10-14 19:42:48 +02:00
if ( ! hasTitle ) {
// add empty string as title for segment if no
// "Title"-element has been specified
m_titles . emplace_back ( ) ;
}
2015-04-22 19:22:01 +02:00
if ( rawDuration > 0.0 & & timeScale > 0 ) {
m_duration + = TimeSpan : : fromSeconds ( rawDuration * timeScale / 1000000000 ) ;
}
}
}
void MatroskaContainer : : internalParseTags ( )
{
static const string context ( " parsing tags of Matroska container " ) ;
for ( EbmlElement * element : m_tagsElements ) {
try {
element - > parse ( ) ;
for ( EbmlElement * subElement = element - > firstChild ( ) ; subElement ; subElement = subElement - > nextSibling ( ) ) {
subElement - > parse ( ) ;
switch ( subElement - > id ( ) ) {
case MatroskaIds : : Tag :
m_tags . emplace_back ( make_unique < MatroskaTag > ( ) ) ;
try {
m_tags . back ( ) - > parse ( * subElement ) ;
} catch ( NoDataFoundException & ) {
m_tags . pop_back ( ) ;
} catch ( Failure & ) {
addNotification ( NotificationType : : Critical , " Unable to parse tag " + ConversionUtilities : : numberToString ( m_tags . size ( ) ) + " . " , context ) ;
}
break ;
case EbmlIds : : Crc32 :
case EbmlIds : : Void :
break ;
default :
addNotification ( NotificationType : : Warning , " \" Tags \" -element contains unknown child. It will be ignored. " , context ) ;
}
}
} catch ( Failure & ) {
addNotification ( NotificationType : : Critical , " Element structure seems to be invalid. " , context ) ;
throw ;
}
}
}
void MatroskaContainer : : internalParseTracks ( )
{
invalidateStatus ( ) ;
static const string context ( " parsing tracks of Matroska container " ) ;
for ( EbmlElement * element : m_tracksElements ) {
try {
element - > parse ( ) ;
for ( EbmlElement * subElement = element - > firstChild ( ) ; subElement ; subElement = subElement - > nextSibling ( ) ) {
subElement - > parse ( ) ;
switch ( subElement - > id ( ) ) {
case MatroskaIds : : TrackEntry :
m_tracks . emplace_back ( make_unique < MatroskaTrack > ( * subElement ) ) ;
try {
m_tracks . back ( ) - > parseHeader ( ) ;
} catch ( NoDataFoundException & ) {
m_tracks . pop_back ( ) ;
} catch ( Failure & ) {
addNotification ( NotificationType : : Critical , " Unable to parse track " + ConversionUtilities : : numberToString ( m_tracks . size ( ) ) + " . " , context ) ;
}
break ;
case EbmlIds : : Crc32 :
case EbmlIds : : Void :
break ;
default :
addNotification ( NotificationType : : Warning , " \" Tracks \" -element contains unknown child element \" " + subElement - > idToString ( ) + " \" . It will be ignored. " , context ) ;
}
}
} catch ( Failure & ) {
addNotification ( NotificationType : : Critical , " Element structure seems to be invalid. " , context ) ;
throw ;
}
}
}
void MatroskaContainer : : internalParseChapters ( )
{
invalidateStatus ( ) ;
static const string context ( " parsing editions/chapters of Matroska container " ) ;
for ( EbmlElement * element : m_chaptersElements ) {
try {
element - > parse ( ) ;
for ( EbmlElement * subElement = element - > firstChild ( ) ; subElement ; subElement = subElement - > nextSibling ( ) ) {
subElement - > parse ( ) ;
switch ( subElement - > id ( ) ) {
case MatroskaIds : : EditionEntry :
m_editionEntries . emplace_back ( make_unique < MatroskaEditionEntry > ( subElement ) ) ;
try {
m_editionEntries . back ( ) - > parseNested ( ) ;
} catch ( NoDataFoundException & ) {
m_editionEntries . pop_back ( ) ;
} catch ( Failure & ) {
addNotification ( NotificationType : : Critical , " Unable to parse edition entry " + ConversionUtilities : : numberToString ( m_editionEntries . size ( ) ) + " . " , context ) ;
}
break ;
case EbmlIds : : Crc32 :
case EbmlIds : : Void :
break ;
default :
addNotification ( NotificationType : : Warning , " \" Chapters \" -element contains unknown child element \" " + subElement - > idToString ( ) + " \" . It will be ignored. " , context ) ;
}
}
} catch ( Failure & ) {
addNotification ( NotificationType : : Critical , " Element structure seems to be invalid. " , context ) ;
throw ;
}
}
}
void MatroskaContainer : : internalParseAttachments ( )
{
invalidateStatus ( ) ;
static const string context ( " parsing attachments of Matroska container " ) ;
for ( EbmlElement * element : m_attachmentsElements ) {
try {
element - > parse ( ) ;
for ( EbmlElement * subElement = element - > firstChild ( ) ; subElement ; subElement = subElement - > nextSibling ( ) ) {
subElement - > parse ( ) ;
switch ( subElement - > id ( ) ) {
case MatroskaIds : : AttachedFile :
m_attachments . emplace_back ( make_unique < MatroskaAttachment > ( ) ) ;
try {
m_attachments . back ( ) - > parse ( subElement ) ;
} catch ( NoDataFoundException & ) {
m_attachments . pop_back ( ) ;
} catch ( Failure & ) {
addNotification ( NotificationType : : Critical , " Unable to parse attached file " + ConversionUtilities : : numberToString ( m_attachments . size ( ) ) + " . " , context ) ;
}
break ;
case EbmlIds : : Crc32 :
case EbmlIds : : Void :
break ;
default :
addNotification ( NotificationType : : Warning , " \" Attachments \" -element contains unknown child element \" " + subElement - > idToString ( ) + " \" . It will be ignored. " , context ) ;
}
}
} catch ( Failure & ) {
addNotification ( NotificationType : : Critical , " Element structure seems to be invalid. " , context ) ;
throw ;
}
}
}
void MatroskaContainer : : internalMakeFile ( )
{
invalidateStatus ( ) ;
static const string context ( " making Matroska container " ) ;
2015-11-11 11:52:32 +01:00
updateStatus ( " Calculating element sizes ... " ) ;
2015-04-22 19:22:01 +02:00
if ( ! isHeaderParsed ( ) ) {
addNotification ( NotificationType : : Critical , " The header has not been parsed yet. " , context ) ;
throw InvalidDataException ( ) ;
}
2015-11-11 11:52:32 +01:00
EbmlElement * level0Element = firstElement ( ) , * level1Element , * level2Element ;
2015-04-22 19:22:01 +02:00
if ( ! level0Element ) {
addNotification ( NotificationType : : Critical , " No EBML elements could be found. " , context ) ;
throw InvalidDataException ( ) ;
}
2015-11-07 15:23:36 +01:00
// check whether a rewrite is required
2015-04-22 19:22:01 +02:00
try {
2015-11-07 15:23:36 +01:00
// calculate size of tags
2015-04-22 19:22:01 +02:00
vector < MatroskaTagMaker > tagMaker ;
2015-11-07 15:23:36 +01:00
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
2015-04-22 19:22:01 +02:00
vector < MatroskaAttachmentMaker > attachmentMaker ;
2015-11-07 15:23:36 +01:00
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 ( ) ;
2015-04-22 19:22:01 +02:00
}
2015-11-07 15:23:36 +01:00
} catch ( Failure & ) {
// nothing to do because notifications will be added anyways
}
addNotifications ( * attachment ) ;
}
}
uint64 attachmentsSize = attachedFileElementsSize ? 4 + EbmlElement : : calculateSizeDenotationLength ( attachedFileElementsSize ) + attachedFileElementsSize : 0 ;
2015-11-11 11:52:32 +01:00
// check:
// - the number of segments to be written
// - current media data / first cluster offset
// - current padding
// - whether there are tags or attachments in additional segments
// to determine whether a rewrite is required
2015-11-07 15:23:36 +01:00
unsigned int lastSegmentIndex = static_cast < unsigned int > ( - 1 ) ;
2015-11-11 11:52:32 +01:00
bool firstClusterFound = false ;
uint64 currentFirstClusterOffset = 0 ;
uint64 currentPadding = 0 ;
bool rewriteRequired = false ;
2015-11-07 15:23:36 +01:00
for ( ; level0Element ; level0Element = level0Element - > nextSibling ( ) ) {
level0Element - > parse ( ) ;
2015-11-11 11:52:32 +01:00
switch ( level0Element - > id ( ) ) {
case EbmlIds : : Void :
if ( ! firstClusterFound ) {
currentPadding + = level0Element - > totalSize ( ) ;
}
break ;
case MatroskaIds : : Segment :
// check the additional segment for tags and attachments
if ( + + lastSegmentIndex & & ! rewriteRequired ) {
for ( level1Element = level0Element - > firstChild ( ) ; level1Element ; level1Element = level1Element - > nextSibling ( ) ) {
level1Element - > parse ( ) ;
if ( level1Element - > id ( ) = = MatroskaIds : : Attachments | | level1Element - > id ( ) = = MatroskaIds : : Tags ) {
rewriteRequired = true ;
break ;
}
}
}
// check the segment for the first "Cluster"-element (if not found yet)
if ( ! firstClusterFound ) {
for ( level1Element = level0Element - > firstChild ( ) ; level1Element ; level1Element = level1Element - > nextSibling ( ) ) {
level1Element - > parse ( ) ;
switch ( level1Element - > id ( ) ) {
case EbmlIds : : Void :
currentPadding + = level1Element - > totalSize ( ) ;
break ;
case MatroskaIds : : Cluster :
currentFirstClusterOffset = level1Element - > startOffset ( ) ;
firstClusterFound = true ;
break ;
}
}
}
break ;
2015-11-07 15:23:36 +01:00
}
}
2015-11-11 11:52:32 +01:00
if ( ! rewriteRequired ) {
rewriteRequired = currentPadding < = fileInfo ( ) . maxPadding ( ) & & currentPadding > = fileInfo ( ) . minPadding ( ) ;
}
2015-11-07 15:23:36 +01:00
// prepare rewriting the file
2015-11-11 11:52:32 +01:00
//TODO: do backup stuff only when rewrite is really required
2015-11-07 15:23:36 +01:00
updateStatus ( " Preparing for rewriting Matroska/EBML file ... " ) ;
fileInfo ( ) . close ( ) ; // ensure the file is close before renaming it
string backupPath ;
fstream & outputStream = fileInfo ( ) . stream ( ) ;
BinaryWriter outputWriter ( & outputStream ) ;
fstream backupStream ; // create a stream to open the backup/original file
try {
BackupHelper : : createBackupFile ( fileInfo ( ) . path ( ) , backupPath , backupStream ) ;
// set backup stream as associated input stream since we need the original elements to write the new file
setStream ( backupStream ) ;
// recreate original file, define buffer variables
outputStream . open ( fileInfo ( ) . path ( ) , ios_base : : out | ios_base : : binary | ios_base : : trunc ) ;
// define needed variables
uint64 elementSize ; // the size of the current element
uint64 clusterSize ; // the new size the current cluster
uint64 clusterReadOffset ; // the read offset of the current cluster
uint64 clusterReadSize ; // the original size of the current cluster
vector < uint64 > clusterSizes ; // the sizes of the cluster elements
vector < uint64 > : : const_iterator clusterSizesIterator ;
uint64 readOffset = 0 ; // the current read offset to calculate positions
uint64 currentOffset = 0 ; // the current write offset to calculate positions
uint64 offset ; // offset of the segment which is currently written, offset of "Cues"-element in segment
bool cuesPresent ; // whether the "Cues"-element is present in the current segment
vector < tuple < uint64 , uint64 > > crc32Offsets ; // holds the offsets of all CRC-32 elements and the length of the enclosing block
bool elementHasCrc32 ; // whether the current segment has a CRC-32 element
byte sizeLength ; // size length used to make size denotations
char buff [ 8 ] ; // buffer used to make size denotations
// calculate EBML header size
2015-11-11 11:52:32 +01:00
uint64 ebmlHeaderSize = 2 * 7 ; // sub element ID sizes
2015-11-07 15:23:36 +01:00
for ( auto headerValue : initializer_list < uint64 > { m_version , m_readVersion , m_maxIdLength , m_maxSizeLength , m_doctypeVersion , m_doctypeReadVersion } ) {
2015-11-11 11:52:32 +01:00
ebmlHeaderSize + = sizeLength = EbmlElement : : calculateUIntegerLength ( headerValue ) ;
ebmlHeaderSize + = EbmlElement : : calculateSizeDenotationLength ( sizeLength ) ;
2015-11-07 15:23:36 +01:00
}
2015-11-11 11:52:32 +01:00
ebmlHeaderSize + = m_doctype . size ( ) ;
ebmlHeaderSize + = EbmlElement : : calculateSizeDenotationLength ( m_doctype . size ( ) ) ;
// prepare writing segments
2015-11-07 15:23:36 +01:00
uint64 segmentInfoElementDataSize ;
MatroskaSeekInfo seekInfo ;
MatroskaCuePositionUpdater cuesUpdater ;
unsigned int segmentIndex = 0 ;
unsigned int index ;
try {
for ( level0Element = firstElement ( ) ; level0Element ; level0Element = level0Element - > nextSibling ( ) ) {
switch ( level0Element - > id ( ) ) {
case EbmlIds : : Header :
break ; // header is already written; skip header here
case EbmlIds : : Void :
case EbmlIds : : Crc32 :
break ;
case MatroskaIds : : Segment :
// write "Segment" element
updateStatus ( " Prepare writing segment ... " , 0.0 ) ;
// prepare writing tags
// ensure seek info contains no old entries
seekInfo . clear ( ) ;
// parse cues
cuesUpdater . invalidateNotifications ( ) ;
if ( ( level1Element = level0Element - > childById ( MatroskaIds : : Cues ) ) ) {
cuesPresent = true ;
2015-04-22 19:22:01 +02:00
try {
2015-11-07 15:23:36 +01:00
cuesUpdater . parse ( level1Element ) ;
2015-04-22 19:22:01 +02:00
} catch ( Failure & ) {
2015-11-07 15:23:36 +01:00
addNotifications ( cuesUpdater ) ;
throw ;
2015-04-22 19:22:01 +02:00
}
addNotifications ( cuesUpdater ) ;
2015-10-14 19:42:48 +02:00
} else {
2015-11-07 15:23:36 +01:00
cuesPresent = false ;
2015-10-14 19:42:48 +02:00
}
2015-11-07 15:23:36 +01:00
// check whether the segment has a CRC-32 element
elementHasCrc32 = level0Element - > firstChild ( ) & & level0Element - > firstChild ( ) - > id ( ) = = EbmlIds : : Crc32 ;
// calculate segment size
calculateSegmentSize :
// CRC-32 element is 6 byte long
elementSize = elementHasCrc32 ? 6 : 0 ;
// calculate size of "SeekHead"-element
elementSize + = seekInfo . actualSize ( ) ;
// pretend writing elements to find out the offsets and the total segment size
// pretend writing "SegmentInfo"-element
for ( level1Element = level0Element - > childById ( MatroskaIds : : SegmentInfo ) , index = 0 ; level1Element ; level1Element = level1Element - > siblingById ( MatroskaIds : : SegmentInfo ) , + + index ) {
2015-04-22 19:22:01 +02:00
// update offset in "SeekHead"-element
2015-11-07 15:23:36 +01:00
if ( seekInfo . push ( index , MatroskaIds : : SegmentInfo , currentOffset + elementSize ) ) {
2015-04-22 19:22:01 +02:00
goto calculateSegmentSize ;
} else {
2015-11-07 15:23:36 +01:00
// 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
2015-04-22 19:22:01 +02:00
for ( level2Element = level1Element - > firstChild ( ) ; level2Element ; level2Element = level2Element - > nextSibling ( ) ) {
level2Element - > parse ( ) ;
switch ( level2Element - > id ( ) ) {
2015-11-07 15:23:36 +01:00
case EbmlIds : : Void : // skipped
case EbmlIds : : Crc32 : // skipped
case MatroskaIds : : Title : // calculated separately
case MatroskaIds : : MuxingApp : // calculated separately
case MatroskaIds : : WrittingApp : // calculated separately
2015-04-22 19:22:01 +02:00
break ;
default :
2015-11-07 15:23:36 +01:00
segmentInfoElementDataSize + = level2Element - > totalSize ( ) ;
2015-04-22 19:22:01 +02:00
}
}
2015-11-07 15:23:36 +01:00
// -> calculate total size
elementSize + = 4 + EbmlElement : : calculateSizeDenotationLength ( segmentInfoElementDataSize ) + segmentInfoElementDataSize ;
2015-04-22 19:22:01 +02:00
}
}
2015-11-07 15:23:36 +01:00
// 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 ) ) {
goto calculateSegmentSize ;
} else {
// add size of element
elementSize + = level1Element - > totalSize ( ) ;
}
2015-10-14 19:42:48 +02:00
}
}
2015-11-07 15:23:36 +01:00
// 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
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 ;
}
2015-10-14 19:42:48 +02:00
}
}
2015-11-07 15:23:36 +01:00
// pretend writing "Cues"-element
if ( cuesPresent ) {
offset = elementSize ; // save current offset
// update offset of "Cues"-element in "SeekHead"-element
if ( seekInfo . push ( 0 , MatroskaIds : : Cues , currentOffset + elementSize ) ) {
goto calculateSegmentSize ;
} else {
// add size of "Cues"-element
addCuesElementSize :
elementSize + = cuesUpdater . totalSize ( ) ;
}
2015-04-22 19:22:01 +02:00
}
2015-11-07 15:23:36 +01:00
// pretend writing "Cluster"-element
clusterSizes . clear ( ) ;
for ( level1Element = level0Element - > childById ( MatroskaIds : : Cluster ) , index = 0 ; level1Element ; level1Element = level1Element - > siblingById ( MatroskaIds : : Cluster ) , + + index ) {
// update offset of "Cluster"-element in "Cues"-element
//if(cuesPresent && cuesUpdater.updatePositions(currentOffset + level1Element->startOffset() - level0Element->dataOffset(), elementSize)) {
clusterReadOffset = level1Element - > startOffset ( ) - level0Element - > dataOffset ( ) + readOffset ;
if ( cuesPresent & & cuesUpdater . updateOffsets ( clusterReadOffset , currentOffset + elementSize ) ) {
elementSize = offset ; // reset element size to previously saved offset of "Cues"-element
goto addCuesElementSize ;
} else {
if ( index = = 0 & & seekInfo . push ( index , MatroskaIds : : Cluster , currentOffset + elementSize ) ) {
goto calculateSegmentSize ;
} else {
// add size of "Cluster"-element
clusterSize = 0 ;
clusterReadSize = 0 ;
for ( level2Element = level1Element - > firstChild ( ) ; level2Element ; level2Element = level2Element - > nextSibling ( ) ) {
level2Element - > parse ( ) ;
if ( cuesPresent & & cuesUpdater . updateRelativeOffsets ( clusterReadOffset , clusterReadSize , clusterSize ) ) {
elementSize = offset ;
goto addCuesElementSize ;
}
switch ( level2Element - > id ( ) ) {
case EbmlIds : : Void :
case EbmlIds : : Crc32 :
break ;
case MatroskaIds : : Position :
clusterSize + = 1 + 1 + EbmlElement : : calculateUIntegerLength ( currentOffset + elementSize ) ;
break ;
default :
clusterSize + = level2Element - > totalSize ( ) ;
}
clusterReadSize + = level2Element - > totalSize ( ) ;
}
clusterSizes . push_back ( clusterSize ) ;
elementSize + = 4 + EbmlElement : : calculateSizeDenotationLength ( clusterSize ) + clusterSize ;
}
}
2015-04-22 19:22:01 +02:00
}
2015-11-07 15:23:36 +01:00
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 ;
}
}
}
2015-11-11 11:52:32 +01:00
// start the actual writing
if ( ! segmentIndex ) {
// nothing written so far (just prepared)
// -> decided whether it is necessary to rewrite the entire file
// TODO
// write EBML header (before writing the first segment)
updateStatus ( " Writing EBML header ... " ) ;
outputWriter . writeUInt32BE ( EbmlIds : : Header ) ;
sizeLength = EbmlElement : : makeSizeDenotation ( ebmlHeaderSize , buff ) ;
outputStream . write ( buff , sizeLength ) ;
EbmlElement : : makeSimpleElement ( outputStream , EbmlIds : : Version , m_version ) ;
EbmlElement : : makeSimpleElement ( outputStream , EbmlIds : : ReadVersion , m_readVersion ) ;
EbmlElement : : makeSimpleElement ( outputStream , EbmlIds : : MaxIdLength , m_maxIdLength ) ;
EbmlElement : : makeSimpleElement ( outputStream , EbmlIds : : MaxSizeLength , m_maxSizeLength ) ;
EbmlElement : : makeSimpleElement ( outputStream , EbmlIds : : DocType , m_doctype ) ;
EbmlElement : : makeSimpleElement ( outputStream , EbmlIds : : DocTypeVersion , m_doctypeVersion ) ;
EbmlElement : : makeSimpleElement ( outputStream , EbmlIds : : DocTypeReadVersion , m_doctypeReadVersion ) ;
}
2015-11-07 15:23:36 +01:00
// write "Segment"-element actually
updateStatus ( " Writing segment header ... " ) ;
outputWriter . writeUInt32BE ( MatroskaIds : : Segment ) ;
sizeLength = EbmlElement : : makeSizeDenotation ( elementSize , buff ) ;
2015-04-22 19:22:01 +02:00
outputStream . write ( buff , sizeLength ) ;
2015-11-07 15:23:36 +01:00
offset = outputStream . tellp ( ) ; // store segment data offset here
// write CRC-32 element ...
if ( elementHasCrc32 ) {
// ... if the original element had a CRC-32 element
* buff = EbmlIds : : Crc32 ;
* ( buff + 1 ) = 0x84 ; // length denotation: 4 byte
// set the value after writing the element
crc32Offsets . emplace_back ( outputStream . tellp ( ) , elementSize ) ;
outputStream . write ( buff , 6 ) ;
2015-04-22 19:22:01 +02:00
}
2015-11-07 15:23:36 +01:00
// write "SeekHead"-element (except there is no seek information for the current segment)
seekInfo . invalidateNotifications ( ) ;
seekInfo . make ( outputStream ) ;
addNotifications ( seekInfo ) ;
// 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 ) ;
2015-04-22 19:22:01 +02:00
}
2015-11-07 15:23:36 +01:00
// 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 ) ;
}
}
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
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
}
}
// write "Cues"-element
if ( cuesPresent ) {
try {
cuesUpdater . make ( outputStream ) ;
} catch ( Failure & ) {
addNotifications ( cuesUpdater ) ;
throw ;
2015-04-22 19:22:01 +02:00
}
}
2015-11-07 15:23:36 +01:00
// update status, check whether the operation has been aborted
2015-04-22 19:22:01 +02:00
if ( isAborted ( ) ) {
throw OperationAbortedException ( ) ;
} else {
2015-11-07 15:23:36 +01:00
addNotifications ( cuesUpdater ) ;
updateStatus ( " Writing segment data ... " , static_cast < double > ( static_cast < uint64 > ( outputStream . tellp ( ) ) - offset ) / elementSize ) ;
}
// write "Cluster"-element
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
outputWriter . writeUInt32BE ( MatroskaIds : : Cluster ) ;
sizeLength = EbmlElement : : makeSizeDenotation ( * clusterSizesIterator , buff ) ;
outputStream . write ( buff , sizeLength ) ;
// write childs
for ( level2Element = level1Element - > firstChild ( ) ; level2Element ; level2Element = level2Element - > nextSibling ( ) ) {
switch ( level2Element - > id ( ) ) {
case EbmlIds : : Void :
case EbmlIds : : Crc32 :
break ;
case MatroskaIds : : Position :
EbmlElement : : makeSimpleElement ( outputStream , MatroskaIds : : Position , clusterSize ) ;
break ;
default :
level2Element - > copyEntirely ( outputStream ) ;
}
}
// update percentage, check whether the operation has been aborted
if ( isAborted ( ) ) {
throw OperationAbortedException ( ) ;
} else {
updatePercentage ( static_cast < double > ( static_cast < uint64 > ( outputStream . tellp ( ) ) - offset ) / elementSize ) ;
}
2015-04-22 19:22:01 +02:00
}
2015-11-07 15:23:36 +01:00
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
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 ;
default :
// just copy any unknown top-level elements
addNotification ( NotificationType : : Warning , " The top-level element \" " + level0Element - > idToString ( ) + " \" of the original file is unknown and will just be copied. " , context ) ;
level0Element - > copyEntirely ( outputStream ) ;
2015-04-22 19:22:01 +02:00
}
}
2015-11-11 11:52:32 +01:00
} catch ( OperationAbortedException & ) {
throw ;
2015-11-07 15:23:36 +01:00
} catch ( Failure & ) {
2015-11-11 11:52:32 +01:00
// any failures here are caused because the original faile could not be parsed correctly (except OperationAbortedException which is handled above)
2015-11-07 15:23:36 +01:00
addNotifications ( cuesUpdater ) ;
addNotification ( NotificationType : : Critical , " Unable to parse content in top-level element at " + numberToString ( level0Element - > startOffset ( ) ) + " of original file. " , context ) ;
throw ;
}
// reparse what is written so far
updateStatus ( " Reparsing output file ... " ) ;
outputStream . close ( ) ; // the outputStream needs to be reopened to be able to read again
outputStream . open ( fileInfo ( ) . path ( ) , ios_base : : in | ios_base : : out | ios_base : : binary ) ;
setStream ( outputStream ) ;
reset ( ) ;
try {
parseHeader ( ) ;
} catch ( Failure & ) {
addNotification ( NotificationType : : Critical , " Unable to reparse the header of the new file. " , context ) ;
throw ;
}
// update CRC-32 checksums
if ( ! crc32Offsets . empty ( ) ) {
updateStatus ( " Updating CRC-32 checksums ... " ) ;
for ( const auto & crc32Offset : crc32Offsets ) {
outputStream . seekg ( get < 0 > ( crc32Offset ) + 6 ) ;
outputStream . seekp ( get < 0 > ( crc32Offset ) + 2 ) ;
writer ( ) . writeUInt32LE ( reader ( ) . readCrc32 ( get < 1 > ( crc32Offset ) - 6 ) ) ;
}
}
updatePercentage ( 100.0 ) ;
// flush output stream
outputStream . flush ( ) ;
// handle errors which occured after renaming/creating backup file
} catch ( OperationAbortedException & ) {
setStream ( outputStream ) ;
reset ( ) ;
addNotification ( NotificationType : : Information , " Rewriting the file to apply changed tag information has been aborted. " , context ) ;
BackupHelper : : restoreOriginalFileFromBackupFile ( fileInfo ( ) . path ( ) , backupPath , outputStream , backupStream ) ;
2015-04-22 19:22:01 +02:00
throw ;
} catch ( Failure & ) {
2015-11-07 15:23:36 +01:00
setStream ( outputStream ) ;
reset ( ) ;
addNotification ( NotificationType : : Critical , " Rewriting the file to apply changed tag information failed. " , context ) ;
BackupHelper : : restoreOriginalFileFromBackupFile ( fileInfo ( ) . path ( ) , backupPath , outputStream , backupStream ) ;
throw ;
} catch ( ios_base : : failure & ) {
setStream ( outputStream ) ;
reset ( ) ;
addNotification ( NotificationType : : Critical , " An IO error occured when rewriting the file to apply changed tag information. " , context ) ;
BackupHelper : : restoreOriginalFileFromBackupFile ( fileInfo ( ) . path ( ) , backupPath , outputStream , backupStream ) ;
2015-04-22 19:22:01 +02:00
throw ;
}
2015-11-07 15:23:36 +01:00
// handle errors which occured before renaming/creating backup file
2015-04-22 19:22:01 +02:00
} catch ( Failure & ) {
2015-11-07 15:23:36 +01:00
addNotification ( NotificationType : : Critical , " Parsing the original file failed. " , context ) ;
2015-04-22 19:22:01 +02:00
throw ;
} catch ( ios_base : : failure & ) {
2015-11-07 15:23:36 +01:00
addNotification ( NotificationType : : Critical , " An IO error occured when parsing the original file. " , context ) ;
2015-04-22 19:22:01 +02:00
throw ;
}
}
}