Tag Parser  10.0.0
C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags
matroskatagfield.cpp
Go to the documentation of this file.
1 #include "./matroskatagfield.h"
2 #include "./ebmlelement.h"
3 #include "./matroskacontainer.h"
4 
5 #include "../exceptions.h"
6 
7 #include <c++utilities/io/binarywriter.h>
8 
9 #include <memory>
10 
11 using namespace std;
12 using namespace CppUtilities;
13 
14 namespace TagParser {
15 
24 MatroskaTagField::MatroskaTagField()
25 {
26 }
27 
31 MatroskaTagField::MatroskaTagField(const string &id, const TagValue &value)
32  : TagField<MatroskaTagField>(id, value)
33 {
34 }
35 
46 void 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 
174 void MatroskaTagField::make(ostream &stream, Diagnostics &diag)
175 {
176  prepareMaking(diag).make(stream);
177 }
178 
190 MatroskaTagFieldMaker::MatroskaTagFieldMaker(MatroskaTagField &field, Diagnostics &diag)
191  : m_field(field)
192  , m_language(m_field.value().locale().abbreviatedName(LocaleFormat::ISO_639_2_B, LocaleFormat::Unknown))
193  , m_languageIETF(m_field.value().locale().abbreviatedName(LocaleFormat::BCP_47))
194  , m_isBinary(false)
195 {
196  try {
197  m_stringValue = m_field.value().toString();
198  } catch (const ConversionException &) {
199  diag.emplace_back(DiagLevel::Warning,
200  "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 "
201  "official Matroska specifiecation doesn't list any binary fields).",
202  "making Matroska \"SimpleTag\" element.");
203  m_isBinary = true;
204  }
205 
206  // compute size of the mandatory "TagLanguage" element (if there's no language set, the 3 byte long value "und" is used)
207  const auto languageSize = m_language.empty() ? 3 : m_language.size();
208  const auto languageElementSize = 2 + EbmlElement::calculateSizeDenotationLength(languageSize) + languageSize;
209  // compute size of the optional "TagLanguageIETF" element
210  const auto languageIETFElementSize
211  = m_languageIETF.empty() ? 0 : (2 + EbmlElement::calculateSizeDenotationLength(m_languageIETF.size()) + m_languageIETF.size());
212 
213  // compute "SimpleTag" element size
214  m_simpleTagSize =
215  // "TagName" element
216  +2 + EbmlElement::calculateSizeDenotationLength(m_field.id().size())
217  + m_field.id().size()
218  // language elements
219  + languageElementSize
220  + languageIETFElementSize
221  // "TagDefault" element
222  + 2 + 1
223  + 1
224  // "TagString" element
225  + 2 + EbmlElement::calculateSizeDenotationLength(m_stringValue.size()) + m_stringValue.size();
226 
227  // compute size of nested tags
228  for (auto &nestedField : field.nestedFields()) {
229  m_nestedMaker.emplace_back(nestedField.prepareMaking(diag));
230  m_simpleTagSize += m_nestedMaker.back().m_totalSize;
231  }
232  m_totalSize = 2 + EbmlElement::calculateSizeDenotationLength(m_simpleTagSize) + m_simpleTagSize;
233 }
234 
242 void MatroskaTagFieldMaker::make(ostream &stream) const
243 {
244  BinaryWriter writer(&stream);
245  char buff[8];
246  // write "SimpleTag" element
247  writer.writeUInt16BE(MatroskaIds::SimpleTag);
248  std::uint8_t sizeDenotationLen = EbmlElement::makeSizeDenotation(m_simpleTagSize, buff);
249  stream.write(buff, sizeDenotationLen);
250  // write "TagName" element
251  writer.writeUInt16BE(MatroskaIds::TagName);
252  sizeDenotationLen = EbmlElement::makeSizeDenotation(m_field.id().size(), buff);
253  stream.write(buff, sizeDenotationLen);
254  stream.write(m_field.id().c_str(), static_cast<std::streamsize>(m_field.id().size()));
255  // write "TagLanguage" element
256  writer.writeUInt16BE(MatroskaIds::TagLanguage);
257  if (m_language.empty()) {
258  stream.put(static_cast<ostream::char_type>(0x80 | 3));
259  stream.write("und", 3);
260  } else {
261  sizeDenotationLen = EbmlElement::makeSizeDenotation(m_language.size(), buff);
262  stream.write(buff, sizeDenotationLen);
263  stream.write(m_language.data(), static_cast<std::streamsize>(m_language.size()));
264  }
265  // write "TagLanguageIETF" element
266  if (!m_languageIETF.empty()) {
267  writer.writeUInt16BE(MatroskaIds::TagLanguageIETF);
268  sizeDenotationLen = EbmlElement::makeSizeDenotation(m_languageIETF.size(), buff);
269  stream.write(buff, sizeDenotationLen);
270  stream.write(m_languageIETF.data(), static_cast<std::streamsize>(m_languageIETF.size()));
271  }
272  // write "TagDefault" element
273  writer.writeUInt16BE(MatroskaIds::TagDefault);
274  stream.put(static_cast<ostream::char_type>(0x80 | 1));
275  stream.put(m_field.isDefault() ? 1 : 0);
276  // write "TagString"/"TagBinary" element
277  if (m_isBinary) {
278  writer.writeUInt16BE(MatroskaIds::TagBinary);
279  sizeDenotationLen = EbmlElement::makeSizeDenotation(m_field.value().dataSize(), buff);
280  stream.write(buff, sizeDenotationLen);
281  stream.write(m_field.value().dataPointer(), static_cast<std::streamsize>(m_field.value().dataSize()));
282  } else {
283  writer.writeUInt16BE(MatroskaIds::TagString);
284  sizeDenotationLen = EbmlElement::makeSizeDenotation(m_stringValue.size(), buff);
285  stream.write(buff, sizeDenotationLen);
286  stream.write(m_stringValue.data(), static_cast<std::streamsize>(m_stringValue.size()));
287  }
288  // make nested tags
289  for (const auto &maker : m_nestedMaker) {
290  maker.make(stream);
291  }
292 }
293 
294 } // 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.
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).
The TagField class is used by FieldMapBasedTag to store the fields.
void setDefault(bool isDefault)
Sets whether the field is labeled as default.
const IdentifierType & id() const
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:559
const Locale & locale() const
Returns the locale.
Definition: tagvalue.h:653
char * dataPointer()
Returns a pointer to the raw data assigned to the current instance.
Definition: tagvalue.h:570
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