Tag Parser 11.2.1
C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags
matroskatagfield.cpp
Go to the documentation of this file.
2#include "./ebmlelement.h"
4
5#include "../exceptions.h"
6
7#include <c++utilities/io/binarywriter.h>
8
9#include <memory>
10
11using namespace std;
12using namespace CppUtilities;
13
14namespace TagParser {
15
25{
26}
27
31MatroskaTagField::MatroskaTagField(const string &id, const TagValue &value)
32 : TagField<MatroskaTagField>(id, value)
33{
34}
35
46void MatroskaTagField::reparse(EbmlElement &simpleTagElement, Diagnostics &diag, bool parseNestedFields)
47{
48 string context("parsing Matroska tag field");
49 simpleTagElement.parse(diag);
50 bool tagDefaultFound = false, tagLanguageFound = false, tagLanguageIETFFound = false;
51 for (EbmlElement *child = simpleTagElement.firstChild(); child; child = child->nextSibling()) {
52 try {
53 child->parse(diag);
54 } catch (const Failure &) {
55 diag.emplace_back(DiagLevel::Critical, "Unable to parse children of \"SimpleTag\"-element.", context);
56 break;
57 }
58 switch (child->id()) {
60 if (id().empty()) {
61 setId(child->readString());
62 context = "parsing Matroska tag field " + id();
63 } else {
64 diag.emplace_back(DiagLevel::Warning,
65 "\"SimpleTag\"-element contains multiple \"TagName\"-elements. Surplus TagName elements will be ignored.", context);
66 }
67 break;
70 if (value().isEmpty()) {
71 unique_ptr<char[]> buffer = make_unique<char[]>(child->dataSize());
72 child->stream().seekg(static_cast<streamoff>(child->dataOffset()));
73 child->stream().read(buffer.get(), static_cast<streamoff>(child->dataSize()));
74 switch (child->id()) {
76 value().assignData(move(buffer), child->dataSize(), TagDataType::Text, TagTextEncoding::Utf8);
77 break;
79 value().assignData(move(buffer), child->dataSize(), TagDataType::Undefined);
80 break;
81 }
82 } else {
83 diag.emplace_back(DiagLevel::Warning,
84 "\"SimpleTag\"-element contains multiple \"TagString\"/\"TagBinary\"-elements. Surplus \"TagName\"/\"TagBinary\"-elements will "
85 "be ignored.",
86 context);
87 }
88 break;
90 if (!tagLanguageFound) {
91 tagLanguageFound = true;
92 auto language = child->readString();
93 if (language != "und") {
94 value().locale().emplace_back(std::move(language), LocaleFormat::ISO_639_2_B);
95 }
96 } else {
97 diag.emplace_back(DiagLevel::Warning,
98 "\"SimpleTag\"-element contains multiple \"TagLanguage\"-elements. Surplus \"TagLanguage\"-elements will be ignored.", context);
99 }
100 break;
102 if (!tagLanguageIETFFound) {
103 tagLanguageIETFFound = true;
104 value().locale().emplace_back(child->readString(), LocaleFormat::BCP_47);
105 } else {
106 diag.emplace_back(DiagLevel::Warning,
107 "\"SimpleTag\"-element contains multiple \"TagLanguageIETF\"-elements. Surplus \"TagLanguageIETF\"-elements will be ignored.",
108 context);
109 }
110 break;
112 if (!tagDefaultFound) {
113 setDefault(child->readUInteger() > 0);
114 tagDefaultFound = true;
115 } else {
116 diag.emplace_back(DiagLevel::Warning,
117 "\"SimpleTag\"-element contains multiple \"TagDefault\" elements. Surplus \"TagDefault\"-elements will be ignored.", context);
118 }
119 break;
121 if (parseNestedFields) {
122 nestedFields().emplace_back();
123 nestedFields().back().reparse(*child, diag, true);
124 } else {
125 diag.emplace_back(DiagLevel::Warning,
126 "Nested fields are currently not supported. Nested tags can not be displayed and will be discarded when rewriting the file.",
127 context);
128 }
129 break;
130 case EbmlIds::Crc32:
131 case EbmlIds::Void:
132 break;
133 default:
134 diag.emplace_back(DiagLevel::Warning,
135 argsToString(
136 "\"SimpleTag\"-element contains unknown element ", child->idToString(), " at ", child->startOffset(), ". It will be ignored."),
137 context);
138 }
139 }
140}
141
153{
154 static const string context("making Matroska \"SimpleTag\" element.");
155 // check whether ID is empty
156 if (id().empty()) {
157 diag.emplace_back(DiagLevel::Critical, "Can not make \"SimpleTag\" element with empty \"TagName\".", context);
158 throw InvalidDataException();
159 }
160 try {
161 return MatroskaTagFieldMaker(*this, diag);
162 } catch (const ConversionException &) {
163 diag.emplace_back(DiagLevel::Critical, "The assigned tag value can not be converted to be written appropriately.", context);
164 throw InvalidDataException();
165 }
166}
167
174void MatroskaTagField::make(ostream &stream, Diagnostics &diag)
175{
176 prepareMaking(diag).make(stream);
177}
178
183void MatroskaTagField::normalizeId(std::string &id)
184{
185 for (auto &c : id) {
186 if (c >= 'a' && c <= 'z') {
187 c -= 'a' - 'A';
188 }
189 }
190}
191
203MatroskaTagFieldMaker::MatroskaTagFieldMaker(MatroskaTagField &field, Diagnostics &diag)
204 : m_field(field)
205 , m_language(m_field.value().locale().abbreviatedName(LocaleFormat::ISO_639_2_B, LocaleFormat::Unknown))
206 , m_languageIETF(m_field.value().locale().abbreviatedName(LocaleFormat::BCP_47))
207 , m_isBinary(false)
208{
209 try {
210 m_stringValue = m_field.value().toString();
211 } catch (const ConversionException &) {
212 diag.emplace_back(DiagLevel::Warning,
213 "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 "
214 "official Matroska specifiecation doesn't list any binary fields).",
215 "making Matroska \"SimpleTag\" element.");
216 m_isBinary = true;
217 }
218
219 // compute size of the mandatory "TagLanguage" element (if there's no language set, the 3 byte long value "und" is used)
220 const auto languageSize = m_language.empty() ? 3 : m_language.size();
221 const auto languageElementSize = 2 + EbmlElement::calculateSizeDenotationLength(languageSize) + languageSize;
222 // compute size of the optional "TagLanguageIETF" element
223 const auto languageIETFElementSize
224 = m_languageIETF.empty() ? 0 : (2 + EbmlElement::calculateSizeDenotationLength(m_languageIETF.size()) + m_languageIETF.size());
225
226 // compute "SimpleTag" element size
227 m_simpleTagSize =
228 // "TagName" element
229 +2 + EbmlElement::calculateSizeDenotationLength(m_field.id().size())
230 + m_field.id().size()
231 // language elements
232 + languageElementSize
233 + languageIETFElementSize
234 // "TagDefault" element
235 + 2 + 1
236 + 1
237 // "TagString" element
238 + 2 + EbmlElement::calculateSizeDenotationLength(m_stringValue.size()) + m_stringValue.size();
239
240 // compute size of nested tags
241 for (auto &nestedField : field.nestedFields()) {
242 m_nestedMaker.emplace_back(nestedField.prepareMaking(diag));
243 m_simpleTagSize += m_nestedMaker.back().m_totalSize;
244 }
245 m_totalSize = 2 + EbmlElement::calculateSizeDenotationLength(m_simpleTagSize) + m_simpleTagSize;
246}
247
255void MatroskaTagFieldMaker::make(ostream &stream) const
256{
257 BinaryWriter writer(&stream);
258 char buff[8];
259 // write "SimpleTag" element
260 writer.writeUInt16BE(MatroskaIds::SimpleTag);
261 std::uint8_t sizeDenotationLen = EbmlElement::makeSizeDenotation(m_simpleTagSize, buff);
262 stream.write(buff, sizeDenotationLen);
263 // write "TagName" element
264 writer.writeUInt16BE(MatroskaIds::TagName);
265 sizeDenotationLen = EbmlElement::makeSizeDenotation(m_field.id().size(), buff);
266 stream.write(buff, sizeDenotationLen);
267 stream.write(m_field.id().c_str(), static_cast<std::streamsize>(m_field.id().size()));
268 // write "TagLanguage" element
269 writer.writeUInt16BE(MatroskaIds::TagLanguage);
270 if (m_language.empty()) {
271 stream.put(static_cast<ostream::char_type>(0x80 | 3));
272 stream.write("und", 3);
273 } else {
274 sizeDenotationLen = EbmlElement::makeSizeDenotation(m_language.size(), buff);
275 stream.write(buff, sizeDenotationLen);
276 stream.write(m_language.data(), static_cast<std::streamsize>(m_language.size()));
277 }
278 // write "TagLanguageIETF" element
279 if (!m_languageIETF.empty()) {
280 writer.writeUInt16BE(MatroskaIds::TagLanguageIETF);
281 sizeDenotationLen = EbmlElement::makeSizeDenotation(m_languageIETF.size(), buff);
282 stream.write(buff, sizeDenotationLen);
283 stream.write(m_languageIETF.data(), static_cast<std::streamsize>(m_languageIETF.size()));
284 }
285 // write "TagDefault" element
286 writer.writeUInt16BE(MatroskaIds::TagDefault);
287 stream.put(static_cast<ostream::char_type>(0x80 | 1));
288 stream.put(m_field.isDefault() ? 1 : 0);
289 // write "TagString"/"TagBinary" element
290 if (m_isBinary) {
291 writer.writeUInt16BE(MatroskaIds::TagBinary);
292 sizeDenotationLen = EbmlElement::makeSizeDenotation(m_field.value().dataSize(), buff);
293 stream.write(buff, sizeDenotationLen);
294 stream.write(m_field.value().dataPointer(), static_cast<std::streamsize>(m_field.value().dataSize()));
295 } else {
296 writer.writeUInt16BE(MatroskaIds::TagString);
297 sizeDenotationLen = EbmlElement::makeSizeDenotation(m_stringValue.size(), buff);
298 stream.write(buff, sizeDenotationLen);
299 stream.write(m_stringValue.data(), static_cast<std::streamsize>(m_stringValue.size()));
300 }
301 // make nested tags
302 for (const auto &maker : m_nestedMaker) {
303 maker.make(stream);
304 }
305}
306
307} // namespace TagParser
The Diagnostics class is a container for DiagMessage.
Definition: diagnostics.h:156
The EbmlElement class helps to parse EBML files such as Matroska files.
Definition: ebmlelement.h:32
static std::uint8_t calculateSizeDenotationLength(std::uint64_t size)
Returns the length of the size denotation for the specified size in byte.
static std::uint8_t makeSizeDenotation(std::uint64_t size, char *buff)
Makes the size denotation for the specified size and stores it to buff.
The class inherits from std::exception and serves as base class for exceptions thrown by the elements...
Definition: exceptions.h:11
ImplementationType * nextSibling()
Returns the next sibling of the element.
ImplementationType * firstChild()
Returns the first child of the element.
void parse(Diagnostics &diag)
Parses the header information of the element which is read from the related stream at the start offse...
The exception that is thrown when the data to be parsed or to be made seems invalid and therefore can...
Definition: exceptions.h:25
The MatroskaTagFieldMaker class helps making tag fields.
void make(std::ostream &stream) const
Saves the field (specified when constructing the object) to the specified stream (makes a "SimpleTag"...
The MatroskaTagField class is used by MatroskaTag to store the fields.
static void normalizeId(std::string &id)
Ensures the specified id is upper-case as recommended by the Matroska spec.
void reparse(EbmlElement &simpleTagElement, Diagnostics &diag, bool parseNestedFields=true)
Parses field information from the specified EbmlElement.
MatroskaTagFieldMaker prepareMaking(Diagnostics &diag)
Prepares making.
void make(std::ostream &stream, Diagnostics &diag)
Saves the field to the specified stream (makes a "SimpleTag" element).
MatroskaTagField()
Constructs a new MatroskaTagField.
The TagField class is used by FieldMapBasedTag to store the fields.
void setDefault(bool isDefault)
Sets whether the field is labeled as default.
IdentifierType & id()
Returns the id of the current TagField.
const std::vector< MatroskaTagField > & nestedFields() const
Returns the nested fields.
void setId(const IdentifierType &id)
Sets the id of the current Tag Field.
bool isDefault() const
Returns an indication whether the field is labeled as default.
TagValue & value()
Returns the value of the current TagField.
The TagValue class wraps values of different types.
Definition: tagvalue.h:95
void assignData(const char *data, std::size_t length, TagDataType type=TagDataType::Binary, TagTextEncoding encoding=TagTextEncoding::Latin1)
std::size_t dataSize() const
Returns the size of the assigned value in bytes.
Definition: tagvalue.h:557
const Locale & locale() const
Returns the locale.
Definition: tagvalue.h:651
char * dataPointer()
Returns a pointer to the raw data assigned to the current instance.
Definition: tagvalue.h:568
constexpr TAG_PARSER_EXPORT std::string_view language()
Contains all classes and functions of the TagInfo library.
Definition: aaccodebook.h:10
LocaleFormat
The LocaleFormat enum class specifies the format used by a LocaleDetail.
Definition: localehelper.h:14