Tag Parser  8.2.0
C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags
matroskatag.cpp
Go to the documentation of this file.
1 #include "./matroskatag.h"
2 #include "./ebmlelement.h"
3 
4 #include "../diagnostics.h"
5 
6 #include <initializer_list>
7 #include <map>
8 #include <stdexcept>
9 
10 using namespace std;
11 using namespace ConversionUtilities;
12 
13 namespace TagParser {
14 
20 MatroskaTag::IdentifierType MatroskaTag::internallyGetFieldId(KnownField field) const
21 {
22  using namespace MatroskaTagIds;
23  switch (field) {
24  case KnownField::Artist:
25  return artist();
26  case KnownField::Album:
27  return album();
29  return comment();
30  case KnownField::RecordDate:
31  return dateRecorded();
32  case KnownField::Year:
33  return dateRelease();
34  case KnownField::Title:
35  return title();
36  case KnownField::Genre:
37  return genre();
38  case KnownField::PartNumber:
39  return partNumber();
40  case KnownField::TotalParts:
41  return totalParts();
43  return encoder();
44  case KnownField::EncoderSettings:
45  return encoderSettings();
46  case KnownField::Bpm:
47  return bpm();
48  case KnownField::Bps:
49  return bps();
50  case KnownField::Rating:
51  return rating();
53  return description();
54  case KnownField::Lyrics:
55  return lyrics();
57  return label();
59  return actor();
61  return lyricist();
63  return composer();
64  case KnownField::Length:
65  return duration();
66  case KnownField::Language:
67  return language();
68  default:
69  return string();
70  }
71 }
72 
73 KnownField MatroskaTag::internallyGetKnownField(const IdentifierType &id) const
74 {
75  using namespace MatroskaTagIds;
76  static const map<string, KnownField> fieldMap({
78  { album(), KnownField::Album },
80  { dateRecorded(), KnownField::RecordDate },
82  { title(), KnownField::Title },
83  { partNumber(), KnownField::PartNumber },
84  { totalParts(), KnownField::TotalParts },
86  { encoderSettings(), KnownField::EncoderSettings },
87  { bpm(), KnownField::Bpm },
88  { bps(), KnownField::Bps },
96  { duration(), KnownField::Length },
97  { language(), KnownField::Language },
98  });
99  const auto knownField(fieldMap.find(id));
100  return knownField != fieldMap.cend() ? knownField->second : KnownField::Invalid;
101 }
102 
110 void MatroskaTag::parse(EbmlElement &tagElement, Diagnostics &diag)
111 {
112  static const string context("parsing Matroska tag");
113  tagElement.parse(diag);
114  if (tagElement.totalSize() > numeric_limits<uint32>::max()) {
115  // FIXME: Support this? Likely not very useful in practise.
116  diag.emplace_back(DiagLevel::Critical, "Matroska tag is too big.", context);
117  throw NotImplementedException();
118  }
119  m_size = static_cast<uint32>(tagElement.totalSize());
120  for (EbmlElement *child = tagElement.firstChild(); child; child = child->nextSibling()) {
121  child->parse(diag);
122  switch (child->id()) {
124  try {
125  MatroskaTagField field;
126  field.reparse(*child, diag, true);
127  fields().emplace(field.id(), move(field));
128  } catch (const Failure &) {
129  }
130  break;
132  parseTargets(*child, diag);
133  break;
134  }
135  }
136 }
137 
145 void MatroskaTag::parseTargets(EbmlElement &targetsElement, Diagnostics &diag)
146 {
147  static const string context("parsing targets of Matroska tag");
148  m_target.clear();
149  bool targetTypeValueFound = false;
150  bool targetTypeFound = false;
151  targetsElement.parse(diag);
152  for (EbmlElement *child = targetsElement.firstChild(); child; child = child->nextSibling()) {
153  try {
154  child->parse(diag);
155  } catch (const Failure &) {
156  diag.emplace_back(DiagLevel::Critical, "Unable to parse childs of Targets element.", context);
157  break;
158  }
159  switch (child->id()) {
161  if (!targetTypeValueFound) {
162  m_target.setLevel(child->readUInteger());
163  targetTypeValueFound = true;
164  } else {
165  diag.emplace_back(
166  DiagLevel::Warning, "Targets element contains multiple TargetTypeValue elements. Surplus elements will be ignored.", context);
167  }
168  break;
170  if (!targetTypeFound) {
171  m_target.setLevelName(child->readString());
172  targetTypeFound = true;
173  } else {
174  diag.emplace_back(
175  DiagLevel::Warning, "Targets element contains multiple TargetType elements. Surplus elements will be ignored.", context);
176  }
177  break;
179  m_target.tracks().emplace_back(child->readUInteger());
180  break;
182  m_target.editions().emplace_back(child->readUInteger());
183  break;
185  m_target.chapters().emplace_back(child->readUInteger());
186  break;
188  m_target.attachments().emplace_back(child->readUInteger());
189  break;
190  default:
191  diag.emplace_back(DiagLevel::Warning, "Targets element contains unknown element. It will be ignored.", context);
192  }
193  }
194  if (!m_target.level()) {
195  m_target.setLevel(50); // default level
196  }
197 }
198 
210 MatroskaTagMaker::MatroskaTagMaker(MatroskaTag &tag, Diagnostics &diag)
211  : m_tag(tag)
212 {
213  // calculate size of "Targets" element
214  m_targetsSize = 0; // NOT including ID and size
215  if (m_tag.target().level() != 50) {
216  // size of "TargetTypeValue"
217  m_targetsSize += 2 + 1 + EbmlElement::calculateUIntegerLength(m_tag.target().level());
218  }
219  if (!m_tag.target().levelName().empty()) {
220  // size of "TargetType"
221  m_targetsSize += 2 + EbmlElement::calculateSizeDenotationLength(m_tag.target().levelName().size()) + m_tag.target().levelName().size();
222  }
223  for (const auto &v : initializer_list<vector<uint64>>{
224  m_tag.target().tracks(), m_tag.target().editions(), m_tag.target().chapters(), m_tag.target().attachments() }) {
225  for (auto uid : v) {
226  // size of UID denotation
227  m_targetsSize += 2 + 1 + EbmlElement::calculateUIntegerLength(uid);
228  }
229  }
230  m_tagSize = 2 + EbmlElement::calculateSizeDenotationLength(m_targetsSize) + m_targetsSize;
231  // calculate size of "SimpleTag" elements
232  m_maker.reserve(m_tag.fields().size());
233  m_simpleTagsSize = 0; // including ID and size
234  for (auto &pair : m_tag.fields()) {
235  try {
236  m_maker.emplace_back(pair.second.prepareMaking(diag));
237  m_simpleTagsSize += m_maker.back().requiredSize();
238  } catch (const Failure &) {
239  }
240  }
241  m_tagSize += m_simpleTagsSize;
242  m_totalSize = 2 + EbmlElement::calculateSizeDenotationLength(m_tagSize) + m_tagSize;
243 }
244 
252 void MatroskaTagMaker::make(ostream &stream) const
253 {
254  // write header
255  char buff[11];
256  BE::getBytes(static_cast<uint16>(MatroskaIds::Tag), buff);
257  stream.write(buff, 2); // ID
258  byte len = EbmlElement::makeSizeDenotation(m_tagSize, buff);
259  stream.write(buff, len); // size
260  // write "Targets" element
261  BE::getBytes(static_cast<uint16>(MatroskaIds::Targets), buff);
262  stream.write(buff, 2);
263  len = EbmlElement::makeSizeDenotation(m_targetsSize, buff);
264  stream.write(buff, len);
265  const TagTarget &t = m_tag.target();
266  if (t.level() != 50) {
267  // write "TargetTypeValue"
268  BE::getBytes(static_cast<uint16>(MatroskaIds::TargetTypeValue), buff);
269  stream.write(buff, 2);
270  len = EbmlElement::makeUInteger(t.level(), buff);
271  stream.put(static_cast<char>(0x80 | len));
272  stream.write(buff, len);
273  }
274  if (!t.levelName().empty()) {
275  // write "TargetType"
276  BE::getBytes(static_cast<uint16>(MatroskaIds::TargetType), buff);
277  stream.write(buff, 2);
278  len = EbmlElement::makeSizeDenotation(t.levelName().size(), buff);
279  stream.write(buff, len);
280  stream.write(t.levelName().c_str(), t.levelName().size());
281  }
282  // write UIDs
283  using p = pair<uint16, vector<uint64>>;
284  for (const auto &pair : initializer_list<p>{ p(MatroskaIds::TagTrackUID, t.tracks()), p(MatroskaIds::TagEditionUID, t.editions()),
286  if (!pair.second.empty()) {
287  BE::getBytes(pair.first, buff);
288  for (auto uid : pair.second) {
289  len = EbmlElement::makeUInteger(uid, buff + 3);
290  *(buff + 2) = static_cast<char>(0x80 | len);
291  stream.write(buff, 3 + len);
292  }
293  }
294  }
295  // write "SimpleTag" elements using maker objects prepared previously
296  for (const auto &maker : m_maker) {
297  maker.make(stream);
298  }
299 }
300 
301 } // namespace TagParser
constexpr TAG_PARSER_EXPORT const char * encoder()
This exception is thrown when the an operation is invoked that has not been implemented yet.
Definition: exceptions.h:60
constexpr TAG_PARSER_EXPORT const char * bpm()
constexpr TAG_PARSER_EXPORT const char * dateRecorded()
typename FieldMapBasedTagTraits< MatroskaTag >::FieldType::IdentifierType IdentifierType
Definition: fieldbasedtag.h:36
const IdContainerType & attachments() const
Returns the attachments.
Definition: tagtarget.h:152
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.
uint64 level() const
Returns the level.
Definition: tagtarget.h:72
constexpr TAG_PARSER_EXPORT const char * lyrics()
constexpr TAG_PARSER_EXPORT const char * lyricist()
KnownField
Specifies the field.
Definition: tag.h:42
static byte calculateUIntegerLength(uint64 integer)
Returns the length of the specified unsigned integer in byte.
The EbmlElement class helps to parse EBML files such as Matroska files.
Definition: ebmlelement.h:31
uint64 totalSize() const
Returns the total size of the element.
The MatroskaTagField class is used by MatroskaTag to store the fields.
constexpr TAG_PARSER_EXPORT const char * rating()
constexpr TAG_PARSER_EXPORT const char * language()
constexpr TAG_PARSER_EXPORT const char * encoderSettings()
constexpr TAG_PARSER_EXPORT const char * comment()
void parse(Diagnostics &diag)
Parses the header information of the element which is read from the related stream at the start offse...
constexpr TAG_PARSER_EXPORT const char * actor()
static byte makeUInteger(uint64 value, char *buff)
Writes value to buff.
static byte makeSizeDenotation(uint64 size, char *buff)
Makes the size denotation for the specified size and stores it to buff.
The TagTarget class specifies the target of a tag.
Definition: tagtarget.h:21
constexpr TAG_PARSER_EXPORT const char * partNumber()
Definition: matroskatagid.h:30
constexpr TAG_PARSER_EXPORT const char * album()
Definition: matroskatagid.h:81
constexpr TAG_PARSER_EXPORT const char * dateRelease()
constexpr TAG_PARSER_EXPORT const char * title()
Definition: matroskatagid.h:39
const IdentifierType & id() const
Returns the id of the current TagField.
constexpr TAG_PARSER_EXPORT const char * bps()
constexpr TAG_PARSER_EXPORT const char * genre()
void make(std::ostream &stream) const
Saves the tag (specified when constructing the object) to the specified stream (makes a "Tag"-element...
const IdContainerType & tracks() const
Returns the tracks.
Definition: tagtarget.h:104
constexpr TAG_PARSER_EXPORT const char * duration()
constexpr TAG_PARSER_EXPORT const char * artist()
Definition: matroskatagid.h:77
constexpr TAG_PARSER_EXPORT const char * composer()
Definition: matroskatagid.h:93
The class inherits from std::exception and serves as base class for exceptions thrown by the elements...
Definition: exceptions.h:11
const std::string & levelName() const
Returns the level name.
Definition: tagtarget.h:88
const IdContainerType & editions() const
Returns the editions.
Definition: tagtarget.h:136
constexpr TAG_PARSER_EXPORT const char * description()
const IdContainerType & chapters() const
Returns the chapters.
Definition: tagtarget.h:120
const TagTarget & target() const
Returns the target of tag.
Definition: tag.h:174
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.
constexpr TAG_PARSER_EXPORT const char * totalParts()
Definition: matroskatagid.h:26
The Diagnostics class is a container for DiagMessage.
Definition: diagnostics.h:156