Tag Parser  8.2.0
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 IoUtilities;
13 using namespace ConversionUtilities;
14 
15 namespace TagParser {
16 
26 Mp4ExtendedFieldId::Mp4ExtendedFieldId(KnownField field)
27 {
28  switch (field) {
29  case KnownField::EncoderSettings:
32  updateOnly = false;
33  break;
37  updateOnly = true; // set record label via extended field only if extended field is already present
38  break;
39  default:
40  mean = nullptr;
41  name = nullptr;
42  updateOnly = false;
43  }
44 }
45 
51 bool Mp4Tag::canEncodingBeUsed(TagTextEncoding encoding) const
52 {
53  switch (encoding) {
55  return true;
56  case TagTextEncoding::Utf16BigEndian:
57  return true;
58  default:
59  return false;
60  }
61 }
62 
63 const TagValue &Mp4Tag::value(KnownField field) const
64 {
65  switch (field) {
66  case KnownField::Genre: {
68  if (!value.isEmpty()) {
69  return value;
70  } else {
72  }
73  }
74  case KnownField::EncoderSettings:
78  if (!value.isEmpty()) {
79  return value;
80  } else {
82  }
83  }
84  default:
85  return FieldMapBasedTag<Mp4Tag>::value(field);
86  }
87 }
88 
89 std::vector<const TagValue *> Mp4Tag::values(KnownField field) const
90 {
91  auto values = FieldMapBasedTag<Mp4Tag>::values(field);
92  const Mp4ExtendedFieldId extendedId(field);
93  if (extendedId) {
94  auto range = fields().equal_range(Mp4TagAtomIds::Extended);
95  for (auto i = range.first; i != range.second; ++i) {
96  if (extendedId.matches(i->second)) {
97  values.emplace_back(&i->second.value());
98  }
99  }
100  }
101  return values;
102 }
103 
109 const TagValue &Mp4Tag::value(const char *mean, const char *name) const
110 {
111  auto range = fields().equal_range(Mp4TagAtomIds::Extended);
112  for (auto i = range.first; i != range.second; ++i) {
113  if (i->second.mean() == mean && i->second.name() == name) {
114  return i->second.value();
115  }
116  }
117  return TagValue::empty();
118 }
119 
120 Mp4Tag::IdentifierType Mp4Tag::internallyGetFieldId(KnownField field) const
121 {
122  using namespace Mp4TagAtomIds;
123  switch (field) {
124  case KnownField::Album:
125  return Album;
126  case KnownField::Artist:
127  return Artist;
128  case KnownField::Comment:
129  return Comment;
130  case KnownField::Year:
131  return Year;
132  case KnownField::Title:
133  return Title;
134  case KnownField::Genre:
135  return Genre;
137  return TrackPosition;
139  return DiskPosition;
141  return Composer;
142  case KnownField::Encoder:
143  return Encoder;
144  case KnownField::Bpm:
145  return Bpm;
146  case KnownField::Cover:
147  return Cover;
148  case KnownField::Rating:
149  return Rating;
151  return Grouping;
153  return Description;
154  case KnownField::Lyrics:
155  return Lyrics;
157  return RecordLabel;
159  return Performers;
161  return Lyricist;
163  return AlbumArtist;
164  default:
165  return 0;
166  }
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::Year;
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 }
218 
219 bool Mp4Tag::setValue(KnownField field, const TagValue &value)
220 {
221  switch (field) {
222  case KnownField::Genre:
223  switch (value.type()) {
224  case TagDataType::StandardGenreIndex:
225  fields().erase(Mp4TagAtomIds::Genre);
227  default:
228  fields().erase(Mp4TagAtomIds::PreDefinedGenre);
230  }
231  case KnownField::EncoderSettings:
234  if (!this->value(Mp4TagExtendedMeanIds::iTunes, Mp4TagExtendedNameIds::label).isEmpty()) {
236  }
237  FALLTHROUGH;
238  default:
239  return FieldMapBasedTag<Mp4Tag>::setValue(field, value);
240  }
241 }
242 
243 bool Mp4Tag::setValues(KnownField field, const std::vector<TagValue> &values)
244 {
245  const Mp4ExtendedFieldId extendedId(field);
246  if (extendedId) {
247  auto valuesIterator = values.cbegin();
248  auto range = fields().equal_range(Mp4TagAtomIds::Extended);
249  for (; valuesIterator != values.cend() && range.first != range.second;) {
250  if (!valuesIterator->isEmpty()) {
251  if (extendedId.matches(range.first->second) && (!extendedId.updateOnly || !range.first->second.value().isEmpty())) {
252  range.first->second.setValue(*valuesIterator);
253  ++valuesIterator;
254  }
255  ++range.first;
256  } else {
257  ++valuesIterator;
258  }
259  }
260  for (; valuesIterator != values.cend(); ++valuesIterator) {
261  Mp4TagField tagField(Mp4TagAtomIds::Extended, *valuesIterator);
262  tagField.setMean(extendedId.mean);
263  tagField.setName(extendedId.name);
264  fields().insert(std::make_pair(Mp4TagAtomIds::Extended, move(tagField)));
265  }
266  for (; range.first != range.second; ++range.first) {
267  range.first->second.setValue(TagValue());
268  }
269  }
270  return FieldMapBasedTag<Mp4Tag>::setValues(field, values);
271 }
272 
280 bool Mp4Tag::setValue(const char *mean, const char *name, const TagValue &value)
281 {
282  auto range = fields().equal_range(Mp4TagAtomIds::Extended);
283  for (auto i = range.first; i != range.second; ++i) {
284  if (i->second.mean() == mean && i->second.name() == name) {
285  i->second.setValue(value);
286  return true;
287  }
288  }
289  fields().insert(make_pair(Mp4TagAtomIds::Extended, FieldType(mean, name, value)));
290  return true;
291 }
292 
293 bool Mp4Tag::hasField(KnownField field) const
294 {
295  switch (field) {
296  case KnownField::Genre:
298  default:
300  }
301 }
302 
310 void Mp4Tag::parse(Mp4Atom &metaAtom, Diagnostics &diag)
311 {
312  static const string context("parsing MP4 tag");
313  istream &stream = metaAtom.container().stream();
314  BinaryReader &reader = metaAtom.container().reader();
315  if (metaAtom.totalSize() > numeric_limits<uint32>::max()) {
316  diag.emplace_back(DiagLevel::Critical, "Can't handle such big \"meta\" atoms.", context);
317  throw NotImplementedException();
318  }
319  m_size = static_cast<uint32>(metaAtom.totalSize());
320  Mp4Atom *subAtom = nullptr;
321  try {
322  metaAtom.childById(Mp4AtomIds::HandlerReference, diag);
323  } catch (const Failure &) {
324  diag.emplace_back(DiagLevel::Critical, "Unable to parse child atoms of meta atom (stores hdlr and ilst atoms).", context);
325  }
326  if (subAtom) {
327  stream.seekg(static_cast<streamoff>(subAtom->startOffset() + subAtom->headerSize()));
328  int versionByte = reader.readByte();
329  if (versionByte != 0) {
330  diag.emplace_back(DiagLevel::Warning, "Version is unknown.", context);
331  }
332  if (reader.readUInt24BE()) {
333  diag.emplace_back(DiagLevel::Warning, "Flags (hdlr atom) aren't set to 0.", context);
334  }
335  if (reader.readInt32BE()) {
336  diag.emplace_back(DiagLevel::Warning, "Predefined 32-bit integer (hdlr atom) ins't set to 0.", context);
337  }
338  uint64 handlerType = reader.readUInt64BE();
339  if (/*(((handlerType & 0xFFFFFFFF00000000) >> 32) != 0x6D647461) && */ (handlerType != 0x6d6469726170706c)) {
340  diag.emplace_back(DiagLevel::Warning, "Handler type (value in hdlr atom) is unknown. Trying to parse meta information anyhow.", context);
341  }
342  m_version = ConversionUtilities::numberToString(versionByte);
343  } else {
344  m_version.clear();
345  }
346  try {
347  subAtom = metaAtom.childById(Mp4AtomIds::ItunesList, diag);
348  } catch (const Failure &) {
349  diag.emplace_back(DiagLevel::Critical, "Unable to parse child atoms of meta atom (stores hdlr and ilst atoms).", context);
350  }
351  if (!subAtom) {
352  diag.emplace_back(DiagLevel::Warning, "No ilst atom found (stores attached meta information).", context);
353  throw NoDataFoundException();
354  }
355  for (auto *child = subAtom->firstChild(); child; child = child->nextSibling()) {
356  Mp4TagField tagField;
357  try {
358  child->parse(diag);
359  tagField.reparse(*child, diag);
360  fields().emplace(child->id(), move(tagField));
361  } catch (const Failure &) {
362  }
363  }
364 }
365 
376 Mp4TagMaker Mp4Tag::prepareMaking(Diagnostics &diag)
377 {
378  return Mp4TagMaker(*this, diag);
379 }
380 
387 void Mp4Tag::make(ostream &stream, Diagnostics &diag)
388 {
389  prepareMaking(diag).make(stream, diag);
390 }
391 
403 Mp4TagMaker::Mp4TagMaker(Mp4Tag &tag, Diagnostics &diag)
404  : m_tag(tag)
405  ,
406  // meta head, hdlr atom
407  m_metaSize(8 + 37)
408  ,
409  // ilst head
410  m_ilstSize(8)
411  ,
412  // ensure there only one genre atom is written (prefer genre as string)
413  m_omitPreDefinedGenre(m_tag.fields().count(m_tag.hasField(Mp4TagAtomIds::Genre)))
414 {
415  m_maker.reserve(m_tag.fields().size());
416  for (auto &field : m_tag.fields()) {
417  if (!field.second.value().isEmpty() && (!m_omitPreDefinedGenre || field.first != Mp4TagAtomIds::PreDefinedGenre)) {
418  try {
419  m_maker.emplace_back(field.second.prepareMaking(diag));
420  m_ilstSize += m_maker.back().requiredSize();
421  } catch (const Failure &) {
422  }
423  }
424  }
425  if (m_ilstSize != 8) {
426  m_metaSize += m_ilstSize;
427  }
428  if (m_metaSize >= numeric_limits<uint32>::max()) {
429  diag.emplace_back(DiagLevel::Critical, "Making such big tags is not implemented.", "making MP4 tag");
430  throw NotImplementedException();
431  }
432 }
433 
441 void Mp4TagMaker::make(ostream &stream, Diagnostics &diag)
442 {
443  // write meta head
444  BinaryWriter writer(&stream);
445  writer.writeUInt32BE(static_cast<uint32>(m_metaSize));
446  writer.writeUInt32BE(Mp4AtomIds::Meta);
447  // write hdlr atom
448  static const byte hdlrData[37] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x68, 0x64, 0x6C, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
449  0x00, 0x00, 0x6D, 0x64, 0x69, 0x72, 0x61, 0x70, 0x70, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
450  stream.write(reinterpret_cast<const char *>(hdlrData), sizeof(hdlrData));
451  if (m_ilstSize != 8) {
452  // write ilst head
453  writer.writeUInt32BE(static_cast<uint32>(m_ilstSize));
454  writer.writeUInt32BE(Mp4AtomIds::ItunesList);
455  // write fields
456  for (auto &maker : m_maker) {
457  maker.make(stream);
458  }
459  } else {
460  // no fields to be written -> no ilst to be written
461  diag.emplace_back(DiagLevel::Warning, "Tag is empty.", "making MP4 tag");
462  }
463 }
464 
465 } // namespace TagParser
This exception is thrown when the an operation is invoked that has not been implemented yet.
Definition: exceptions.h:60
The FieldMapBasedTag provides a generic implementation of Tag which stores the tag fields using std::...
Definition: fieldbasedtag.h:31
void make(std::ostream &stream, Diagnostics &diag)
Saves the tag (specified when constructing the object) to the specified stream.
Definition: mp4tag.cpp:441
const char * mean
mean parameter, usually Mp4TagExtendedMeanIds::iTunes
Definition: mp4tag.h:21
Implementation of TagParser::Tag for the MP4 container.
Definition: mp4tag.h:97
The Mp4TagField class is used by Mp4Tag to store the fields.
Definition: mp4tagfield.h:97
bool matches(const Mp4TagField &field) const
Returns whether the current parameter match the specified field.
Definition: mp4tag.h:49
typename FieldMapBasedTagTraits< Mp4Tag >::FieldType::IdentifierType IdentifierType
Definition: fieldbasedtag.h:36
KnownField
Specifies the field.
Definition: tag.h:42
The Mp4TagMaker class helps writing MP4 tags.
Definition: mp4tag.h:54
The Mp4Atom class helps to parse MP4 files.
Definition: mp4atom.h:38
uint64 totalSize() const
Returns the total size of the element.
typename FieldMapBasedTagTraits< Mp4Tag >::FieldType FieldType
Definition: fieldbasedtag.h:35
The Mp4ExtendedFieldId specifies parameter for an extended field denoted via Mp4TagAtomIds::Extended.
Definition: mp4tag.h:13
const char * name
name parameter
Definition: mp4tag.h:23
bool isEmpty() const
Returns an indication whether an value is assigned.
Definition: tagvalue.h:389
Contains utility classes helping to read and write streams.
ContainerType & container()
Returns the related container.
void setMean(const std::string &mean)
Sets the "mean" for the "extended" field.
Definition: mp4tagfield.h:168
The exception that is thrown when the data to be parsed holds no parsable information (e....
Definition: exceptions.h:18
TagDataType type() const
Returns the type of the assigned value.
Definition: tagvalue.h:353
void reparse(Mp4Atom &ilstChild, Diagnostics &diag)
Parses field information from the specified Mp4Atom.
Definition: mp4tagfield.cpp:74
void setName(const std::string &name)
Sets the "name" for the "extended" field.
Definition: mp4tagfield.h:152
The TagValue class wraps values of different types.
Definition: tagvalue.h:65
The class inherits from std::exception and serves as base class for exceptions thrown by the elements...
Definition: exceptions.h:11
bool updateOnly
Whether only existing fields should be updated but no new extended field should be created.
Definition: mp4tag.h:25
ImplementationType * childById(const IdentifierType &id, Diagnostics &diag)
Returns the first child with the specified id.
TagTextEncoding
Specifies the text encoding.
Definition: tagvalue.h:24
Contains all classes and functions of the TagInfo library.
Definition: aaccodebook.h:9
The Diagnostics class is a container for DiagMessage.
Definition: diagnostics.h:156