Tag Parser 12.1.0
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
28
32MatroskaTagField::MatroskaTagField(const string &id, const TagValue &value)
33 : TagField<MatroskaTagField>(id, value)
34{
35}
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
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 * 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: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.
std::size_t dataSize() const
Returns the size of the assigned value in bytes.
Definition tagvalue.h:522
const Locale & locale() const
Returns the locale.
Definition tagvalue.h:616
char * dataPointer()
Returns a pointer to the raw data assigned to the current instance.
Definition tagvalue.h:533
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
double rating
The rating on a tag type specific scale.
Definition tagvalue.h:76