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;
|
2019-06-10 22:49:11 +02:00
|
|
|
using namespace CppUtilities;
|
2015-04-22 19:22:01 +02:00
|
|
|
|
2018-03-06 23:09:15 +01:00
|
|
|
namespace TagParser {
|
2015-04-22 19:22:01 +02:00
|
|
|
|
|
|
|
/*!
|
2018-06-03 20:38:32 +02:00
|
|
|
* \class TagParser::MatroskaTagField
|
2015-04-22 19:22:01 +02:00
|
|
|
* \brief The MatroskaTagField class is used by MatroskaTag to store the fields.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Constructs a new MatroskaTagField.
|
|
|
|
*/
|
|
|
|
MatroskaTagField::MatroskaTagField()
|
2018-03-07 01:17:50 +01:00
|
|
|
{
|
|
|
|
}
|
2015-04-22 19:22:01 +02:00
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Constructs a new MatroskaTagField with the specified \a id and \a value.
|
|
|
|
*/
|
2018-03-07 01:17:50 +01:00
|
|
|
MatroskaTagField::MatroskaTagField(const string &id, const TagValue &value)
|
|
|
|
: TagField<MatroskaTagField>(id, value)
|
|
|
|
{
|
|
|
|
}
|
2015-04-22 19:22:01 +02:00
|
|
|
|
|
|
|
/*!
|
|
|
|
* \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.
|
2018-06-03 20:38:32 +02:00
|
|
|
* \throws Throws TagParser::Failure or a derived exception when a parsing
|
2015-04-22 19:22:01 +02:00
|
|
|
* 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");
|
2018-03-05 17:49:29 +01:00
|
|
|
simpleTagElement.parse(diag);
|
2020-11-26 23:18:14 +01:00
|
|
|
bool tagDefaultFound = false, tagLanguageFound = false, tagLanguageIETFFound = false;
|
2018-03-07 01:17:50 +01: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;
|
|
|
|
}
|
2018-03-07 01:17:50 +01:00
|
|
|
switch (child->id()) {
|
2015-04-22 19:22:01 +02:00
|
|
|
case MatroskaIds::TagName:
|
2018-03-07 01:17:50 +01:00
|
|
|
if (id().empty()) {
|
2015-04-22 19:22:01 +02:00
|
|
|
setId(child->readString());
|
|
|
|
context = "parsing Matroska tag field " + id();
|
|
|
|
} else {
|
2018-03-07 01:17:50 +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:
|
2018-03-07 01:17:50 +01:00
|
|
|
if (value().isEmpty()) {
|
|
|
|
unique_ptr<char[]> buffer = make_unique<char[]>(child->dataSize());
|
2018-06-02 22:56:08 +02:00
|
|
|
child->stream().seekg(static_cast<streamoff>(child->dataOffset()));
|
|
|
|
child->stream().read(buffer.get(), static_cast<streamoff>(child->dataSize()));
|
2018-03-07 01:17:50 +01:00
|
|
|
switch (child->id()) {
|
2015-04-22 19:22:01 +02:00
|
|
|
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-07 01:17:50 +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:
|
2020-12-13 18:37:15 +01:00
|
|
|
if (!tagLanguageFound) {
|
2020-11-26 23:18:14 +01:00
|
|
|
tagLanguageFound = true;
|
2020-12-13 18:37:15 +01:00
|
|
|
auto language = child->readString();
|
|
|
|
if (language != "und") {
|
|
|
|
value().locale().emplace_back(std::move(language), LocaleFormat::ISO_639_2_B);
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
2020-12-13 18:37:15 +01:00
|
|
|
} else {
|
2018-03-07 01:17:50 +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;
|
2020-11-26 23:18:14 +01:00
|
|
|
case MatroskaIds::TagLanguageIETF:
|
|
|
|
if (!tagLanguageIETFFound) {
|
|
|
|
tagLanguageIETFFound = true;
|
2020-12-13 18:37:15 +01:00
|
|
|
value().locale().emplace_back(child->readString(), LocaleFormat::BCP_47);
|
2020-11-26 23:18:14 +01:00
|
|
|
} else {
|
|
|
|
diag.emplace_back(DiagLevel::Warning,
|
|
|
|
"\"SimpleTag\"-element contains multiple \"TagLanguageIETF\"-elements. Surplus \"TagLanguageIETF\"-elements will be ignored.",
|
|
|
|
context);
|
|
|
|
}
|
|
|
|
break;
|
2015-04-22 19:22:01 +02:00
|
|
|
case MatroskaIds::TagDefault:
|
2018-03-07 01:17:50 +01:00
|
|
|
if (!tagDefaultFound) {
|
2015-04-22 19:22:01 +02:00
|
|
|
setDefault(child->readUInteger() > 0);
|
|
|
|
tagDefaultFound = true;
|
|
|
|
} else {
|
2018-03-07 01:17:50 +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:
|
2018-03-07 01:17:50 +01:00
|
|
|
if (parseNestedFields) {
|
2015-04-22 19:22:01 +02:00
|
|
|
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-07 01:17:50 +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-07 01:17:50 +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.
|
2018-06-03 20:38:32 +02:00
|
|
|
* \throws Throws TagParser::Failure or a derived exception when a making
|
2015-04-22 19:22:01 +02:00
|
|
|
* 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
|
2018-03-07 01:17:50 +01:00
|
|
|
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);
|
2018-03-07 01:17:50 +01: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.
|
2018-06-03 20:38:32 +02:00
|
|
|
* \throws Throws TagParser::Failure or a derived exception when a making
|
2015-04-22 19:22:01 +02:00
|
|
|
* 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
|
|
|
}
|
|
|
|
|
2021-08-25 18:53:20 +02:00
|
|
|
/*!
|
|
|
|
* \brief Ensures the specified \a id is upper-case as recommended by the Matroska spec.
|
|
|
|
* \sa https://matroska.org/technical/tagging.html#tag-formatting
|
|
|
|
*/
|
|
|
|
void MatroskaTagField::normalizeId(std::string &id)
|
|
|
|
{
|
|
|
|
for (auto &c : id) {
|
|
|
|
if (c >= 'a' && c <= 'z') {
|
|
|
|
c -= 'a' - 'A';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-04-22 19:22:01 +02:00
|
|
|
/*!
|
2018-06-03 20:38:32 +02:00
|
|
|
* \class TagParser::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-07 01:17:50 +01:00
|
|
|
MatroskaTagFieldMaker::MatroskaTagFieldMaker(MatroskaTagField &field, Diagnostics &diag)
|
|
|
|
: m_field(field)
|
2020-12-13 18:37:15 +01:00
|
|
|
, m_language(m_field.value().locale().abbreviatedName(LocaleFormat::ISO_639_2_B, LocaleFormat::Unknown))
|
|
|
|
, m_languageIETF(m_field.value().locale().abbreviatedName(LocaleFormat::BCP_47))
|
2018-03-07 01:17:50 +01:00
|
|
|
, 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();
|
2018-03-07 01:17:50 +01:00
|
|
|
} catch (const ConversionException &) {
|
|
|
|
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;
|
|
|
|
}
|
2020-12-13 18:37:15 +01:00
|
|
|
|
|
|
|
// compute size of the mandatory "TagLanguage" element (if there's no language set, the 3 byte long value "und" is used)
|
|
|
|
const auto languageSize = m_language.empty() ? 3 : m_language.size();
|
|
|
|
const auto languageElementSize = 2 + EbmlElement::calculateSizeDenotationLength(languageSize) + languageSize;
|
|
|
|
// compute size of the optional "TagLanguageIETF" element
|
|
|
|
const auto languageIETFElementSize
|
|
|
|
= m_languageIETF.empty() ? 0 : (2 + EbmlElement::calculateSizeDenotationLength(m_languageIETF.size()) + m_languageIETF.size());
|
|
|
|
|
|
|
|
// compute "SimpleTag" element size
|
2015-04-22 19:22:01 +02:00
|
|
|
m_simpleTagSize =
|
2018-03-07 01:17:50 +01:00
|
|
|
// "TagName" element
|
|
|
|
+2 + EbmlElement::calculateSizeDenotationLength(m_field.id().size())
|
|
|
|
+ m_field.id().size()
|
2020-12-13 18:37:15 +01:00
|
|
|
// language elements
|
|
|
|
+ languageElementSize
|
|
|
|
+ languageIETFElementSize
|
2018-03-07 01:17:50 +01:00
|
|
|
// "TagDefault" element
|
|
|
|
+ 2 + 1
|
|
|
|
+ 1
|
|
|
|
// "TagString" element
|
|
|
|
+ 2 + EbmlElement::calculateSizeDenotationLength(m_stringValue.size()) + m_stringValue.size();
|
2020-12-13 18:37:15 +01:00
|
|
|
|
|
|
|
// compute size of nested tags
|
2018-03-07 01:17:50 +01:00
|
|
|
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
|
2018-06-03 20:38:32 +02:00
|
|
|
* throw TagParser::Failure or a derived exception.
|
2015-04-22 19:22:01 +02:00
|
|
|
*/
|
|
|
|
void MatroskaTagFieldMaker::make(ostream &stream) const
|
|
|
|
{
|
|
|
|
BinaryWriter writer(&stream);
|
|
|
|
char buff[8];
|
2020-12-13 18:37:15 +01:00
|
|
|
// write "SimpleTag" element
|
2015-04-22 19:22:01 +02:00
|
|
|
writer.writeUInt16BE(MatroskaIds::SimpleTag);
|
2019-03-13 19:06:42 +01:00
|
|
|
std::uint8_t sizeDenotationLen = EbmlElement::makeSizeDenotation(m_simpleTagSize, buff);
|
2015-04-22 19:22:01 +02:00
|
|
|
stream.write(buff, sizeDenotationLen);
|
2020-12-13 18:37:15 +01:00
|
|
|
// write "TagName" element
|
2015-04-22 19:22:01 +02:00
|
|
|
writer.writeUInt16BE(MatroskaIds::TagName);
|
|
|
|
sizeDenotationLen = EbmlElement::makeSizeDenotation(m_field.id().size(), buff);
|
|
|
|
stream.write(buff, sizeDenotationLen);
|
2021-03-20 21:26:25 +01:00
|
|
|
stream.write(m_field.id().c_str(), static_cast<std::streamsize>(m_field.id().size()));
|
2020-12-13 18:37:15 +01:00
|
|
|
// write "TagLanguage" element
|
2015-04-22 19:22:01 +02:00
|
|
|
writer.writeUInt16BE(MatroskaIds::TagLanguage);
|
2020-12-13 18:37:15 +01:00
|
|
|
if (m_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 {
|
2020-12-13 18:37:15 +01:00
|
|
|
sizeDenotationLen = EbmlElement::makeSizeDenotation(m_language.size(), buff);
|
|
|
|
stream.write(buff, sizeDenotationLen);
|
2021-03-20 21:26:25 +01:00
|
|
|
stream.write(m_language.data(), static_cast<std::streamsize>(m_language.size()));
|
2020-12-13 18:37:15 +01:00
|
|
|
}
|
|
|
|
// write "TagLanguageIETF" element
|
|
|
|
if (!m_languageIETF.empty()) {
|
|
|
|
writer.writeUInt16BE(MatroskaIds::TagLanguageIETF);
|
|
|
|
sizeDenotationLen = EbmlElement::makeSizeDenotation(m_languageIETF.size(), buff);
|
2015-04-22 19:22:01 +02:00
|
|
|
stream.write(buff, sizeDenotationLen);
|
2021-03-20 21:26:25 +01:00
|
|
|
stream.write(m_languageIETF.data(), static_cast<std::streamsize>(m_languageIETF.size()));
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
2020-12-13 18:37:15 +01:00
|
|
|
// write "TagDefault" element
|
2015-04-22 19:22:01 +02:00
|
|
|
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);
|
2020-12-13 18:37:15 +01:00
|
|
|
// write "TagString"/"TagBinary" element
|
2018-03-07 01:17:50 +01:00
|
|
|
if (m_isBinary) {
|
2016-07-22 01:37:25 +02:00
|
|
|
writer.writeUInt16BE(MatroskaIds::TagBinary);
|
|
|
|
sizeDenotationLen = EbmlElement::makeSizeDenotation(m_field.value().dataSize(), buff);
|
|
|
|
stream.write(buff, sizeDenotationLen);
|
2021-03-20 21:26:25 +01:00
|
|
|
stream.write(m_field.value().dataPointer(), static_cast<std::streamsize>(m_field.value().dataSize()));
|
2016-07-22 01:37:25 +02:00
|
|
|
} else {
|
|
|
|
writer.writeUInt16BE(MatroskaIds::TagString);
|
|
|
|
sizeDenotationLen = EbmlElement::makeSizeDenotation(m_stringValue.size(), buff);
|
|
|
|
stream.write(buff, sizeDenotationLen);
|
2021-03-20 21:26:25 +01:00
|
|
|
stream.write(m_stringValue.data(), static_cast<std::streamsize>(m_stringValue.size()));
|
2016-07-22 01:37:25 +02:00
|
|
|
}
|
2015-04-22 19:22:01 +02:00
|
|
|
// make nested tags
|
2018-03-07 01:17:50 +01:00
|
|
|
for (const auto &maker : m_nestedMaker) {
|
2015-04-22 19:22:01 +02:00
|
|
|
maker.make(stream);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-07 01:17:50 +01:00
|
|
|
} // namespace TagParser
|