Tag Parser  9.4.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 && !tagLanguageIETFFound) {
91  tagLanguageFound = true;
92  string lng = child->readString();
93  if (lng != "und") {
94  value().setLanguage(lng);
95  }
96  } else if (tagLanguageFound) {
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  diag.emplace_back(DiagLevel::Warning,
105  "\"SimpleTag\"-element contains a \"TagLanguageIETF\"-element. That's not supported at this point. The element will be dropped "
106  "when applying changes.",
107  context);
108  } else {
109  diag.emplace_back(DiagLevel::Warning,
110  "\"SimpleTag\"-element contains multiple \"TagLanguageIETF\"-elements. Surplus \"TagLanguageIETF\"-elements will be ignored.",
111  context);
112  }
113  break;
115  if (!tagDefaultFound) {
116  setDefault(child->readUInteger() > 0);
117  tagDefaultFound = true;
118  } else {
119  diag.emplace_back(DiagLevel::Warning,
120  "\"SimpleTag\"-element contains multiple \"TagDefault\" elements. Surplus \"TagDefault\"-elements will be ignored.", context);
121  }
122  break;
124  if (parseNestedFields) {
125  nestedFields().emplace_back();
126  nestedFields().back().reparse(*child, diag, true);
127  } else {
128  diag.emplace_back(DiagLevel::Warning,
129  "Nested fields are currently not supported. Nested tags can not be displayed and will be discarded when rewriting the file.",
130  context);
131  }
132  break;
133  case EbmlIds::Crc32:
134  case EbmlIds::Void:
135  break;
136  default:
137  diag.emplace_back(DiagLevel::Warning,
138  argsToString(
139  "\"SimpleTag\"-element contains unknown element ", child->idToString(), " at ", child->startOffset(), ". It will be ignored."),
140  context);
141  }
142  }
143 }
144 
156 {
157  static const string context("making Matroska \"SimpleTag\" element.");
158  // check whether ID is empty
159  if (id().empty()) {
160  diag.emplace_back(DiagLevel::Critical, "Can not make \"SimpleTag\" element with empty \"TagName\".", context);
161  throw InvalidDataException();
162  }
163  try {
164  return MatroskaTagFieldMaker(*this, diag);
165  } catch (const ConversionException &) {
166  diag.emplace_back(DiagLevel::Critical, "The assigned tag value can not be converted to be written appropriately.", context);
167  throw InvalidDataException();
168  }
169 }
170 
177 void MatroskaTagField::make(ostream &stream, Diagnostics &diag)
178 {
179  prepareMaking(diag).make(stream);
180 }
181 
193 MatroskaTagFieldMaker::MatroskaTagFieldMaker(MatroskaTagField &field, Diagnostics &diag)
194  : m_field(field)
195  , m_isBinary(false)
196 {
197  try {
198  m_stringValue = m_field.value().toString();
199  } catch (const ConversionException &) {
200  diag.emplace_back(DiagLevel::Warning,
201  "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 "
202  "official Matroska specifiecation doesn't list any binary fields).",
203  "making Matroska \"SimpleTag\" element.");
204  m_isBinary = true;
205  }
206  size_t languageSize = m_field.value().language().size();
207  if (!languageSize) {
208  languageSize = 3; // if there's no language set, the 3 byte long value "und" is used
209  }
210  m_simpleTagSize =
211  // "TagName" element
212  +2 + EbmlElement::calculateSizeDenotationLength(m_field.id().size())
213  + m_field.id().size()
214  // "TagLanguage" element
216  + languageSize
217  // "TagDefault" element
218  + 2 + 1
219  + 1
220  // "TagString" element
221  + 2 + EbmlElement::calculateSizeDenotationLength(m_stringValue.size()) + m_stringValue.size();
222  // nested tags
223  for (auto &nestedField : field.nestedFields()) {
224  m_nestedMaker.emplace_back(nestedField.prepareMaking(diag));
225  m_simpleTagSize += m_nestedMaker.back().m_totalSize;
226  }
227  m_totalSize = 2 + EbmlElement::calculateSizeDenotationLength(m_simpleTagSize) + m_simpleTagSize;
228 }
229 
237 void MatroskaTagFieldMaker::make(ostream &stream) const
238 {
239  BinaryWriter writer(&stream);
240  char buff[8];
241  // write header of "SimpleTag" element
242  writer.writeUInt16BE(MatroskaIds::SimpleTag);
243  std::uint8_t sizeDenotationLen = EbmlElement::makeSizeDenotation(m_simpleTagSize, buff);
244  stream.write(buff, sizeDenotationLen);
245  // write header of "TagName" element
246  writer.writeUInt16BE(MatroskaIds::TagName);
247  sizeDenotationLen = EbmlElement::makeSizeDenotation(m_field.id().size(), buff);
248  stream.write(buff, sizeDenotationLen);
249  stream.write(m_field.id().c_str(), m_field.id().size());
250  // write header of "TagLanguage" element
251  writer.writeUInt16BE(MatroskaIds::TagLanguage);
252  if (m_field.value().language().empty()) {
253  stream.put(static_cast<ostream::char_type>(0x80 | 3));
254  stream.write("und", 3);
255  } else {
256  sizeDenotationLen = EbmlElement::makeSizeDenotation(m_field.value().language().size(), buff);
257  stream.write(buff, sizeDenotationLen);
258  stream.write(m_field.value().language().c_str(), m_field.value().language().size());
259  }
260  // write header of "TagDefault" element
261  writer.writeUInt16BE(MatroskaIds::TagDefault);
262  stream.put(static_cast<ostream::char_type>(0x80 | 1));
263  stream.put(m_field.isDefault() ? 1 : 0);
264  // write header of "TagString"/"TagBinary" element
265  if (m_isBinary) {
266  writer.writeUInt16BE(MatroskaIds::TagBinary);
267  sizeDenotationLen = EbmlElement::makeSizeDenotation(m_field.value().dataSize(), buff);
268  stream.write(buff, sizeDenotationLen);
269  stream.write(m_field.value().dataPointer(), m_field.value().dataSize());
270  } else {
271  writer.writeUInt16BE(MatroskaIds::TagString);
272  sizeDenotationLen = EbmlElement::makeSizeDenotation(m_stringValue.size(), buff);
273  stream.write(buff, sizeDenotationLen);
274  stream.write(m_stringValue.data(), m_stringValue.size());
275  }
276  // make nested tags
277  for (const auto &maker : m_nestedMaker) {
278  maker.make(stream);
279  }
280 }
281 
282 } // namespace TagParser
TagParser::MatroskaIds::SimpleTag
@ SimpleTag
Definition: matroskaid.h:212
TagParser::MatroskaTagField::prepareMaking
MatroskaTagFieldMaker prepareMaking(Diagnostics &diag)
Prepares making.
Definition: matroskatagfield.cpp:155
TagParser::RawDataType::Utf8
@ Utf8
Definition: mp4tagfield.h:21
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::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:237
TagParser::MatroskaIds::TagDefault
@ TagDefault
Definition: matroskaid.h:224
TagParser::MatroskaTagFieldMaker
The MatroskaTagFieldMaker class helps making tag fields.
Definition: matroskatagfield.h:30
TagParser::Diagnostics
The Diagnostics class is a container for DiagMessage.
Definition: diagnostics.h:156
TagParser
Contains all classes and functions of the TagInfo library.
Definition: aaccodebook.h:10
matroskacontainer.h
TagParser::TagField< MatroskaTagField >::id
const IdentifierType & id() const
Returns the id of the current TagField.
Definition: generictagfield.h:115
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:771
TagParser::GenericFileElement::firstChild
ImplementationType * firstChild()
Returns the first child of the element.
Definition: genericfileelement.h:460
TagParser::MatroskaIds::TagString
@ TagString
Definition: matroskaid.h:221
TagParser::TagField
The TagField class is used by FieldMapBasedTag to store the fields.
Definition: generictagfield.h:30
TagParser::Failure
The class inherits from std::exception and serves as base class for exceptions thrown by the elements...
Definition: exceptions.h:11
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::TagField< MatroskaTagField >::value
TagValue & value()
Returns the value of the current TagField.
Definition: generictagfield.h:144
TagParser::EbmlElement
The EbmlElement class helps to parse EBML files such as Matroska files.
Definition: ebmlelement.h:31
TagParser::MatroskaTagField
The MatroskaTagField class is used by MatroskaTag to store the fields.
Definition: matroskatagfield.h:65
TagParser::MatroskaIds::TagLanguageIETF
@ TagLanguageIETF
Definition: matroskaid.h:223
TagParser::MatroskaIds::TagName
@ TagName
Definition: matroskaid.h:220
TagParser::TagDataType::Text
@ Text
CppUtilities
Definition: abstractcontainer.h:15
TagParser::TagValue::dataPointer
char * dataPointer()
Returns a pointer to the raw data assigned to the current instance.
Definition: tagvalue.h:507
TagParser::EbmlIds::Void
@ Void
Definition: ebmlid.h:28
TagParser::TagValue::dataSize
std::size_t dataSize() const
Returns the size of the assigned value in bytes.
Definition: tagvalue.h:496
TagParser::MatroskaIds::TagBinary
@ TagBinary
Definition: matroskaid.h:225
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
matroskatagfield.h
TagParser::TagValue
The TagValue class wraps values of different types.
Definition: tagvalue.h:75
TagParser::MatroskaTagField::make
void make(std::ostream &stream, Diagnostics &diag)
Saves the field to the specified stream (makes a "SimpleTag" element).
Definition: matroskatagfield.cpp:177
ebmlelement.h
TagParser::MatroskaIds::TagLanguage
@ TagLanguage
Definition: matroskaid.h:222
TagParser::EbmlIds::Crc32
@ Crc32
Definition: ebmlid.h:28
TagParser::TagField< MatroskaTagField >::nestedFields
const std::vector< MatroskaTagField > & nestedFields() const
Returns the nested fields.
Definition: generictagfield.h:250
TagParser::TagField< MatroskaTagField >::setId
void setId(const IdentifierType &id)
Sets the id of the current Tag Field.
Definition: generictagfield.h:128
TagParser::TagField< MatroskaTagField >::setDefault
void setDefault(bool isDefault)
Sets whether the field is labeled as default.
Definition: generictagfield.h:218
TagParser::TagValue::assignData
void assignData(const char *data, std::size_t length, TagDataType type=TagDataType::Binary, TagTextEncoding encoding=TagTextEncoding::Latin1)
TagParser::TagField::isDefault
bool isDefault() const
Returns an indication whether the field is labeled as default.
Definition: generictagfield.h:210
TagParser::TagValue::setLanguage
void setLanguage(const std::string &language)
Sets the language.
Definition: tagvalue.h:582
TagParser::TagValue::language
const std::string & language() const
Returns the language.
Definition: tagvalue.h:572
TagParser::MatroskaTagField::reparse
void reparse(EbmlElement &simpleTagElement, Diagnostics &diag, bool parseNestedFields=true)
Parses field information from the specified EbmlElement.
Definition: matroskatagfield.cpp:46