Tag Parser  6.5.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 Media {
16 
25 MatroskaTagField::MatroskaTagField()
26 {}
27 
31 MatroskaTagField::MatroskaTagField(const string &id, const TagValue &value) :
32  TagField<MatroskaTagField>(id, value)
33 {}
34 
45 void MatroskaTagField::reparse(EbmlElement &simpleTagElement, bool parseNestedFields)
46 {
47  string context("parsing Matroska tag field");
48  clear();
49  simpleTagElement.parse();
50  bool tagDefaultFound = false;
51  for(EbmlElement *child = simpleTagElement.firstChild(); child; child = child->nextSibling()) {
52  try {
53  child->parse();
54  } catch (const Failure &) {
55  addNotification(NotificationType::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  addNotification(NotificationType::Warning, "\"SimpleTag\"-element contains multiple \"TagName\"-elements. Surplus TagName elements will be ignored.", context);
65  }
66  break;
69  if(value().isEmpty()) {
70  unique_ptr<char[]> buffer = make_unique<char []>(child->dataSize());
71  child->stream().seekg(child->dataOffset());
72  child->stream().read(buffer.get(), child->dataSize());
73  switch(child->id()) {
75  value().assignData(move(buffer), child->dataSize(), TagDataType::Text, TagTextEncoding::Utf8);
76  break;
78  value().assignData(move(buffer), child->dataSize(), TagDataType::Undefined);
79  break;
80  }
81  } else {
82  addNotification(NotificationType::Warning, "\"SimpleTag\"-element contains multiple \"TagString\"/\"TagBinary\"-elements. Surplus \"TagName\"/\"TagBinary\"-elements will be ignored.", context);
83  }
84  break;
86  if(value().language().empty() || value().language() == "und") {
87  string lng = child->readString();
88  if(lng != "und") {
89  value().setLanguage(lng);
90  }
91  } else {
92  addNotification(NotificationType::Warning, "\"SimpleTag\"-element contains multiple \"TagLanguage\"-elements. Surplus \"TagLanguage\"-elements will be ignored.", context);
93  }
94  break;
96  if(!tagDefaultFound) {
97  setDefault(child->readUInteger() > 0);
98  tagDefaultFound = true;
99  } else {
100  addNotification(NotificationType::Warning, "\"SimpleTag\"-element contains multiple \"TagDefault\" elements. Surplus \"TagDefault\"-elements will be ignored.", context);
101  }
102  break;
104  if(parseNestedFields) {
105  nestedFields().emplace_back();
106  nestedFields().back().reparse(*child, true);
107  } else {
108  addNotification(NotificationType::Warning, "Nested fields are currently not supported. Nested tags can not be displayed and will be discarded when rewriting the file.", context);
109  }
110  break;
111  case EbmlIds::Crc32:
112  case EbmlIds::Void:
113  break;
114  default:
115  addNotification(NotificationType::Warning, argsToString("\"SimpleTag\"-element contains unknown element ", child->idToString(), " at ", child->startOffset(), ". It will be ignored."), context);
116  }
117  }
118 }
119 
131 {
132  static const string context("making Matroska \"SimpleTag\" element.");
133  // check whether ID is empty
134  if(id().empty()) {
135  addNotification(NotificationType::Critical, "Can not make \"SimpleTag\" element with empty \"TagName\".", context);
136  throw InvalidDataException();
137  }
138  try {
139  return MatroskaTagFieldMaker(*this);
140  } catch(const ConversionException &) {
141  addNotification(NotificationType::Critical, "The assigned tag value can not be converted to be written appropriately.", context);
142  throw InvalidDataException();
143  }
144 }
145 
152 void MatroskaTagField::make(ostream &stream)
153 {
154  prepareMaking().make(stream);
155 }
156 
168 MatroskaTagFieldMaker::MatroskaTagFieldMaker(MatroskaTagField &field) :
169  m_field(field),
170  m_isBinary(false)
171 {
172  try {
173  m_stringValue = m_field.value().toString();
174  } catch(const ConversionException &) {
175  m_field.addNotification(NotificationType::Warning, "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 official Matroska specifiecation doesn't list any binary fields).", "making Matroska \"SimpleTag\" element.");
176  m_isBinary = true;
177  }
178  size_t languageSize = m_field.value().language().size();
179  if(!languageSize) {
180  languageSize = 3; // if there's no language set, the 3 byte long value "und" is used
181  }
182  m_simpleTagSize =
183  // "TagName" element
184  + 2 + EbmlElement::calculateSizeDenotationLength(m_field.id().size()) + m_field.id().size()
185  // "TagLanguage" element
186  + 2 + EbmlElement::calculateSizeDenotationLength(languageSize) + languageSize
187  // "TagDefault" element
188  + 2 + 1 + 1
189  // "TagString" element
190  + 2 + EbmlElement::calculateSizeDenotationLength(m_stringValue.size()) + m_stringValue.size();
191  // nested tags
192  for(auto &nestedField : field.nestedFields()) {
193  m_nestedMaker.emplace_back(nestedField.prepareMaking());
194  m_simpleTagSize += m_nestedMaker.back().m_totalSize;
195  }
196  m_totalSize = 2 + EbmlElement::calculateSizeDenotationLength(m_simpleTagSize) + m_simpleTagSize;
197 }
198 
206 void MatroskaTagFieldMaker::make(ostream &stream) const
207 {
208  BinaryWriter writer(&stream);
209  char buff[8];
210  // write header of "SimpleTag" element
211  writer.writeUInt16BE(MatroskaIds::SimpleTag);
212  byte sizeDenotationLen = EbmlElement::makeSizeDenotation(m_simpleTagSize, buff);
213  stream.write(buff, sizeDenotationLen);
214  // write header of "TagName" element
215  writer.writeUInt16BE(MatroskaIds::TagName);
216  sizeDenotationLen = EbmlElement::makeSizeDenotation(m_field.id().size(), buff);
217  stream.write(buff, sizeDenotationLen);
218  stream.write(m_field.id().c_str(), m_field.id().size());
219  // write header of "TagLanguage" element
220  writer.writeUInt16BE(MatroskaIds::TagLanguage);
221  if(m_field.value().language().empty()) {
222  stream.put(static_cast<ostream::char_type>(0x80 | 3));
223  stream.write("und", 3);
224  } else {
225  sizeDenotationLen = EbmlElement::makeSizeDenotation(m_field.value().language().size(), buff);
226  stream.write(buff, sizeDenotationLen);
227  stream.write(m_field.value().language().c_str(), m_field.value().language().size());
228  }
229  // write header of "TagDefault" element
230  writer.writeUInt16BE(MatroskaIds::TagDefault);
231  stream.put(static_cast<ostream::char_type>(0x80 | 1));
232  stream.put(m_field.isDefault() ? 1 : 0);
233  // write header of "TagString"/"TagBinary" element
234  if(m_isBinary) {
235  writer.writeUInt16BE(MatroskaIds::TagBinary);
236  sizeDenotationLen = EbmlElement::makeSizeDenotation(m_field.value().dataSize(), buff);
237  stream.write(buff, sizeDenotationLen);
238  stream.write(m_field.value().dataPointer(), m_field.value().dataSize());
239  } else {
240  writer.writeUInt16BE(MatroskaIds::TagString);
241  sizeDenotationLen = EbmlElement::makeSizeDenotation(m_stringValue.size(), buff);
242  stream.write(buff, sizeDenotationLen);
243  stream.write(m_stringValue.data(), m_stringValue.size());
244  }
245  // make nested tags
246  for(const auto &maker : m_nestedMaker) {
247  maker.make(stream);
248  }
249 }
250 
251 }
The MatroskaTagFieldMaker class helps making tag fields.
The TagValue class wraps values of different types.
Definition: tagvalue.h:64
void make(std::ostream &stream) const
Saves the field (specified when constructing the object) to the specified stream (makes a "SimpleTag"...
static byte calculateSizeDenotationLength(uint64 size)
Returns the length of the size denotation for the specified size in byte.
TAG_PARSER_EXPORT const char * language()
The EbmlElement class helps to parse EBML files such as Matroska files.
Definition: ebmlelement.h:50
void clear()
Clears id, value, type info and sets default flag to false.
char * dataPointer() const
Returns a pointer to the raw data assigned to the current instance.
Definition: tagvalue.h:376
void parse()
Parses the header information of the element which is read from the related stream at the start offse...
void setDefault(bool isDefault)
Sets whether the field is labeled as default.
STL namespace.
void addNotification(const Notification &notification)
This method is meant to be called by the derived class to add a notification.
const identifierType & id() const
Returns the id of the current TagField.
bool isDefault() const
Returns an indication whether the field is labeled as default.
const std::string & language() const
Returns the language.
Definition: tagvalue.h:432
static byte makeSizeDenotation(uint64 size, char *buff)
Makes the size denotation for the specified size and stores it to buff.
Contains utility classes helping to read and write streams.
The exception that is thrown when the data to be parsed or to be made seems invalid and therefore can...
Definition: exceptions.h:27
MatroskaTagFieldMaker prepareMaking()
Prepares making.
TagValue & value()
Returns the value of the current TagField.
implementationType * firstChild()
Returns the first child of the element.
The class inherits from std::exception and serves as base class for exceptions thrown by the elements...
Definition: exceptions.h:11
The TagField class is used by FieldMapBasedTag to store the fields.
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:648
void reparse(EbmlElement &simpleTagElement, bool parseNestedFields=true)
Parses field information from the specified EbmlElement.
The MatroskaTagField class is used by MatroskaTag to store the fields.
void make(std::ostream &stream)
Saves the field to the specified stream (makes a "SimpleTag" element).
Contains all classes and functions of the TagInfo library.
Definition: exceptions.h:9
void setLanguage(const std::string &value)
Sets the language.
Definition: tagvalue.h:443
const std::vector< MatroskaTagField > & nestedFields() const
Returns the nested fields.
size_t dataSize() const
Returns the size of the assigned value in bytes.
Definition: tagvalue.h:365
void setId(const identifierType &id)
Sets the id of the current Tag Field.