Tag Parser  9.1.3
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;
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 (value().language().empty() || value().language() == "und") {
91  string lng = child->readString();
92  if (lng != "und") {
93  value().setLanguage(lng);
94  }
95  } else {
96  diag.emplace_back(DiagLevel::Warning,
97  "\"SimpleTag\"-element contains multiple \"TagLanguage\"-elements. Surplus \"TagLanguage\"-elements will be ignored.", context);
98  }
99  break;
101  if (!tagDefaultFound) {
102  setDefault(child->readUInteger() > 0);
103  tagDefaultFound = true;
104  } else {
105  diag.emplace_back(DiagLevel::Warning,
106  "\"SimpleTag\"-element contains multiple \"TagDefault\" elements. Surplus \"TagDefault\"-elements will be ignored.", context);
107  }
108  break;
110  if (parseNestedFields) {
111  nestedFields().emplace_back();
112  nestedFields().back().reparse(*child, diag, true);
113  } else {
114  diag.emplace_back(DiagLevel::Warning,
115  "Nested fields are currently not supported. Nested tags can not be displayed and will be discarded when rewriting the file.",
116  context);
117  }
118  break;
119  case EbmlIds::Crc32:
120  case EbmlIds::Void:
121  break;
122  default:
123  diag.emplace_back(DiagLevel::Warning,
124  argsToString(
125  "\"SimpleTag\"-element contains unknown element ", child->idToString(), " at ", child->startOffset(), ". It will be ignored."),
126  context);
127  }
128  }
129 }
130 
142 {
143  static const string context("making Matroska \"SimpleTag\" element.");
144  // check whether ID is empty
145  if (id().empty()) {
146  diag.emplace_back(DiagLevel::Critical, "Can not make \"SimpleTag\" element with empty \"TagName\".", context);
147  throw InvalidDataException();
148  }
149  try {
150  return MatroskaTagFieldMaker(*this, diag);
151  } catch (const ConversionException &) {
152  diag.emplace_back(DiagLevel::Critical, "The assigned tag value can not be converted to be written appropriately.", context);
153  throw InvalidDataException();
154  }
155 }
156 
163 void MatroskaTagField::make(ostream &stream, Diagnostics &diag)
164 {
165  prepareMaking(diag).make(stream);
166 }
167 
179 MatroskaTagFieldMaker::MatroskaTagFieldMaker(MatroskaTagField &field, Diagnostics &diag)
180  : m_field(field)
181  , m_isBinary(false)
182 {
183  try {
184  m_stringValue = m_field.value().toString();
185  } catch (const ConversionException &) {
186  diag.emplace_back(DiagLevel::Warning,
187  "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 "
188  "official Matroska specifiecation doesn't list any binary fields).",
189  "making Matroska \"SimpleTag\" element.");
190  m_isBinary = true;
191  }
192  size_t languageSize = m_field.value().language().size();
193  if (!languageSize) {
194  languageSize = 3; // if there's no language set, the 3 byte long value "und" is used
195  }
196  m_simpleTagSize =
197  // "TagName" element
198  +2 + EbmlElement::calculateSizeDenotationLength(m_field.id().size())
199  + m_field.id().size()
200  // "TagLanguage" element
202  + languageSize
203  // "TagDefault" element
204  + 2 + 1
205  + 1
206  // "TagString" element
207  + 2 + EbmlElement::calculateSizeDenotationLength(m_stringValue.size()) + m_stringValue.size();
208  // nested tags
209  for (auto &nestedField : field.nestedFields()) {
210  m_nestedMaker.emplace_back(nestedField.prepareMaking(diag));
211  m_simpleTagSize += m_nestedMaker.back().m_totalSize;
212  }
213  m_totalSize = 2 + EbmlElement::calculateSizeDenotationLength(m_simpleTagSize) + m_simpleTagSize;
214 }
215 
223 void MatroskaTagFieldMaker::make(ostream &stream) const
224 {
225  BinaryWriter writer(&stream);
226  char buff[8];
227  // write header of "SimpleTag" element
228  writer.writeUInt16BE(MatroskaIds::SimpleTag);
229  std::uint8_t sizeDenotationLen = EbmlElement::makeSizeDenotation(m_simpleTagSize, buff);
230  stream.write(buff, sizeDenotationLen);
231  // write header of "TagName" element
232  writer.writeUInt16BE(MatroskaIds::TagName);
233  sizeDenotationLen = EbmlElement::makeSizeDenotation(m_field.id().size(), buff);
234  stream.write(buff, sizeDenotationLen);
235  stream.write(m_field.id().c_str(), m_field.id().size());
236  // write header of "TagLanguage" element
237  writer.writeUInt16BE(MatroskaIds::TagLanguage);
238  if (m_field.value().language().empty()) {
239  stream.put(static_cast<ostream::char_type>(0x80 | 3));
240  stream.write("und", 3);
241  } else {
242  sizeDenotationLen = EbmlElement::makeSizeDenotation(m_field.value().language().size(), buff);
243  stream.write(buff, sizeDenotationLen);
244  stream.write(m_field.value().language().c_str(), m_field.value().language().size());
245  }
246  // write header of "TagDefault" element
247  writer.writeUInt16BE(MatroskaIds::TagDefault);
248  stream.put(static_cast<ostream::char_type>(0x80 | 1));
249  stream.put(m_field.isDefault() ? 1 : 0);
250  // write header of "TagString"/"TagBinary" element
251  if (m_isBinary) {
252  writer.writeUInt16BE(MatroskaIds::TagBinary);
253  sizeDenotationLen = EbmlElement::makeSizeDenotation(m_field.value().dataSize(), buff);
254  stream.write(buff, sizeDenotationLen);
255  stream.write(m_field.value().dataPointer(), m_field.value().dataSize());
256  } else {
257  writer.writeUInt16BE(MatroskaIds::TagString);
258  sizeDenotationLen = EbmlElement::makeSizeDenotation(m_stringValue.size(), buff);
259  stream.write(buff, sizeDenotationLen);
260  stream.write(m_stringValue.data(), m_stringValue.size());
261  }
262  // make nested tags
263  for (const auto &maker : m_nestedMaker) {
264  maker.make(stream);
265  }
266 }
267 
268 } // namespace TagParser
TagParser::MatroskaIds::SimpleTag
@ SimpleTag
Definition: matroskaid.h:211
TagParser::MatroskaTagField::prepareMaking
MatroskaTagFieldMaker prepareMaking(Diagnostics &diag)
Prepares making.
Definition: matroskatagfield.cpp:141
TagParser::TagTextEncoding::Utf8
@ Utf8
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:223
TagParser::MatroskaIds::TagDefault
@ TagDefault
Definition: matroskaid.h:222
TagParser::DiagLevel::Warning
@ Warning
TagParser::TagField< MatroskaTagField >::nestedFields
const std::vector< MatroskaTagField > & nestedFields() const
Returns the nested fields.
Definition: generictagfield.h:250
TagParser::MatroskaTagFieldMaker
The MatroskaTagFieldMaker class helps making tag fields. It allows to calculate the required size.
Definition: matroskatagfield.h:30
TagParser::Diagnostics
The Diagnostics class is a container for DiagMessage.
Definition: diagnostics.h:156
TagParser::TagValue::dataPointer
char * dataPointer()
Returns a pointer to the raw data assigned to the current instance.
Definition: tagvalue.h:492
TagParser
Contains all classes and functions of the TagInfo library.
Definition: aaccodebook.h:10
TagParser::TagField< MatroskaTagField >::value
TagValue & value()
Returns the value of the current TagField.
Definition: generictagfield.h:144
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::MatroskaTagIds::language
constexpr const TAG_PARSER_EXPORT char * language()
Definition: matroskatagid.h:346
TagParser::MatroskaIds::TagString
@ TagString
Definition: matroskaid.h:220
TagParser::TagField
The TagField class is used by FieldMapBasedTag to store the fields.
Definition: generictagfield.h:8
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::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::TagName
@ TagName
Definition: matroskaid.h:219
TagParser::TagDataType::Undefined
@ Undefined
TagParser::DiagLevel::Critical
@ Critical
TagParser::TagDataType::Text
@ Text
CppUtilities
Definition: abstractcontainer.h:15
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:481
TagParser::GenericFileElement::firstChild
ImplementationType * firstChild()
Returns the first child of the element.
Definition: genericfileelement.h:460
TagParser::MatroskaIds::TagBinary
@ TagBinary
Definition: matroskaid.h:223
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. It is meant to be assigned to a tag field.
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:163
ebmlelement.h
TagParser::MatroskaIds::TagLanguage
@ TagLanguage
Definition: matroskaid.h:221
TagParser::EbmlIds::Crc32
@ Crc32
Definition: ebmlid.h:28
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:567
TagParser::TagValue::language
const std::string & language() const
Returns the language.
Definition: tagvalue.h:557
TagParser::MatroskaTagField::reparse
void reparse(EbmlElement &simpleTagElement, Diagnostics &diag, bool parseNestedFields=true)
Parses field information from the specified EbmlElement.
Definition: matroskatagfield.cpp:46