Tag Parser  10.0.1
C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags
mp4tag.cpp
Go to the documentation of this file.
1 #include "./mp4tag.h"
2 #include "./mp4atom.h"
3 #include "./mp4container.h"
4 #include "./mp4ids.h"
5 
6 #include "../exceptions.h"
7 
8 #include <c++utilities/conversion/stringconversion.h>
9 #include <c++utilities/io/binarywriter.h>
10 
11 using namespace std;
12 using namespace CppUtilities;
13 
14 namespace TagParser {
15 
25 Mp4ExtendedFieldId::Mp4ExtendedFieldId(KnownField field)
26 {
27  switch (field) {
28  case KnownField::EncoderSettings:
31  updateOnly = false;
32  break;
36  updateOnly = true; // set record label via extended field only if extended field is already present
37  break;
38  default:;
39  }
40 }
41 
47 bool Mp4Tag::canEncodingBeUsed(TagTextEncoding encoding) const
48 {
49  switch (encoding) {
51  return true;
52  case TagTextEncoding::Utf16BigEndian:
53  return true;
54  default:
55  return false;
56  }
57 }
58 
59 const TagValue &Mp4Tag::value(KnownField field) const
60 {
61  switch (field) {
62  case KnownField::Genre: {
64  if (!value.isEmpty()) {
65  return value;
66  } else {
68  }
69  }
70  case KnownField::EncoderSettings:
74  if (!value.isEmpty()) {
75  return value;
76  } else {
78  }
79  }
80  default:
81  return FieldMapBasedTag<Mp4Tag>::value(field);
82  }
83 }
84 
85 std::vector<const TagValue *> Mp4Tag::values(KnownField field) const
86 {
87  auto values = FieldMapBasedTag<Mp4Tag>::values(field);
88  if (const auto extendedId = Mp4ExtendedFieldId(field)) {
89  auto range = fields().equal_range(Mp4TagAtomIds::Extended);
90  for (auto i = range.first; i != range.second; ++i) {
91  const auto &extendedField = i->second;
92  if (extendedId.matches(extendedField)) {
93  values.emplace_back(&extendedField.value());
94  for (const auto &additionalData : extendedField.additionalData()) {
95  values.emplace_back(&additionalData.value);
96  }
97  }
98  }
99  }
100  return values;
101 }
102 
108 const TagValue &Mp4Tag::value(std::string_view mean, std::string_view name) const
109 {
110  auto range = fields().equal_range(Mp4TagAtomIds::Extended);
111  for (auto i = range.first; i != range.second; ++i) {
112  if (i->second.mean() == mean && i->second.name() == name) {
113  return i->second.value();
114  }
115  }
116  return TagValue::empty();
117 }
118 
119 Mp4Tag::IdentifierType Mp4Tag::internallyGetFieldId(KnownField field) const
120 {
121  using namespace Mp4TagAtomIds;
122  switch (field) {
123  case KnownField::Album:
124  return Album;
125  case KnownField::Artist:
126  return Artist;
127  case KnownField::Comment:
128  return Comment;
129  case KnownField::RecordDate:
130  return Year;
131  case KnownField::Title:
132  return Title;
133  case KnownField::Genre:
134  return Genre;
136  return TrackPosition;
138  return DiskPosition;
140  return Composer;
141  case KnownField::Encoder:
142  return Encoder;
143  case KnownField::Bpm:
144  return Bpm;
145  case KnownField::Cover:
146  return Cover;
147  case KnownField::Rating:
148  return Rating;
150  return Grouping;
152  return Description;
153  case KnownField::Lyrics:
154  return Lyrics;
156  return RecordLabel;
158  return Performers;
160  return Lyricist;
162  return AlbumArtist;
163  default:
164  return 0;
165  }
166  // do not forget to extend Mp4Tag::internallyGetKnownField() and Mp4TagField::appropriateRawDataType() as well
167 }
168 
169 KnownField Mp4Tag::internallyGetKnownField(const IdentifierType &id) const
170 {
171  using namespace Mp4TagAtomIds;
172  switch (id) {
173  case Album:
174  return KnownField::Album;
175  case Artist:
176  return KnownField::Artist;
177  case Comment:
178  return KnownField::Comment;
179  case Year:
180  return KnownField::RecordDate;
181  case Title:
182  return KnownField::Title;
183  case PreDefinedGenre:
184  case Genre:
185  return KnownField::Genre;
186  case TrackPosition:
188  case DiskPosition:
190  case Composer:
191  return KnownField::Composer;
192  case Encoder:
193  return KnownField::Encoder;
194  case Bpm:
195  return KnownField::Bpm;
196  case Cover:
197  return KnownField::Cover;
198  case Rating:
199  return KnownField::Rating;
200  case Grouping:
201  return KnownField::Grouping;
202  case Description:
204  case Lyrics:
205  return KnownField::Lyrics;
206  case RecordLabel:
208  case Performers:
209  return KnownField::Performers;
210  case Lyricist:
211  return KnownField::Lyricist;
212  case AlbumArtist:
214  default:
215  return KnownField::Invalid;
216  }
217  // do not forget to extend Mp4Tag::internallyGetFieldId() and Mp4TagField::appropriateRawDataType() as well
218 }
219 
223 void Mp4Tag::internallyGetValuesFromField(const Mp4Tag::FieldType &field, std::vector<const TagValue *> &values) const
224 {
225  if (!field.value().isEmpty()) {
226  values.emplace_back(&field.value());
227  }
228  for (const auto &value : field.additionalData()) {
229  if (!value.value.isEmpty()) {
230  values.emplace_back(&value.value);
231  }
232  }
233 }
234 
235 bool Mp4Tag::setValue(KnownField field, const TagValue &value)
236 {
237  switch (field) {
238  case KnownField::Genre:
239  switch (value.type()) {
240  case TagDataType::StandardGenreIndex:
241  fields().erase(Mp4TagAtomIds::Genre);
243  default:
244  fields().erase(Mp4TagAtomIds::PreDefinedGenre);
246  }
247  case KnownField::EncoderSettings:
250  if (!this->value(Mp4TagExtendedMeanIds::iTunes, Mp4TagExtendedNameIds::label).isEmpty()) {
252  }
253  [[fallthrough]];
254  default:
255  return FieldMapBasedTag<Mp4Tag>::setValue(field, value);
256  }
257 }
258 
259 bool Mp4Tag::setValues(KnownField field, const std::vector<TagValue> &values)
260 {
261  if (const auto extendedId = Mp4ExtendedFieldId(field)) {
262  auto valuesIterator = values.cbegin();
263  auto range = fields().equal_range(Mp4TagAtomIds::Extended);
264  for (; valuesIterator != values.cend() && range.first != range.second;) {
265  if (!valuesIterator->isEmpty()) {
266  auto &extendedField = range.first->second;
267  if (extendedId.matches(extendedField) && (!extendedId.updateOnly || !extendedField.value().isEmpty())) {
268  extendedField.clearValue();
269  extendedField.setValue(*valuesIterator);
270  // note: Not sure which extended tag fields support multiple data atoms and which don't. Let's simply use
271  // only one data atom per extended field here and get rid of any possibly assigned additional data
272  // atoms.
273  ++valuesIterator;
274  }
275  ++range.first;
276  } else {
277  ++valuesIterator;
278  }
279  }
280  for (; valuesIterator != values.cend(); ++valuesIterator) {
281  fields().emplace(std::piecewise_construct, std::forward_as_tuple(Mp4TagAtomIds::Extended),
282  std::forward_as_tuple(extendedId.mean, extendedId.name, *valuesIterator));
283  }
284  for (; range.first != range.second; ++range.first) {
285  range.first->second.clearValue();
286  }
287  }
288  return FieldMapBasedTag<Mp4Tag>::setValues(field, values);
289 }
290 
298 bool Mp4Tag::setValue(std::string_view mean, std::string_view name, const TagValue &value)
299 {
300  auto range = fields().equal_range(Mp4TagAtomIds::Extended);
301  for (auto i = range.first; i != range.second; ++i) {
302  if (i->second.mean() == mean && i->second.name() == name) {
303  i->second.setValue(value);
304  return true;
305  }
306  }
307  fields().insert(make_pair(Mp4TagAtomIds::Extended, FieldType(mean, name, value)));
308  return true;
309 }
310 
311 bool Mp4Tag::hasField(KnownField field) const
312 {
313  switch (field) {
314  case KnownField::Genre:
316  default:
318  }
319 }
320 
328 void Mp4Tag::parse(Mp4Atom &metaAtom, Diagnostics &diag)
329 {
330  static const string context("parsing MP4 tag");
331  istream &stream = metaAtom.container().stream();
332  BinaryReader &reader = metaAtom.container().reader();
333  if (metaAtom.totalSize() > numeric_limits<std::uint32_t>::max()) {
334  diag.emplace_back(DiagLevel::Critical, "Can't handle such big \"meta\" atoms.", context);
335  throw NotImplementedException();
336  }
337  m_size = static_cast<std::uint32_t>(metaAtom.totalSize());
338  Mp4Atom *subAtom = nullptr;
339  try {
340  metaAtom.childById(Mp4AtomIds::HandlerReference, diag);
341  } catch (const Failure &) {
342  diag.emplace_back(DiagLevel::Critical, "Unable to parse child atoms of meta atom (stores hdlr and ilst atoms).", context);
343  }
344  if (subAtom) {
345  stream.seekg(static_cast<streamoff>(subAtom->startOffset() + subAtom->headerSize()));
346  int versionByte = reader.readByte();
347  if (versionByte != 0) {
348  diag.emplace_back(DiagLevel::Warning, "Version is unknown.", context);
349  }
350  if (reader.readUInt24BE()) {
351  diag.emplace_back(DiagLevel::Warning, "Flags (hdlr atom) aren't set to 0.", context);
352  }
353  if (reader.readInt32BE()) {
354  diag.emplace_back(DiagLevel::Warning, "Predefined 32-bit integer (hdlr atom) isn't set to 0.", context);
355  }
356  std::uint64_t handlerType = reader.readUInt64BE();
357  if (/*(((handlerType & 0xFFFFFFFF00000000) >> 32) != 0x6D647461) && */ (handlerType != 0x6d6469726170706c)) {
358  diag.emplace_back(DiagLevel::Warning, "Handler type (value in hdlr atom) is unknown. Trying to parse meta information anyhow.", context);
359  }
360  m_version = numberToString(versionByte);
361  } else {
362  m_version.clear();
363  }
364  try {
365  subAtom = metaAtom.childById(Mp4AtomIds::ItunesList, diag);
366  } catch (const Failure &) {
367  diag.emplace_back(DiagLevel::Critical, "Unable to parse child atoms of meta atom (stores hdlr and ilst atoms).", context);
368  }
369  if (!subAtom) {
370  diag.emplace_back(DiagLevel::Warning, "No ilst atom found (stores attached meta information).", context);
371  throw NoDataFoundException();
372  }
373  for (auto *child = subAtom->firstChild(); child; child = child->nextSibling()) {
374  Mp4TagField tagField;
375  try {
376  child->parse(diag);
377  tagField.reparse(*child, diag);
378  fields().emplace(child->id(), move(tagField));
379  } catch (const Failure &) {
380  }
381  }
382 }
383 
394 Mp4TagMaker Mp4Tag::prepareMaking(Diagnostics &diag)
395 {
396  return Mp4TagMaker(*this, diag);
397 }
398 
405 void Mp4Tag::make(ostream &stream, Diagnostics &diag)
406 {
407  prepareMaking(diag).make(stream, diag);
408 }
409 
421 Mp4TagMaker::Mp4TagMaker(Mp4Tag &tag, Diagnostics &diag)
422  : m_tag(tag)
423  ,
424  // meta head, hdlr atom
425  m_metaSize(8 + 37)
426  ,
427  // ilst head
428  m_ilstSize(8)
429  ,
430  // ensure there only one genre atom is written (prefer genre as string)
431  m_omitPreDefinedGenre(m_tag.fields().count(m_tag.hasField(Mp4TagAtomIds::Genre)))
432 {
433  m_maker.reserve(m_tag.fields().size());
434  for (auto &field : m_tag.fields()) {
435  if (!field.second.value().isEmpty() && (!m_omitPreDefinedGenre || field.first != Mp4TagAtomIds::PreDefinedGenre)) {
436  try {
437  m_ilstSize += m_maker.emplace_back(field.second.prepareMaking(diag)).requiredSize();
438  } catch (const Failure &) {
439  }
440  }
441  }
442  if (m_ilstSize != 8) {
443  m_metaSize += m_ilstSize;
444  }
445  if (m_metaSize >= numeric_limits<std::uint32_t>::max()) {
446  diag.emplace_back(DiagLevel::Critical, "Making such big tags is not implemented.", "making MP4 tag");
447  throw NotImplementedException();
448  }
449 }
450 
458 void Mp4TagMaker::make(ostream &stream, Diagnostics &diag)
459 {
460  // write meta head
461  BinaryWriter writer(&stream);
462  writer.writeUInt32BE(static_cast<std::uint32_t>(m_metaSize));
463  writer.writeUInt32BE(Mp4AtomIds::Meta);
464  // write hdlr atom
465  static const std::uint8_t hdlrData[37] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x68, 0x64, 0x6C, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00,
466  0x00, 0x00, 0x00, 0x6D, 0x64, 0x69, 0x72, 0x61, 0x70, 0x70, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
467  stream.write(reinterpret_cast<const char *>(hdlrData), sizeof(hdlrData));
468  if (m_ilstSize != 8) {
469  // write ilst head
470  writer.writeUInt32BE(static_cast<std::uint32_t>(m_ilstSize));
471  writer.writeUInt32BE(Mp4AtomIds::ItunesList);
472  // write fields
473  for (auto &maker : m_maker) {
474  maker.make(stream);
475  }
476  } else {
477  // no fields to be written -> no ilst to be written
478  diag.emplace_back(DiagLevel::Warning, "Tag is empty.", "making MP4 tag");
479  }
480 }
481 
482 } // namespace TagParser
The Diagnostics class is a container for DiagMessage.
Definition: diagnostics.h:156
The class inherits from std::exception and serves as base class for exceptions thrown by the elements...
Definition: exceptions.h:11
The FieldMapBasedTag provides a generic implementation of Tag which stores the tag fields using std::...
Definition: fieldbasedtag.h:31
typename FieldMapBasedTagTraits< Mp4Tag >::FieldType::IdentifierType IdentifierType
Definition: fieldbasedtag.h:36
typename FieldMapBasedTagTraits< Mp4Tag >::FieldType FieldType
Definition: fieldbasedtag.h:35
std::uint64_t startOffset() const
Returns the start offset in the related stream.
std::uint32_t headerSize() const
Returns the header size of the element in byte.
ImplementationType * childById(const IdentifierType &id, Diagnostics &diag)
Returns the first child with the specified id.
ImplementationType * nextSibling()
Returns the next sibling of the element.
ImplementationType * firstChild()
Returns the first child of the element.
std::uint64_t totalSize() const
Returns the total size of the element.
ContainerType & container()
Returns the related container.
The Mp4Atom class helps to parse MP4 files.
Definition: mp4atom.h:38
The Mp4TagField class is used by Mp4Tag to store the fields.
Definition: mp4tagfield.h:112
void reparse(Mp4Atom &ilstChild, Diagnostics &diag)
Parses field information from the specified Mp4Atom.
Definition: mp4tagfield.cpp:73
The Mp4TagMaker class helps writing MP4 tags.
Definition: mp4tag.h:54
void make(std::ostream &stream, Diagnostics &diag)
Saves the tag (specified when constructing the object) to the specified stream.
Definition: mp4tag.cpp:458
Implementation of TagParser::Tag for the MP4 container.
Definition: mp4tag.h:97
The exception that is thrown when the data to be parsed holds no parsable information (e....
Definition: exceptions.h:18
This exception is thrown when the an operation is invoked that has not been implemented yet.
Definition: exceptions.h:60
The TagValue class wraps values of different types.
Definition: tagvalue.h:95
TagDataType type() const
Returns the type of the assigned value.
Definition: tagvalue.h:468
bool isEmpty() const
Returns whether no or an empty value is assigned.
Definition: tagvalue.h:527
Contains all classes and functions of the TagInfo library.
Definition: aaccodebook.h:10
KnownField
Specifies the field.
Definition: tag.h:42
TagTextEncoding
Specifies the text encoding.
Definition: tagvalue.h:28
The Mp4ExtendedFieldId specifies parameter for an extended field denoted via Mp4TagAtomIds::Extended.
Definition: mp4tag.h:13