2015-09-06 19:57:33 +02:00
|
|
|
#include "./matroskatag.h"
|
|
|
|
#include "./ebmlelement.h"
|
2015-04-22 19:22:01 +02:00
|
|
|
|
2018-03-05 17:49:29 +01:00
|
|
|
#include "../diagnostics.h"
|
|
|
|
|
2015-04-22 19:22:01 +02:00
|
|
|
#include <initializer_list>
|
|
|
|
#include <stdexcept>
|
2021-01-30 21:53:06 +01:00
|
|
|
#include <unordered_map>
|
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::MatroskaTag
|
|
|
|
* \brief Implementation of TagParser::Tag for the Matroska container.
|
2015-04-22 19:22:01 +02:00
|
|
|
*/
|
|
|
|
|
2017-03-07 17:16:17 +01:00
|
|
|
MatroskaTag::IdentifierType MatroskaTag::internallyGetFieldId(KnownField field) const
|
2015-04-22 19:22:01 +02:00
|
|
|
{
|
|
|
|
using namespace MatroskaTagIds;
|
2018-03-07 01:17:50 +01:00
|
|
|
switch (field) {
|
|
|
|
case KnownField::Artist:
|
2021-01-30 21:53:06 +01:00
|
|
|
return std::string(artist());
|
2018-03-07 01:17:50 +01:00
|
|
|
case KnownField::Album:
|
2021-01-30 21:53:06 +01:00
|
|
|
return std::string(album());
|
2018-03-07 01:17:50 +01:00
|
|
|
case KnownField::Comment:
|
2021-01-30 21:53:06 +01:00
|
|
|
return std::string(comment());
|
2018-03-07 01:17:50 +01:00
|
|
|
case KnownField::RecordDate:
|
2021-01-30 21:53:06 +01:00
|
|
|
return std::string(dateRecorded());
|
2020-04-22 23:54:10 +02:00
|
|
|
case KnownField::ReleaseDate:
|
2021-01-30 21:53:06 +01:00
|
|
|
return std::string(dateRelease());
|
2018-03-07 01:17:50 +01:00
|
|
|
case KnownField::Title:
|
2021-01-30 21:53:06 +01:00
|
|
|
return std::string(title());
|
2018-03-07 01:17:50 +01:00
|
|
|
case KnownField::Genre:
|
2021-01-30 21:53:06 +01:00
|
|
|
return std::string(genre());
|
2018-03-07 01:17:50 +01:00
|
|
|
case KnownField::PartNumber:
|
2021-01-30 21:53:06 +01:00
|
|
|
return std::string(partNumber());
|
2018-03-07 01:17:50 +01:00
|
|
|
case KnownField::TotalParts:
|
2021-01-30 21:53:06 +01:00
|
|
|
return std::string(totalParts());
|
2018-03-07 01:17:50 +01:00
|
|
|
case KnownField::Encoder:
|
2021-01-30 21:53:06 +01:00
|
|
|
return std::string(encoder());
|
2018-03-07 01:17:50 +01:00
|
|
|
case KnownField::EncoderSettings:
|
2021-01-30 21:53:06 +01:00
|
|
|
return std::string(encoderSettings());
|
2018-03-07 01:17:50 +01:00
|
|
|
case KnownField::Bpm:
|
2021-01-30 21:53:06 +01:00
|
|
|
return std::string(bpm());
|
2018-03-07 01:17:50 +01:00
|
|
|
case KnownField::Bps:
|
2021-01-30 21:53:06 +01:00
|
|
|
return std::string(bps());
|
2018-03-07 01:17:50 +01:00
|
|
|
case KnownField::Rating:
|
2021-01-30 21:53:06 +01:00
|
|
|
return std::string(rating());
|
2018-03-07 01:17:50 +01:00
|
|
|
case KnownField::Description:
|
2021-01-30 21:53:06 +01:00
|
|
|
return std::string(description());
|
2018-03-07 01:17:50 +01:00
|
|
|
case KnownField::Lyrics:
|
2021-01-30 21:53:06 +01:00
|
|
|
return std::string(lyrics());
|
2018-03-07 01:17:50 +01:00
|
|
|
case KnownField::RecordLabel:
|
2021-01-30 21:53:06 +01:00
|
|
|
return std::string(label());
|
2018-03-07 01:17:50 +01:00
|
|
|
case KnownField::Performers:
|
2021-01-30 21:53:06 +01:00
|
|
|
return std::string(actor());
|
2018-03-07 01:17:50 +01:00
|
|
|
case KnownField::Lyricist:
|
2021-01-30 21:53:06 +01:00
|
|
|
return std::string(lyricist());
|
2018-03-07 01:17:50 +01:00
|
|
|
case KnownField::Composer:
|
2021-01-30 21:53:06 +01:00
|
|
|
return std::string(composer());
|
2018-03-07 01:17:50 +01:00
|
|
|
case KnownField::Length:
|
2021-01-30 21:53:06 +01:00
|
|
|
return std::string(duration());
|
2018-03-07 01:17:50 +01:00
|
|
|
case KnownField::Language:
|
2021-01-30 21:53:06 +01:00
|
|
|
return std::string(language());
|
2018-03-07 01:17:50 +01:00
|
|
|
default:
|
2021-01-30 21:53:06 +01:00
|
|
|
return std::string();
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-07 17:16:17 +01:00
|
|
|
KnownField MatroskaTag::internallyGetKnownField(const IdentifierType &id) const
|
2015-04-22 19:22:01 +02:00
|
|
|
{
|
|
|
|
using namespace MatroskaTagIds;
|
2021-01-30 21:53:06 +01:00
|
|
|
static const std::unordered_map<std::string_view, KnownField> fieldMap({
|
2018-03-07 01:17:50 +01:00
|
|
|
{ artist(), KnownField::Artist },
|
|
|
|
{ album(), KnownField::Album },
|
|
|
|
{ comment(), KnownField::Comment },
|
|
|
|
{ dateRecorded(), KnownField::RecordDate },
|
2020-04-22 23:54:10 +02:00
|
|
|
{ dateRelease(), KnownField::ReleaseDate },
|
2018-03-07 01:17:50 +01:00
|
|
|
{ title(), KnownField::Title },
|
|
|
|
{ partNumber(), KnownField::PartNumber },
|
|
|
|
{ totalParts(), KnownField::TotalParts },
|
|
|
|
{ encoder(), KnownField::Encoder },
|
|
|
|
{ encoderSettings(), KnownField::EncoderSettings },
|
|
|
|
{ bpm(), KnownField::Bpm },
|
|
|
|
{ bps(), KnownField::Bps },
|
|
|
|
{ rating(), KnownField::Rating },
|
|
|
|
{ description(), KnownField::Description },
|
|
|
|
{ lyrics(), KnownField::Lyrics },
|
|
|
|
{ label(), KnownField::RecordLabel },
|
|
|
|
{ actor(), KnownField::Performers },
|
|
|
|
{ lyricist(), KnownField::Lyricist },
|
|
|
|
{ composer(), KnownField::Composer },
|
|
|
|
{ duration(), KnownField::Length },
|
|
|
|
{ language(), KnownField::Language },
|
2015-04-22 19:22:01 +02:00
|
|
|
});
|
2018-05-13 20:40:30 +02:00
|
|
|
const auto knownField(fieldMap.find(id));
|
|
|
|
return knownField != fieldMap.cend() ? knownField->second : KnownField::Invalid;
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Parses tag information from the specified \a tagElement.
|
|
|
|
*
|
|
|
|
* \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 MatroskaTag::parse(EbmlElement &tagElement, Diagnostics &diag)
|
2021-08-25 18:53:20 +02:00
|
|
|
{
|
|
|
|
parse2(tagElement, MatroskaTagFlags::None, diag);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Parses tag information from the specified \a tagElement.
|
|
|
|
*
|
|
|
|
* \throws Throws std::ios_base::failure when an IO error occurs.
|
|
|
|
* \throws Throws TagParser::Failure or a derived exception when a parsing
|
|
|
|
* error occurs.
|
|
|
|
*/
|
|
|
|
void MatroskaTag::parse2(EbmlElement &tagElement, MatroskaTagFlags flags, Diagnostics &diag)
|
2015-04-22 19:22:01 +02:00
|
|
|
{
|
|
|
|
static const string context("parsing Matroska tag");
|
2021-08-08 00:05:11 +02:00
|
|
|
m_size = tagElement.totalSize();
|
2018-03-05 17:49:29 +01:00
|
|
|
tagElement.parse(diag);
|
2019-03-13 19:06:42 +01:00
|
|
|
if (tagElement.totalSize() > numeric_limits<std::uint32_t>::max()) {
|
2018-06-02 22:56:08 +02:00
|
|
|
// FIXME: Support this? Likely not very useful in practise.
|
|
|
|
diag.emplace_back(DiagLevel::Critical, "Matroska tag is too big.", context);
|
|
|
|
throw NotImplementedException();
|
|
|
|
}
|
2021-08-25 18:53:20 +02:00
|
|
|
const auto normalize = flags & MatroskaTagFlags::NormalizeKnownFieldIds;
|
2018-03-07 01:17:50 +01:00
|
|
|
for (EbmlElement *child = tagElement.firstChild(); child; child = child->nextSibling()) {
|
2018-03-05 17:49:29 +01:00
|
|
|
child->parse(diag);
|
2018-03-07 01:17:50 +01:00
|
|
|
switch (child->id()) {
|
2018-03-11 22:27:12 +01:00
|
|
|
case MatroskaIds::SimpleTag:
|
2015-04-22 19:22:01 +02:00
|
|
|
try {
|
2021-08-25 18:53:20 +02:00
|
|
|
auto field = MatroskaTagField();
|
2018-03-05 17:49:29 +01:00
|
|
|
field.reparse(*child, diag, true);
|
2021-08-25 18:53:20 +02:00
|
|
|
if (normalize) {
|
|
|
|
auto normalizedId = field.id();
|
|
|
|
MatroskaTagField::normalizeId(normalizedId);
|
|
|
|
if (internallyGetKnownField(normalizedId) != KnownField::Invalid) {
|
|
|
|
field.id() = std::move(normalizedId);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
fields().emplace(field.id(), std::move(field));
|
2018-03-07 01:17:50 +01:00
|
|
|
} catch (const Failure &) {
|
2021-08-25 18:53:20 +02:00
|
|
|
// message will be added to diag anyways
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
|
|
|
break;
|
2018-03-07 01:17:50 +01:00
|
|
|
case MatroskaIds::Targets:
|
2018-03-05 17:49:29 +01:00
|
|
|
parseTargets(*child, diag);
|
2015-04-22 19:22:01 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Parses the specified \a targetsElement.
|
|
|
|
*
|
|
|
|
* \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 MatroskaTag::parseTargets(EbmlElement &targetsElement, Diagnostics &diag)
|
2015-04-22 19:22:01 +02:00
|
|
|
{
|
|
|
|
static const string context("parsing targets of Matroska tag");
|
|
|
|
m_target.clear();
|
|
|
|
bool targetTypeValueFound = false;
|
|
|
|
bool targetTypeFound = false;
|
2018-03-05 17:49:29 +01:00
|
|
|
targetsElement.parse(diag);
|
2018-03-07 01:17:50 +01:00
|
|
|
for (EbmlElement *child = targetsElement.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);
|
2018-03-07 01:17:50 +01:00
|
|
|
} catch (const Failure &) {
|
2019-12-30 22:54:11 +01:00
|
|
|
diag.emplace_back(DiagLevel::Critical, "Unable to parse children of Targets 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::TargetTypeValue:
|
2018-03-07 01:17:50 +01:00
|
|
|
if (!targetTypeValueFound) {
|
2015-04-22 19:22:01 +02:00
|
|
|
m_target.setLevel(child->readUInteger());
|
|
|
|
targetTypeValueFound = true;
|
|
|
|
} else {
|
2018-03-07 01:17:50 +01:00
|
|
|
diag.emplace_back(
|
|
|
|
DiagLevel::Warning, "Targets element contains multiple TargetTypeValue elements. Surplus elements will be ignored.", context);
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case MatroskaIds::TargetType:
|
2018-03-07 01:17:50 +01:00
|
|
|
if (!targetTypeFound) {
|
2015-04-22 19:22:01 +02:00
|
|
|
m_target.setLevelName(child->readString());
|
|
|
|
targetTypeFound = true;
|
|
|
|
} else {
|
2018-03-07 01:17:50 +01:00
|
|
|
diag.emplace_back(
|
|
|
|
DiagLevel::Warning, "Targets element contains multiple TargetType elements. Surplus elements will be ignored.", context);
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
|
|
|
break;
|
2017-08-17 23:20:38 +02:00
|
|
|
case MatroskaIds::TagTrackUID:
|
2015-04-22 19:22:01 +02:00
|
|
|
m_target.tracks().emplace_back(child->readUInteger());
|
|
|
|
break;
|
2017-08-17 23:20:38 +02:00
|
|
|
case MatroskaIds::TagEditionUID:
|
2015-04-22 19:22:01 +02:00
|
|
|
m_target.editions().emplace_back(child->readUInteger());
|
|
|
|
break;
|
2017-08-17 23:20:38 +02:00
|
|
|
case MatroskaIds::TagChapterUID:
|
2015-04-22 19:22:01 +02:00
|
|
|
m_target.chapters().emplace_back(child->readUInteger());
|
|
|
|
break;
|
2017-08-17 23:20:38 +02:00
|
|
|
case MatroskaIds::TagAttachmentUID:
|
2015-04-22 19:22:01 +02:00
|
|
|
m_target.attachments().emplace_back(child->readUInteger());
|
|
|
|
break;
|
|
|
|
default:
|
2018-03-05 17:49:29 +01:00
|
|
|
diag.emplace_back(DiagLevel::Warning, "Targets element contains unknown element. It will be ignored.", context);
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
|
|
|
}
|
2018-03-07 01:17:50 +01:00
|
|
|
if (!m_target.level()) {
|
2015-04-22 19:22:01 +02:00
|
|
|
m_target.setLevel(50); // default level
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
2018-06-03 20:38:32 +02:00
|
|
|
* \class TagParser::MatroskaTagMaker
|
2015-04-22 19:22:01 +02:00
|
|
|
* \brief The MatroskaTagMaker class helps writing Matroska "Tag"-elements storing tag information.
|
|
|
|
*
|
|
|
|
* An instance can be obtained using the MatroskaTag::prepareMaking() method.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Prepares making the specified \a tag.
|
|
|
|
* \sa See MatroskaTag::prepareMaking() for more information.
|
|
|
|
*/
|
2018-03-07 01:17:50 +01:00
|
|
|
MatroskaTagMaker::MatroskaTagMaker(MatroskaTag &tag, Diagnostics &diag)
|
|
|
|
: m_tag(tag)
|
2015-04-22 19:22:01 +02:00
|
|
|
{
|
|
|
|
// calculate size of "Targets" element
|
|
|
|
m_targetsSize = 0; // NOT including ID and size
|
2018-03-07 01:17:50 +01:00
|
|
|
if (m_tag.target().level() != 50) {
|
2015-10-13 23:32:00 +02:00
|
|
|
// size of "TargetTypeValue"
|
2021-03-20 21:26:25 +01:00
|
|
|
m_targetsSize += 2u + 1u + EbmlElement::calculateUIntegerLength(m_tag.target().level());
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
2018-03-07 01:17:50 +01:00
|
|
|
if (!m_tag.target().levelName().empty()) {
|
2015-10-13 23:32:00 +02:00
|
|
|
// size of "TargetType"
|
2021-03-20 21:26:25 +01:00
|
|
|
m_targetsSize += 2u + EbmlElement::calculateSizeDenotationLength(m_tag.target().levelName().size()) + m_tag.target().levelName().size();
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
2019-03-13 19:06:42 +01:00
|
|
|
for (const auto &v : initializer_list<vector<std::uint64_t>>{
|
2018-03-07 01:17:50 +01:00
|
|
|
m_tag.target().tracks(), m_tag.target().editions(), m_tag.target().chapters(), m_tag.target().attachments() }) {
|
|
|
|
for (auto uid : v) {
|
2015-10-13 23:32:00 +02:00
|
|
|
// size of UID denotation
|
2021-03-20 21:26:25 +01:00
|
|
|
m_targetsSize += 2u + 1u + EbmlElement::calculateUIntegerLength(uid);
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
|
|
|
}
|
2021-03-20 21:26:25 +01:00
|
|
|
m_tagSize = 2u + EbmlElement::calculateSizeDenotationLength(m_targetsSize) + m_targetsSize;
|
2015-04-22 19:22:01 +02:00
|
|
|
// calculate size of "SimpleTag" elements
|
2015-12-22 23:54:35 +01:00
|
|
|
m_maker.reserve(m_tag.fields().size());
|
2015-04-22 19:22:01 +02:00
|
|
|
m_simpleTagsSize = 0; // including ID and size
|
2018-03-07 01:17:50 +01:00
|
|
|
for (auto &pair : m_tag.fields()) {
|
2015-04-22 19:22:01 +02:00
|
|
|
try {
|
2018-03-05 17:49:29 +01:00
|
|
|
m_maker.emplace_back(pair.second.prepareMaking(diag));
|
2015-12-22 23:54:35 +01:00
|
|
|
m_simpleTagsSize += m_maker.back().requiredSize();
|
2018-03-07 01:17:50 +01:00
|
|
|
} catch (const Failure &) {
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
m_tagSize += m_simpleTagsSize;
|
2021-03-20 21:26:25 +01:00
|
|
|
m_totalSize = 2u + EbmlElement::calculateSizeDenotationLength(m_tagSize) + m_tagSize;
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Saves the tag (specified when constructing the object) to the
|
|
|
|
* specified \a stream (makes a "Tag"-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 MatroskaTagMaker::make(ostream &stream) const
|
|
|
|
{
|
|
|
|
// write header
|
|
|
|
char buff[11];
|
2019-03-13 19:06:42 +01:00
|
|
|
BE::getBytes(static_cast<std::uint16_t>(MatroskaIds::Tag), buff);
|
2015-04-22 19:22:01 +02:00
|
|
|
stream.write(buff, 2); // ID
|
2019-03-13 19:06:42 +01:00
|
|
|
std::uint8_t len = EbmlElement::makeSizeDenotation(m_tagSize, buff);
|
2015-04-22 19:22:01 +02:00
|
|
|
stream.write(buff, len); // size
|
|
|
|
// write "Targets" element
|
2019-03-13 19:06:42 +01:00
|
|
|
BE::getBytes(static_cast<std::uint16_t>(MatroskaIds::Targets), buff);
|
2015-04-22 19:22:01 +02:00
|
|
|
stream.write(buff, 2);
|
|
|
|
len = EbmlElement::makeSizeDenotation(m_targetsSize, buff);
|
|
|
|
stream.write(buff, len);
|
|
|
|
const TagTarget &t = m_tag.target();
|
2018-03-07 01:17:50 +01:00
|
|
|
if (t.level() != 50) {
|
2015-04-22 19:22:01 +02:00
|
|
|
// write "TargetTypeValue"
|
2019-03-13 19:06:42 +01:00
|
|
|
BE::getBytes(static_cast<std::uint16_t>(MatroskaIds::TargetTypeValue), buff);
|
2015-04-22 19:22:01 +02:00
|
|
|
stream.write(buff, 2);
|
|
|
|
len = EbmlElement::makeUInteger(t.level(), buff);
|
2018-06-02 22:56:08 +02:00
|
|
|
stream.put(static_cast<char>(0x80 | len));
|
2015-04-22 19:22:01 +02:00
|
|
|
stream.write(buff, len);
|
|
|
|
}
|
2018-03-07 01:17:50 +01:00
|
|
|
if (!t.levelName().empty()) {
|
2015-04-22 19:22:01 +02:00
|
|
|
// write "TargetType"
|
2019-03-13 19:06:42 +01:00
|
|
|
BE::getBytes(static_cast<std::uint16_t>(MatroskaIds::TargetType), buff);
|
2015-04-22 19:22:01 +02:00
|
|
|
stream.write(buff, 2);
|
|
|
|
len = EbmlElement::makeSizeDenotation(t.levelName().size(), buff);
|
|
|
|
stream.write(buff, len);
|
2021-03-20 21:26:25 +01:00
|
|
|
stream.write(t.levelName().c_str(), static_cast<std::streamsize>(t.levelName().size()));
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
|
|
|
// write UIDs
|
2019-03-13 19:06:42 +01:00
|
|
|
using p = pair<std::uint16_t, vector<std::uint64_t>>;
|
2018-03-07 01:17:50 +01:00
|
|
|
for (const auto &pair : initializer_list<p>{ p(MatroskaIds::TagTrackUID, t.tracks()), p(MatroskaIds::TagEditionUID, t.editions()),
|
|
|
|
p(MatroskaIds::TagChapterUID, t.chapters()), p(MatroskaIds::TagAttachmentUID, t.attachments()) }) {
|
|
|
|
if (!pair.second.empty()) {
|
2015-04-22 19:22:01 +02:00
|
|
|
BE::getBytes(pair.first, buff);
|
2018-03-07 01:17:50 +01:00
|
|
|
for (auto uid : pair.second) {
|
2015-04-22 19:22:01 +02:00
|
|
|
len = EbmlElement::makeUInteger(uid, buff + 3);
|
2018-06-02 22:56:08 +02:00
|
|
|
*(buff + 2) = static_cast<char>(0x80 | len);
|
2015-04-22 19:22:01 +02:00
|
|
|
stream.write(buff, 3 + len);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// write "SimpleTag" elements using maker objects prepared previously
|
2018-03-07 01:17:50 +01:00
|
|
|
for (const auto &maker : m_maker) {
|
2015-04-22 19:22:01 +02:00
|
|
|
maker.make(stream);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-07 01:17:50 +01:00
|
|
|
} // namespace TagParser
|