2015-09-06 19:57:33 +02:00
# include "./matroskatagfield.h"
# include "./ebmlelement.h"
# include "./matroskacontainer.h"
2015-09-06 15:42:18 +02:00
2015-09-06 19:57:33 +02:00
# include "../exceptions.h"
2015-04-22 19:22:01 +02:00
# include <c++utilities/io/binarywriter.h>
2017-02-05 21:02:40 +01:00
# include <memory>
2015-04-22 19:22:01 +02:00
using namespace std ;
using namespace IoUtilities ;
using namespace ConversionUtilities ;
2018-03-06 23:09:15 +01:00
namespace TagParser {
2015-04-22 19:22:01 +02:00
/*!
* \ class Media : : MatroskaTagField
* \ brief The MatroskaTagField class is used by MatroskaTag to store the fields .
*/
/*!
* \ brief Constructs a new MatroskaTagField .
*/
MatroskaTagField : : MatroskaTagField ( )
{ }
/*!
* \ brief Constructs a new MatroskaTagField with the specified \ a id and \ a value .
*/
MatroskaTagField : : MatroskaTagField ( const string & id , const TagValue & value ) :
TagField < MatroskaTagField > ( id , value )
{ }
/*!
* \ brief Parses field information from the specified EbmlElement .
*
* The specified atom should be a simple tag element . These elements
* represents the fields of a MatroskaTag .
*
* \ throws Throws std : : ios_base : : failure when an IO error occurs .
* \ throws Throws Media : : Failure or a derived exception when a parsing
* error occurs .
*/
2018-03-05 17:49:29 +01:00
void MatroskaTagField : : reparse ( EbmlElement & simpleTagElement , Diagnostics & diag , bool parseNestedFields )
2015-04-22 19:22:01 +02:00
{
string context ( " parsing Matroska tag field " ) ;
clear ( ) ;
2018-03-05 17:49:29 +01:00
simpleTagElement . parse ( diag ) ;
2015-04-22 19:22:01 +02:00
bool tagDefaultFound = false ;
2017-09-21 22:41:03 +02:00
for ( EbmlElement * child = simpleTagElement . firstChild ( ) ; child ; child = child - > nextSibling ( ) ) {
2015-04-22 19:22:01 +02:00
try {
2018-03-05 17:49:29 +01:00
child - > parse ( diag ) ;
2017-09-14 18:19:30 +02:00
} catch ( const Failure & ) {
2018-03-05 17:49:29 +01:00
diag . emplace_back ( DiagLevel : : Critical , " Unable to parse children of \" SimpleTag \" -element. " , context ) ;
2015-04-22 19:22:01 +02:00
break ;
}
switch ( child - > id ( ) ) {
case MatroskaIds : : TagName :
if ( id ( ) . empty ( ) ) {
setId ( child - > readString ( ) ) ;
context = " parsing Matroska tag field " + id ( ) ;
} else {
2018-03-05 17:49:29 +01:00
diag . emplace_back ( DiagLevel : : Warning , " \" SimpleTag \" -element contains multiple \" TagName \" -elements. Surplus TagName elements will be ignored. " , context ) ;
2015-04-22 19:22:01 +02:00
}
break ;
case MatroskaIds : : TagString :
case MatroskaIds : : TagBinary :
if ( value ( ) . isEmpty ( ) ) {
unique_ptr < char [ ] > buffer = make_unique < char [ ] > ( child - > dataSize ( ) ) ;
child - > stream ( ) . seekg ( child - > dataOffset ( ) ) ;
child - > stream ( ) . read ( buffer . get ( ) , child - > dataSize ( ) ) ;
switch ( child - > id ( ) ) {
case MatroskaIds : : TagString :
value ( ) . assignData ( move ( buffer ) , child - > dataSize ( ) , TagDataType : : Text , TagTextEncoding : : Utf8 ) ;
break ;
case MatroskaIds : : TagBinary :
value ( ) . assignData ( move ( buffer ) , child - > dataSize ( ) , TagDataType : : Undefined ) ;
break ;
}
} else {
2018-03-05 17:49:29 +01:00
diag . emplace_back ( DiagLevel : : Warning , " \" SimpleTag \" -element contains multiple \" TagString \" / \" TagBinary \" -elements. Surplus \" TagName \" / \" TagBinary \" -elements will be ignored. " , context ) ;
2015-04-22 19:22:01 +02:00
}
break ;
case MatroskaIds : : TagLanguage :
if ( value ( ) . language ( ) . empty ( ) | | value ( ) . language ( ) = = " und " ) {
string lng = child - > readString ( ) ;
if ( lng ! = " und " ) {
value ( ) . setLanguage ( lng ) ;
}
} else {
2018-03-05 17:49:29 +01:00
diag . emplace_back ( DiagLevel : : Warning , " \" SimpleTag \" -element contains multiple \" TagLanguage \" -elements. Surplus \" TagLanguage \" -elements will be ignored. " , context ) ;
2015-04-22 19:22:01 +02:00
}
break ;
case MatroskaIds : : TagDefault :
if ( ! tagDefaultFound ) {
setDefault ( child - > readUInteger ( ) > 0 ) ;
tagDefaultFound = true ;
} else {
2018-03-05 17:49:29 +01:00
diag . emplace_back ( DiagLevel : : Warning , " \" SimpleTag \" -element contains multiple \" TagDefault \" elements. Surplus \" TagDefault \" -elements will be ignored. " , context ) ;
2015-04-22 19:22:01 +02:00
}
break ;
case MatroskaIds : : SimpleTag :
if ( parseNestedFields ) {
nestedFields ( ) . emplace_back ( ) ;
2018-03-05 17:49:29 +01:00
nestedFields ( ) . back ( ) . reparse ( * child , diag , true ) ;
2015-04-22 19:22:01 +02:00
} else {
2018-03-05 17:49:29 +01:00
diag . emplace_back ( DiagLevel : : Warning , " Nested fields are currently not supported. Nested tags can not be displayed and will be discarded when rewriting the file. " , context ) ;
2015-04-22 19:22:01 +02:00
}
break ;
case EbmlIds : : Crc32 :
case EbmlIds : : Void :
break ;
default :
2018-03-05 17:49:29 +01:00
diag . emplace_back ( DiagLevel : : Warning , argsToString ( " \" SimpleTag \" -element contains unknown element " , child - > idToString ( ) , " at " , child - > startOffset ( ) , " . It will be ignored. " ) , context ) ;
2015-04-22 19:22:01 +02:00
}
}
}
/*!
* \ brief Prepares making .
* \ returns Returns a MatroskaTagFieldMaker object which can be used to actually make the field .
* \ remarks The field must NOT be mutated after making is prepared when it is intended to actually
* make the field using the make method of the returned object .
* \ throws Throws Media : : Failure or a derived exception when a making
* error occurs .
*
* This method might be useful when it is necessary to know the size of the field before making it .
*/
2018-03-05 17:49:29 +01:00
MatroskaTagFieldMaker MatroskaTagField : : prepareMaking ( Diagnostics & diag )
2015-04-22 19:22:01 +02:00
{
static const string context ( " making Matroska \" SimpleTag \" element. " ) ;
// check whether ID is empty
if ( id ( ) . empty ( ) ) {
2018-03-05 17:49:29 +01:00
diag . emplace_back ( DiagLevel : : Critical , " Can not make \" SimpleTag \" element with empty \" TagName \" . " , context ) ;
2015-04-22 19:22:01 +02:00
throw InvalidDataException ( ) ;
}
try {
2018-03-05 17:49:29 +01:00
return MatroskaTagFieldMaker ( * this , diag ) ;
2016-07-22 01:37:25 +02:00
} catch ( const ConversionException & ) {
2018-03-05 17:49:29 +01:00
diag . emplace_back ( DiagLevel : : Critical , " The assigned tag value can not be converted to be written appropriately. " , context ) ;
2015-04-22 19:22:01 +02:00
throw InvalidDataException ( ) ;
}
}
/*!
* \ brief Saves the field to the specified \ a stream ( makes a " SimpleTag " element ) . *
* \ throws Throws std : : ios_base : : failure when an IO error occurs .
* \ throws Throws Media : : Failure or a derived exception when a making
* error occurs .
*/
2018-03-05 17:49:29 +01:00
void MatroskaTagField : : make ( ostream & stream , Diagnostics & diag )
2015-04-22 19:22:01 +02:00
{
2018-03-05 17:49:29 +01:00
prepareMaking ( diag ) . make ( stream ) ;
2015-04-22 19:22:01 +02:00
}
/*!
* \ class Media : : MatroskaTagFieldMaker
2015-12-10 13:50:46 +01:00
* \ brief The MatroskaTagFieldMaker class helps making tag fields .
* It allows to calculate the required size .
2015-04-22 19:22:01 +02:00
* \ sa See MatroskaTagField : : prepareMaking ( ) for more information .
*/
/*!
* \ brief Prepares making the specified \ a field .
* \ sa See MatroskaTagField : : prepareMaking ( ) for more information .
*/
2018-03-05 17:49:29 +01:00
MatroskaTagFieldMaker : : MatroskaTagFieldMaker ( MatroskaTagField & field , Diagnostics & diag ) :
2016-07-22 01:37:25 +02:00
m_field ( field ) ,
m_isBinary ( false )
2015-04-22 19:22:01 +02:00
{
2016-07-22 01:37:25 +02:00
try {
m_stringValue = m_field . value ( ) . toString ( ) ;
} catch ( const ConversionException & ) {
2018-03-05 17:49:29 +01:00
diag . emplace_back ( DiagLevel : : Warning , " The assigned tag value can not be converted to a string and is treated as binary value (which is likely not what you want since official Matroska specifiecation doesn't list any binary fields). " , " making Matroska \" SimpleTag \" element. " ) ;
2016-07-22 01:37:25 +02:00
m_isBinary = true ;
}
2015-04-22 19:22:01 +02:00
size_t languageSize = m_field . value ( ) . language ( ) . size ( ) ;
if ( ! languageSize ) {
languageSize = 3 ; // if there's no language set, the 3 byte long value "und" is used
}
m_simpleTagSize =
// "TagName" element
+ 2 + EbmlElement : : calculateSizeDenotationLength ( m_field . id ( ) . size ( ) ) + m_field . id ( ) . size ( )
// "TagLanguage" element
+ 2 + EbmlElement : : calculateSizeDenotationLength ( languageSize ) + languageSize
// "TagDefault" element
+ 2 + 1 + 1
// "TagString" element
+ 2 + EbmlElement : : calculateSizeDenotationLength ( m_stringValue . size ( ) ) + m_stringValue . size ( ) ;
// nested tags
for ( auto & nestedField : field . nestedFields ( ) ) {
2018-03-05 17:49:29 +01:00
m_nestedMaker . emplace_back ( nestedField . prepareMaking ( diag ) ) ;
2015-04-22 19:22:01 +02:00
m_simpleTagSize + = m_nestedMaker . back ( ) . m_totalSize ;
}
m_totalSize = 2 + EbmlElement : : calculateSizeDenotationLength ( m_simpleTagSize ) + m_simpleTagSize ;
}
/*!
* \ brief Saves the field ( specified when constructing the object ) to the
* specified \ a stream ( makes a " SimpleTag " element ) . *
* \ throws Throws std : : ios_base : : failure when an IO error occurs .
* \ throws Throws Assumes the data is already validated and thus does NOT
* throw Media : : Failure or a derived exception .
*/
void MatroskaTagFieldMaker : : make ( ostream & stream ) const
{
BinaryWriter writer ( & stream ) ;
char buff [ 8 ] ;
// write header of "SimpleTag" element
writer . writeUInt16BE ( MatroskaIds : : SimpleTag ) ;
byte sizeDenotationLen = EbmlElement : : makeSizeDenotation ( m_simpleTagSize , buff ) ;
stream . write ( buff , sizeDenotationLen ) ;
// write header of "TagName" element
writer . writeUInt16BE ( MatroskaIds : : TagName ) ;
sizeDenotationLen = EbmlElement : : makeSizeDenotation ( m_field . id ( ) . size ( ) , buff ) ;
stream . write ( buff , sizeDenotationLen ) ;
stream . write ( m_field . id ( ) . c_str ( ) , m_field . id ( ) . size ( ) ) ;
// write header of "TagLanguage" element
writer . writeUInt16BE ( MatroskaIds : : TagLanguage ) ;
if ( m_field . value ( ) . language ( ) . empty ( ) ) {
2016-11-26 12:33:38 +01:00
stream . put ( static_cast < ostream : : char_type > ( 0x80 | 3 ) ) ;
2015-04-22 19:22:01 +02:00
stream . write ( " und " , 3 ) ;
} else {
sizeDenotationLen = EbmlElement : : makeSizeDenotation ( m_field . value ( ) . language ( ) . size ( ) , buff ) ;
stream . write ( buff , sizeDenotationLen ) ;
stream . write ( m_field . value ( ) . language ( ) . c_str ( ) , m_field . value ( ) . language ( ) . size ( ) ) ;
}
// write header of "TagDefault" element
writer . writeUInt16BE ( MatroskaIds : : TagDefault ) ;
2016-11-26 12:33:38 +01:00
stream . put ( static_cast < ostream : : char_type > ( 0x80 | 1 ) ) ;
2015-04-22 19:22:01 +02:00
stream . put ( m_field . isDefault ( ) ? 1 : 0 ) ;
2016-07-22 01:37:25 +02:00
// write header of "TagString"/"TagBinary" element
if ( m_isBinary ) {
writer . writeUInt16BE ( MatroskaIds : : TagBinary ) ;
sizeDenotationLen = EbmlElement : : makeSizeDenotation ( m_field . value ( ) . dataSize ( ) , buff ) ;
stream . write ( buff , sizeDenotationLen ) ;
stream . write ( m_field . value ( ) . dataPointer ( ) , m_field . value ( ) . dataSize ( ) ) ;
} else {
writer . writeUInt16BE ( MatroskaIds : : TagString ) ;
sizeDenotationLen = EbmlElement : : makeSizeDenotation ( m_stringValue . size ( ) , buff ) ;
stream . write ( buff , sizeDenotationLen ) ;
stream . write ( m_stringValue . data ( ) , m_stringValue . size ( ) ) ;
}
2015-04-22 19:22:01 +02:00
// make nested tags
for ( const auto & maker : m_nestedMaker ) {
maker . make ( stream ) ;
}
}
}