Tag Parser  7.0.1
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 IoUtilities;
13 using namespace ConversionUtilities;
14 
15 namespace TagParser {
16 
25 MatroskaTagField::MatroskaTagField()
26 {
27 }
28 
32 MatroskaTagField::MatroskaTagField(const string &id, const TagValue &value)
33  : TagField<MatroskaTagField>(id, value)
34 {
35 }
36 
47 void MatroskaTagField::reparse(EbmlElement &simpleTagElement, Diagnostics &diag, bool parseNestedFields)
48 {
49  string context("parsing Matroska tag field");
50  simpleTagElement.parse(diag);
51  bool tagDefaultFound = 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(child->dataOffset());
74  child->stream().read(buffer.get(), child->dataSize());
75  switch (child->id()) {
77  value().assignData(move(buffer), child->dataSize(), TagDataType::Text, TagTextEncoding::Utf8);
78  break;
80  value().assignData(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 (value().language().empty() || value().language() == "und") {
92  string lng = child->readString();
93  if (lng != "und") {
94  value().setLanguage(lng);
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 (!tagDefaultFound) {
103  setDefault(child->readUInteger() > 0);
104  tagDefaultFound = true;
105  } else {
106  diag.emplace_back(DiagLevel::Warning,
107  "\"SimpleTag\"-element contains multiple \"TagDefault\" elements. Surplus \"TagDefault\"-elements will be ignored.", context);
108  }
109  break;
111  if (parseNestedFields) {
112  nestedFields().emplace_back();
113  nestedFields().back().reparse(*child, diag, true);
114  } else {
115  diag.emplace_back(DiagLevel::Warning,
116  "Nested fields are currently not supported. Nested tags can not be displayed and will be discarded when rewriting the file.",
117  context);
118  }
119  break;
120  case EbmlIds::Crc32:
121  case EbmlIds::Void:
122  break;
123  default:
124  diag.emplace_back(DiagLevel::Warning,
125  argsToString(
126  "\"SimpleTag\"-element contains unknown element ", child->idToString(), " at ", child->startOffset(), ". It will be ignored."),
127  context);
128  }
129  }
130 }
131 
143 {
144  static const string context("making Matroska \"SimpleTag\" element.");
145  // check whether ID is empty
146  if (id().empty()) {
147  diag.emplace_back(DiagLevel::Critical, "Can not make \"SimpleTag\" element with empty \"TagName\".", context);
148  throw InvalidDataException();
149  }
150  try {
151  return MatroskaTagFieldMaker(*this, diag);
152  } catch (const ConversionException &) {
153  diag.emplace_back(DiagLevel::Critical, "The assigned tag value can not be converted to be written appropriately.", context);
154  throw InvalidDataException();
155  }
156 }
157 
164 void MatroskaTagField::make(ostream &stream, Diagnostics &diag)
165 {
166  prepareMaking(diag).make(stream);
167 }
168 
180 MatroskaTagFieldMaker::MatroskaTagFieldMaker(MatroskaTagField &field, Diagnostics &diag)
181  : m_field(field)
182  , m_isBinary(false)
183 {
184  try {
185  m_stringValue = m_field.value().toString();
186  } catch (const ConversionException &) {
187  diag.emplace_back(DiagLevel::Warning,
188  "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 "
189  "official Matroska specifiecation doesn't list any binary fields).",
190  "making Matroska \"SimpleTag\" element.");
191  m_isBinary = true;
192  }
193  size_t languageSize = m_field.value().language().size();
194  if (!languageSize) {
195  languageSize = 3; // if there's no language set, the 3 byte long value "und" is used
196  }
197  m_simpleTagSize =
198  // "TagName" element
199  +2 + EbmlElement::calculateSizeDenotationLength(m_field.id().size())
200  + m_field.id().size()
201  // "TagLanguage" element
203  + languageSize
204  // "TagDefault" element
205  + 2 + 1
206  + 1
207  // "TagString" element
208  + 2 + EbmlElement::calculateSizeDenotationLength(m_stringValue.size()) + m_stringValue.size();
209  // nested tags
210  for (auto &nestedField : field.nestedFields()) {
211  m_nestedMaker.emplace_back(nestedField.prepareMaking(diag));
212  m_simpleTagSize += m_nestedMaker.back().m_totalSize;
213  }
214  m_totalSize = 2 + EbmlElement::calculateSizeDenotationLength(m_simpleTagSize) + m_simpleTagSize;
215 }
216 
224 void MatroskaTagFieldMaker::make(ostream &stream) const
225 {
226  BinaryWriter writer(&stream);
227  char buff[8];
228  // write header of "SimpleTag" element
229  writer.writeUInt16BE(MatroskaIds::SimpleTag);
230  byte sizeDenotationLen = EbmlElement::makeSizeDenotation(m_simpleTagSize, buff);
231  stream.write(buff, sizeDenotationLen);
232  // write header of "TagName" element
233  writer.writeUInt16BE(MatroskaIds::TagName);
234  sizeDenotationLen = EbmlElement::makeSizeDenotation(m_field.id().size(), buff);
235  stream.write(buff, sizeDenotationLen);
236  stream.write(m_field.id().c_str(), m_field.id().size());
237  // write header of "TagLanguage" element
238  writer.writeUInt16BE(MatroskaIds::TagLanguage);
239  if (m_field.value().language().empty()) {
240  stream.put(static_cast<ostream::char_type>(0x80 | 3));
241  stream.write("und", 3);
242  } else {
243  sizeDenotationLen = EbmlElement::makeSizeDenotation(m_field.value().language().size(), buff);
244  stream.write(buff, sizeDenotationLen);
245  stream.write(m_field.value().language().c_str(), m_field.value().language().size());
246  }
247  // write header of "TagDefault" element
248  writer.writeUInt16BE(MatroskaIds::TagDefault);
249  stream.put(static_cast<ostream::char_type>(0x80 | 1));
250  stream.put(m_field.isDefault() ? 1 : 0);
251  // write header of "TagString"/"TagBinary" element
252  if (m_isBinary) {
253  writer.writeUInt16BE(MatroskaIds::TagBinary);
254  sizeDenotationLen = EbmlElement::makeSizeDenotation(m_field.value().dataSize(), buff);
255  stream.write(buff, sizeDenotationLen);
256  stream.write(m_field.value().dataPointer(), m_field.value().dataSize());
257  } else {
258  writer.writeUInt16BE(MatroskaIds::TagString);
259  sizeDenotationLen = EbmlElement::makeSizeDenotation(m_stringValue.size(), buff);
260  stream.write(buff, sizeDenotationLen);
261  stream.write(m_stringValue.data(), m_stringValue.size());
262  }
263  // make nested tags
264  for (const auto &maker : m_nestedMaker) {
265  maker.make(stream);
266  }
267 }
268 
269 } // namespace TagParser
TAG_PARSER_EXPORT const char * language()
MatroskaTagFieldMaker prepareMaking(Diagnostics &diag)
Prepares making.
const std::vector< MatroskaTagField > & nestedFields() const
Returns the nested fields.
ImplementationType * firstChild()
Returns the first child of the element.
static byte calculateSizeDenotationLength(uint64 size)
Returns the length of the size denotation for the specified size in byte.
std::size_t dataSize() const
Returns the size of the assigned value in bytes.
Definition: tagvalue.h:399
STL namespace.
void make(std::ostream &stream) const
Saves the field (specified when constructing the object) to the specified stream (makes a "SimpleTag"...
void setDefault(bool isDefault)
Sets whether the field is labeled as default.
Contains utility classes helping to read and write streams.
void parse(Diagnostics &diag)
Parses the header information of the element which is read from the related stream at the start offse...
const std::string & language() const
Returns the language.
Definition: tagvalue.h:471
static byte makeSizeDenotation(uint64 size, char *buff)
Makes the size denotation for the specified size and stores it to buff.
The TagField class is used by FieldMapBasedTag to store the fields.
void setLanguage(const std::string &language)
Sets the language.
Definition: tagvalue.h:482
char * dataPointer()
Returns a pointer to the raw data assigned to the current instance.
Definition: tagvalue.h:410
const IdentifierType & id() const
Returns the id of the current TagField.
void make(std::ostream &stream, Diagnostics &diag)
Saves the field to the specified stream (makes a "SimpleTag" element).
bool isDefault() const
Returns an indication whether the field is labeled as default.
void assignData(const char *data, size_t length, TagDataType type=TagDataType::Binary, TagTextEncoding encoding=TagTextEncoding::Latin1)
Assigns a copy of the given data.
Definition: tagvalue.cpp:620
void reparse(EbmlElement &simpleTagElement, Diagnostics &diag, bool parseNestedFields=true)
Parses field information from the specified EbmlElement.
void setId(const IdentifierType &id)
Sets the id of the current Tag Field.
TagValue & value()
Returns the value of the current TagField.