Tag Parser: matroska/matroskatagfield.cpp Source File

Tag Parser 12.5.2 C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags

Loading...
Searching...
No Matches
matroskatagfield.cpp
Go to the documentation of this file.
1#include "./matroskatagfield.h"
2#include "./ebmlelement.h"
3#include "./matroskacontainer.h"
4#include "./matroskatagid.h"
5
6#include "../exceptions.h"
7
8#include <c++utilities/io/binarywriter.h>
9
10#include <memory>
11
12using namespace std;
13using namespace CppUtilities;
14
15namespace TagParser {
16
21
25MatroskaTagField::MatroskaTagField()
26{
27}
28
32 MatroskaTagField::MatroskaTagField(const string &id, const TagValue &value)
33 : TagField<MatroskaTagField>(id, value)
34{
35}
36
47 void MatroskaTagField::reparse(EbmlElement &simpleTagElement, Diagnostics &diag, bool parseNestedFields)
48{
49 string context("parsing Matroska tag field");
50 simpleTagElement.parse(diag);
51 bool tagDefaultFound = false, tagLanguageFound = false, tagLanguageIETFFound = false;
52 for (EbmlElement *child = simpleTagElement.firstChild(); child; child = child->nextSibling()) {
53 try {
54 child->parse(diag);
55 } catch (const Failure &) {
56 diag.emplace_back(DiagLevel::Critical, "Unable to parse children of \"SimpleTag\"-element.", context);
57 break;
58 }
59 switch (child->id()) {
60 case MatroskaIds::TagName:
61 if (id().empty()) {
62 setId(child->readString());
63 context = "parsing Matroska tag field " + id();
64 } else {
65 diag.emplace_back(DiagLevel::Warning,
66 "\"SimpleTag\"-element contains multiple \"TagName\"-elements. Surplus TagName elements will be ignored.", context);
67 }
68 break;
69 case MatroskaIds::TagString:
70 case MatroskaIds::TagBinary:
71 if (value().isEmpty()) {
72 unique_ptr<char[]> buffer = make_unique<char[]>(child->dataSize());
73 child->stream().seekg(static_cast<streamoff>(child->dataOffset()));
74 child->stream().read(buffer.get(), static_cast<streamoff>(child->dataSize()));
75 switch (child->id()) {
76 case MatroskaIds::TagString:
77 value().assignData(std::move(buffer), child->dataSize(), TagDataType::Text, TagTextEncoding::Utf8);
78 break;
79 case MatroskaIds::TagBinary:
80 value().assignData(std::move(buffer), child->dataSize(), TagDataType::Undefined);
81 break;
82 }
83 } else {
84 diag.emplace_back(DiagLevel::Warning,
85 "\"SimpleTag\"-element contains multiple \"TagString\"/\"TagBinary\"-elements. Surplus \"TagName\"/\"TagBinary\"-elements will "
86 "be ignored.",
87 context);
88 }
89 break;
90 case MatroskaIds::TagLanguage:
91 if (!tagLanguageFound) {
92 tagLanguageFound = true;
93 auto language = child->readString();
94 if (language != "und") {
95 value().locale().emplace_back(std::move(language), LocaleFormat::ISO_639_2_B);
96 }
97 } else {
98 diag.emplace_back(DiagLevel::Warning,
99 "\"SimpleTag\"-element contains multiple \"TagLanguage\"-elements. Surplus \"TagLanguage\"-elements will be ignored.", context);
100 }
101 break;
102 case MatroskaIds::TagLanguageIETF:
103 if (!tagLanguageIETFFound) {
104 tagLanguageIETFFound = true;
105 value().locale().emplace_back(child->readString(), LocaleFormat::BCP_47);
106 } else {
107 diag.emplace_back(DiagLevel::Warning,
108 "\"SimpleTag\"-element contains multiple \"TagLanguageIETF\"-elements. Surplus \"TagLanguageIETF\"-elements will be ignored.",
109 context);
110 }
111 break;
112 case MatroskaIds::TagDefault:
113 if (!tagDefaultFound) {
114 setDefault(child->readUInteger() > 0);
115 tagDefaultFound = true;
116 } else {
117 diag.emplace_back(DiagLevel::Warning,
118 "\"SimpleTag\"-element contains multiple \"TagDefault\" elements. Surplus \"TagDefault\"-elements will be ignored.", context);
119 }
120 break;
121 case MatroskaIds::SimpleTag:
122 if (parseNestedFields) {
123 nestedFields().emplace_back();
124 nestedFields().back().reparse(*child, diag, true);
125 } else {
126 diag.emplace_back(DiagLevel::Warning,
127 "Nested fields are currently not supported. Nested tags can not be displayed and will be discarded when rewriting the file.",
128 context);
129 }
130 break;
131 case EbmlIds::Crc32:
132 case EbmlIds::Void:
133 break;
134 default:
135 diag.emplace_back(DiagLevel::Warning,
136 argsToString(
137 "\"SimpleTag\"-element contains unknown element ", child->idToString(), " at ", child->startOffset(), ". It will be ignored."),
138 context);
139 }
140
141 // set rating as Popularity to preserve the scale information
142 if (id() == MatroskaTagIds::rating()) {
143 try {
144 value().assignPopularity(Popularity{ .rating = stringToNumber<double>(value().toString()), .scale = TagType::MatroskaTag });
145 } catch (const ConversionException &) {
146 diag.emplace_back(DiagLevel::Warning, argsToString("The rating is not a number."), context);
147 }
148 }
149 }
150}
151
162 MatroskaTagFieldMaker MatroskaTagField::prepareMaking(Diagnostics &diag)
163{
164 static const string context("making Matroska \"SimpleTag\" element.");
165 // check whether ID is empty
166 if (id().empty()) {
167 diag.emplace_back(DiagLevel::Critical, "Can not make \"SimpleTag\" element with empty \"TagName\".", context);
168 throw InvalidDataException();
169 }
170 try {
171 return MatroskaTagFieldMaker(*this, diag);
172 } catch (const ConversionException &) {
173 diag.emplace_back(DiagLevel::Critical, "The assigned tag value can not be converted to be written appropriately.", context);
174 throw InvalidDataException();
175 }
176}
177
184 void MatroskaTagField::make(ostream &stream, Diagnostics &diag)
185{
186 prepareMaking(diag).make(stream);
187}
188
193 void MatroskaTagField::normalizeId(std::string &id)
194{
195 for (auto &c : id) {
196 if (c >= 'a' && c <= 'z') {
197 c -= 'a' - 'A';
198 }
199 }
200}
201
208
213MatroskaTagFieldMaker::MatroskaTagFieldMaker(MatroskaTagField &field, Diagnostics &diag)
214 : m_field(field)
215 , m_language(m_field.value().locale().abbreviatedName(LocaleFormat::ISO_639_2_B, LocaleFormat::Unknown))
216 , m_languageIETF(m_field.value().locale().abbreviatedName(LocaleFormat::BCP_47))
217 , m_isBinary(false)
218{
219 try {
220 if (m_field.value().type() == TagDataType::Popularity) {
221 m_stringValue = m_field.value().toScaledPopularity(TagType::MatroskaTag).toString();
222 } else {
223 m_stringValue = m_field.value().toString(TagTextEncoding::Utf8);
224 }
225 } catch (const ConversionException &) {
226 diag.emplace_back(DiagLevel::Warning,
227 "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 "
228 "official Matroska specifiecation doesn't list any binary fields).",
229 "making Matroska \"SimpleTag\" element.");
230 m_isBinary = true;
231 }
232
233 // compute size of the mandatory "TagLanguage" element (if there's no language set, the 3 byte long value "und" is used)
234 const auto languageSize = m_language.empty() ? 3 : m_language.size();
235 const auto languageElementSize = 2 + EbmlElement::calculateSizeDenotationLength(languageSize) + languageSize;
236 // compute size of the optional "TagLanguageIETF" element
237 const auto languageIETFElementSize
238 = m_languageIETF.empty() ? 0 : (2 + EbmlElement::calculateSizeDenotationLength(m_languageIETF.size()) + m_languageIETF.size());
239
240 // compute "SimpleTag" element size
241 m_simpleTagSize =
242 // "TagName" element
243 +2 + EbmlElement::calculateSizeDenotationLength(m_field.id().size())
244 + m_field.id().size()
245 // language elements
246 + languageElementSize
247 + languageIETFElementSize
248 // "TagDefault" element
249 + 2 + 1
250 + 1
251 // "TagString" element
252 + 2 + EbmlElement::calculateSizeDenotationLength(m_stringValue.size()) + m_stringValue.size();
253
254 // compute size of nested tags
255 for (auto &nestedField : field.nestedFields()) {
256 m_nestedMaker.emplace_back(nestedField.prepareMaking(diag));
257 m_simpleTagSize += m_nestedMaker.back().m_totalSize;
258 }
259 m_totalSize = 2 + EbmlElement::calculateSizeDenotationLength(m_simpleTagSize) + m_simpleTagSize;
260}
261
269 void MatroskaTagFieldMaker::make(ostream &stream) const
270{
271 BinaryWriter writer(&stream);
272 char buff[8];
273 // write "SimpleTag" element
274 writer.writeUInt16BE(MatroskaIds::SimpleTag);
275 std::uint8_t sizeDenotationLen = EbmlElement::makeSizeDenotation(m_simpleTagSize, buff);
276 stream.write(buff, sizeDenotationLen);
277 // write "TagName" element
278 writer.writeUInt16BE(MatroskaIds::TagName);
279 sizeDenotationLen = EbmlElement::makeSizeDenotation(m_field.id().size(), buff);
280 stream.write(buff, sizeDenotationLen);
281 stream.write(m_field.id().c_str(), static_cast<std::streamsize>(m_field.id().size()));
282 // write "TagLanguage" element
283 writer.writeUInt16BE(MatroskaIds::TagLanguage);
284 if (m_language.empty()) {
285 stream.put(static_cast<ostream::char_type>(0x80 | 3));
286 stream.write("und", 3);
287 } else {
288 sizeDenotationLen = EbmlElement::makeSizeDenotation(m_language.size(), buff);
289 stream.write(buff, sizeDenotationLen);
290 stream.write(m_language.data(), static_cast<std::streamsize>(m_language.size()));
291 }
292 // write "TagLanguageIETF" element
293 if (!m_languageIETF.empty()) {
294 writer.writeUInt16BE(MatroskaIds::TagLanguageIETF);
295 sizeDenotationLen = EbmlElement::makeSizeDenotation(m_languageIETF.size(), buff);
296 stream.write(buff, sizeDenotationLen);
297 stream.write(m_languageIETF.data(), static_cast<std::streamsize>(m_languageIETF.size()));
298 }
299 // write "TagDefault" element
300 writer.writeUInt16BE(MatroskaIds::TagDefault);
301 stream.put(static_cast<ostream::char_type>(0x80 | 1));
302 stream.put(m_field.isDefault() ? 1 : 0);
303 // write "TagString"/"TagBinary" element
304 if (m_isBinary) {
305 writer.writeUInt16BE(MatroskaIds::TagBinary);
306 sizeDenotationLen = EbmlElement::makeSizeDenotation(m_field.value().dataSize(), buff);
307 stream.write(buff, sizeDenotationLen);
308 stream.write(m_field.value().dataPointer(), static_cast<std::streamsize>(m_field.value().dataSize()));
309 } else {
310 writer.writeUInt16BE(MatroskaIds::TagString);
311 sizeDenotationLen = EbmlElement::makeSizeDenotation(m_stringValue.size(), buff);
312 stream.write(buff, sizeDenotationLen);
313 stream.write(m_stringValue.data(), static_cast<std::streamsize>(m_stringValue.size()));
314 }
315 // make nested tags
316 for (const auto &maker : m_nestedMaker) {
317 maker.make(stream);
318 }
319}
320
321} // namespace TagParser
TagParser::Diagnostics
The Diagnostics class is a container for DiagMessage.
Definition diagnostics.h:156
TagParser::EbmlElement
The EbmlElement class helps to parse EBML files such as Matroska files.
Definition ebmlelement.h:32
TagParser::EbmlElement::calculateSizeDenotationLength
static std::uint8_t calculateSizeDenotationLength(std::uint64_t size)
Returns the length of the size denotation for the specified size in byte.
Definition ebmlelement.cpp:288
TagParser::EbmlElement::makeSizeDenotation
static std::uint8_t makeSizeDenotation(std::uint64_t size, char *buff)
Makes the size denotation for the specified size and stores it to buff.
Definition ebmlelement.cpp:343
TagParser::Failure
The class inherits from std::exception and serves as base class for exceptions thrown by the elements...
Definition exceptions.h:11
TagParser::GenericFileElement::nextSibling
ImplementationType * nextSibling()
Returns the next sibling of the element.
Definition genericfileelement.h:439
TagParser::GenericFileElement::firstChild
ImplementationType * firstChild()
Returns the first child of the element.
Definition genericfileelement.h:465
TagParser::GenericFileElement::parse
void parse(Diagnostics &diag)
Parses the header information of the element which is read from the related stream at the start offse...
Definition genericfileelement.h:779
TagParser::InvalidDataException
The exception that is thrown when the data to be parsed or to be made seems invalid and therefore can...
Definition exceptions.h:25
TagParser::MatroskaTagFieldMaker
The MatroskaTagFieldMaker class helps making tag fields.
Definition matroskatagfield.h:30
TagParser::MatroskaTagFieldMaker::make
void make(std::ostream &stream) const
Saves the field (specified when constructing the object) to the specified stream (makes a "SimpleTag"...
Definition matroskatagfield.cpp:269
TagParser::MatroskaTagField
The MatroskaTagField class is used by MatroskaTag to store the fields.
Definition matroskatagfield.h:67
TagParser::MatroskaTagField::normalizeId
static void normalizeId(std::string &id)
Ensures the specified id is upper-case as recommended by the Matroska spec.
Definition matroskatagfield.cpp:193
TagParser::MatroskaTagField::reparse
void reparse(EbmlElement &simpleTagElement, Diagnostics &diag, bool parseNestedFields=true)
Parses field information from the specified EbmlElement.
Definition matroskatagfield.cpp:47
TagParser::MatroskaTagField::prepareMaking
MatroskaTagFieldMaker prepareMaking(Diagnostics &diag)
Prepares making.
Definition matroskatagfield.cpp:162
TagParser::MatroskaTagField::make
void make(std::ostream &stream, Diagnostics &diag)
Saves the field to the specified stream (makes a "SimpleTag" element).
Definition matroskatagfield.cpp:184
TagParser::MatroskaTagField::MatroskaTagField
MatroskaTagField()
Constructs a new MatroskaTagField.
Definition matroskatagfield.cpp:25
TagParser::TagField< MatroskaTagField >::TagField
TagField()
Definition generictagfield.h:84
TagParser::TagField< MatroskaTagField >::setDefault
void setDefault(bool isDefault)
Definition generictagfield.h:230
TagParser::TagField< MatroskaTagField >::id
IdentifierType & id()
Definition generictagfield.h:116
TagParser::TagField< MatroskaTagField >::nestedFields
const std::vector< MatroskaTagField > & nestedFields() const
Definition generictagfield.h:262
TagParser::TagField< MatroskaTagField >::setId
void setId(const IdentifierType &id)
Definition generictagfield.h:140
TagParser::TagField< MatroskaTagField >::value
TagValue & value()
Definition generictagfield.h:156
TagParser::TagValue
The TagValue class wraps values of different types.
Definition tagvalue.h:147
TagParser::TagValue::assignData
void assignData(const char *data, std::size_t length, TagDataType type=TagDataType::Binary, TagTextEncoding encoding=TagTextEncoding::Latin1)
TagParser::TagValue::assignPopularity
void assignPopularity(const Popularity &value)
Assigns the specified popularity value.
Definition tagvalue.cpp:1331
TagParser::TagValue::locale
const Locale & locale() const
Returns the locale.
Definition tagvalue.h:616
ebmlelement.h
exceptions.h
matroskacontainer.h
matroskatagfield.h
matroskatagid.h
CppUtilities
Definition abstractcontainer.h:16
TagParser::EbmlIds::Void
@ Void
Definition ebmlid.h:28
TagParser::EbmlIds::Crc32
@ Crc32
Definition ebmlid.h:28
TagParser::MatroskaIds::TagDefault
@ TagDefault
Definition matroskaid.h:230
TagParser::MatroskaIds::TagString
@ TagString
Definition matroskaid.h:227
TagParser::MatroskaIds::TagLanguageIETF
@ TagLanguageIETF
Definition matroskaid.h:229
TagParser::MatroskaIds::TagName
@ TagName
Definition matroskaid.h:226
TagParser::MatroskaIds::TagLanguage
@ TagLanguage
Definition matroskaid.h:228
TagParser::MatroskaIds::TagBinary
@ TagBinary
Definition matroskaid.h:231
TagParser::MatroskaIds::SimpleTag
@ SimpleTag
Definition matroskaid.h:218
TagParser::MatroskaTagIds::rating
constexpr TAG_PARSER_EXPORT std::string_view rating()
Definition matroskatagid.h:335
TagParser
Contains all classes and functions of the TagInfo library.
Definition aaccodebook.h:10
TagParser::LocaleFormat
LocaleFormat
The LocaleFormat enum class specifies the format used by a LocaleDetail.
Definition localehelper.h:14
TagParser::LocaleFormat::BCP_47
@ BCP_47
Definition localehelper.h:20
TagParser::LocaleFormat::ISO_639_2_B
@ ISO_639_2_B
Definition localehelper.h:19
TagParser::StereoMode::Unknown
@ Unknown
Definition abstracttrack.h:77
TagParser::TagTextEncoding::Utf8
@ Utf8
Definition tagvalue.h:31
TagParser::TagType::MatroskaTag
@ MatroskaTag
Definition tagtype.h:16
TagParser::DiagLevel::Warning
@ Warning
Definition diagnostics.h:20
TagParser::DiagLevel::Critical
@ Critical
Definition diagnostics.h:21
TagParser::TagDataType::Text
@ Text
Definition tagvalue.h:120
TagParser::TagDataType::Popularity
@ Popularity
Definition tagvalue.h:129
TagParser::TagDataType::Undefined
@ Undefined
Definition tagvalue.h:128
TagParser::Popularity
The Popularity class contains a value for ID3v2's "Popularimeter" field.
Definition tagvalue.h:72