Tag Parser  7.1.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 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(static_cast<streamoff>(child->dataOffset()));
74  child->stream().read(buffer.get(), static_cast<streamoff>(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.
STL namespace.
void make(std::ostream &stream) const
Saves the field (specified when constructing the object) to the specified stream (makes a "SimpleTag"...
The EbmlElement class helps to parse EBML files such as Matroska files.
Definition: ebmlelement.h:31
The MatroskaTagField class is used by MatroskaTag to store the fields.
void setDefault(bool isDefault)
Sets whether the field is labeled as default.
std::size_t dataSize() const
Returns the size of the assigned value in bytes.
Definition: tagvalue.h:416
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:488
static byte makeSizeDenotation(uint64 size, char *buff)
Makes the size denotation for the specified size and stores it to buff.
The MatroskaTagFieldMaker class helps making tag fields.
The TagField class is used by FieldMapBasedTag to store the fields.
void setLanguage(const std::string &language)
Sets the language.
Definition: tagvalue.h:499
char * dataPointer()
Returns a pointer to the raw data assigned to the current instance.
Definition: tagvalue.h:427
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).
The exception that is thrown when the data to be parsed or to be made seems invalid and therefore can...
Definition: exceptions.h:25
void assignData(const char *data, std::size_t length, TagDataType type=TagDataType::Binary, TagTextEncoding encoding=TagTextEncoding::Latin1)
bool isDefault() const
Returns an indication whether the field is labeled as default.
The TagValue class wraps values of different types.
Definition: tagvalue.h:64
The class inherits from std::exception and serves as base class for exceptions thrown by the elements...
Definition: exceptions.h:11
Contains all classes and functions of the TagInfo library.
Definition: aaccodebook.h:9
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.
The Diagnostics class is a container for DiagMessage.
Definition: diagnostics.h:154
TagValue & value()
Returns the value of the current TagField.