Tag Parser 12.5.3
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.
2#include "./ebmlelement.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
28
36
47void 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()) {
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;
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()) {
77 value().assignData(std::move(buffer), child->dataSize(), TagDataType::Text, TagTextEncoding::Utf8);
78 break;
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;
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;
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;
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;
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
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
184void MatroskaTagField::make(ostream &stream, Diagnostics &diag)
185{
186 prepareMaking(diag).make(stream);
187}
188
193void 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
269void 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
The Diagnostics class is a container for DiagMessage.
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...
InvalidDataException() noexcept
Constructs a new exception.
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.
const std::vector< MatroskaTagField > & nestedFields() const
void setId(const IdentifierType &id)
The TagValue class wraps values of different types.
Definition tagvalue.h:147
void assignData(const char *data, std::size_t length, TagDataType type=TagDataType::Binary, TagTextEncoding encoding=TagTextEncoding::Latin1)
void assignPopularity(const Popularity &value)
Assigns the specified popularity value.
const Locale & locale() const
Returns the locale.
Definition tagvalue.h:616
constexpr TAG_PARSER_EXPORT std::string_view rating()
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.
The Popularity class contains a value for ID3v2's "Popularimeter" field.
Definition tagvalue.h:72