Tag Parser  8.0.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;
162  default:
163  return 0;
164  }
165 }
166 
167 KnownField Mp4Tag::internallyGetKnownField(const IdentifierType &id) const
168 {
169  using namespace Mp4TagAtomIds;
170  switch (id) {
171  case Album:
172  return KnownField::Album;
173  case Artist:
174  return KnownField::Artist;
175  case Comment:
176  return KnownField::Comment;
177  case Year:
178  return KnownField::Year;
179  case Title:
180  return KnownField::Title;
181  case PreDefinedGenre:
182  case Genre:
183  return KnownField::Genre;
184  case TrackPosition:
186  case DiskPosition:
188  case Composer:
189  return KnownField::Composer;
190  case Encoder:
191  return KnownField::Encoder;
192  case Bpm:
193  return KnownField::Bpm;
194  case Cover:
195  return KnownField::Cover;
196  case Rating:
197  return KnownField::Rating;
198  case Grouping:
199  return KnownField::Grouping;
200  case Description:
202  case Lyrics:
203  return KnownField::Lyrics;
204  case RecordLabel:
206  case Performers:
207  return KnownField::Performers;
208  case Lyricist:
209  return KnownField::Lyricist;
210  default:
211  return KnownField::Invalid;
212  }
213 }
214 
215 bool Mp4Tag::setValue(KnownField field, const TagValue &value)
216 {
217  switch (field) {
218  case KnownField::Genre:
219  switch (value.type()) {
220  case TagDataType::StandardGenreIndex:
221  fields().erase(Mp4TagAtomIds::Genre);
223  default:
224  fields().erase(Mp4TagAtomIds::PreDefinedGenre);
226  }
227  case KnownField::EncoderSettings:
230  if (!this->value(Mp4TagExtendedMeanIds::iTunes, Mp4TagExtendedNameIds::label).isEmpty()) {
232  }
233  FALLTHROUGH;
234  default:
235  return FieldMapBasedTag<Mp4Tag>::setValue(field, value);
236  }
237 }
238 
239 bool Mp4Tag::setValues(KnownField field, const std::vector<TagValue> &values)
240 {
241  const Mp4ExtendedFieldId extendedId(field);
242  if (extendedId) {
243  auto valuesIterator = values.cbegin();
244  auto range = fields().equal_range(Mp4TagAtomIds::Extended);
245  for (; valuesIterator != values.cend() && range.first != range.second;) {
246  if (!valuesIterator->isEmpty()) {
247  if (extendedId.matches(range.first->second) && (!extendedId.updateOnly || !range.first->second.value().isEmpty())) {
248  range.first->second.setValue(*valuesIterator);
249  ++valuesIterator;
250  }
251  ++range.first;
252  } else {
253  ++valuesIterator;
254  }
255  }
256  for (; valuesIterator != values.cend(); ++valuesIterator) {
257  Mp4TagField tagField(Mp4TagAtomIds::Extended, *valuesIterator);
258  tagField.setMean(extendedId.mean);
259  tagField.setName(extendedId.name);
260  fields().insert(std::make_pair(Mp4TagAtomIds::Extended, move(tagField)));
261  }
262  for (; range.first != range.second; ++range.first) {
263  range.first->second.setValue(TagValue());
264  }
265  }
266  return FieldMapBasedTag<Mp4Tag>::setValues(field, values);
267 }
268 
276 bool Mp4Tag::setValue(const char *mean, const char *name, const TagValue &value)
277 {
278  auto range = fields().equal_range(Mp4TagAtomIds::Extended);
279  for (auto i = range.first; i != range.second; ++i) {
280  if (i->second.mean() == mean && i->second.name() == name) {
281  i->second.setValue(value);
282  return true;
283  }
284  }
285  fields().insert(make_pair(Mp4TagAtomIds::Extended, FieldType(mean, name, value)));
286  return true;
287 }
288 
289 bool Mp4Tag::hasField(KnownField field) const
290 {
291  switch (field) {
292  case KnownField::Genre:
294  default:
296  }
297 }
298 
306 void Mp4Tag::parse(Mp4Atom &metaAtom, Diagnostics &diag)
307 {
308  static const string context("parsing MP4 tag");
309  istream &stream = metaAtom.container().stream();
310  BinaryReader &reader = metaAtom.container().reader();
311  if (metaAtom.totalSize() > numeric_limits<uint32>::max()) {
312  diag.emplace_back(DiagLevel::Critical, "Can't handle such big \"meta\" atoms.", context);
313  throw NotImplementedException();
314  }
315  m_size = static_cast<uint32>(metaAtom.totalSize());
316  Mp4Atom *subAtom = nullptr;
317  try {
318  metaAtom.childById(Mp4AtomIds::HandlerReference, diag);
319  } catch (const Failure &) {
320  diag.emplace_back(DiagLevel::Critical, "Unable to parse child atoms of meta atom (stores hdlr and ilst atoms).", context);
321  }
322  if (subAtom) {
323  stream.seekg(static_cast<streamoff>(subAtom->startOffset() + subAtom->headerSize()));
324  int versionByte = reader.readByte();
325  if (versionByte != 0) {
326  diag.emplace_back(DiagLevel::Warning, "Version is unknown.", context);
327  }
328  if (reader.readUInt24BE()) {
329  diag.emplace_back(DiagLevel::Warning, "Flags (hdlr atom) aren't set to 0.", context);
330  }
331  if (reader.readInt32BE()) {
332  diag.emplace_back(DiagLevel::Warning, "Predefined 32-bit integer (hdlr atom) ins't set to 0.", context);
333  }
334  uint64 handlerType = reader.readUInt64BE();
335  if (/*(((handlerType & 0xFFFFFFFF00000000) >> 32) != 0x6D647461) && */ (handlerType != 0x6d6469726170706c)) {
336  diag.emplace_back(DiagLevel::Warning, "Handler type (value in hdlr atom) is unknown. Trying to parse meta information anyhow.", context);
337  }
338  m_version = ConversionUtilities::numberToString(versionByte);
339  } else {
340  m_version.clear();
341  }
342  try {
343  subAtom = metaAtom.childById(Mp4AtomIds::ItunesList, diag);
344  } catch (const Failure &) {
345  diag.emplace_back(DiagLevel::Critical, "Unable to parse child atoms of meta atom (stores hdlr and ilst atoms).", context);
346  }
347  if (!subAtom) {
348  diag.emplace_back(DiagLevel::Warning, "No ilst atom found (stores attached meta information).", context);
349  throw NoDataFoundException();
350  }
351  for (auto *child = subAtom->firstChild(); child; child = child->nextSibling()) {
352  Mp4TagField tagField;
353  try {
354  child->parse(diag);
355  tagField.reparse(*child, diag);
356  fields().emplace(child->id(), move(tagField));
357  } catch (const Failure &) {
358  }
359  }
360 }
361 
372 Mp4TagMaker Mp4Tag::prepareMaking(Diagnostics &diag)
373 {
374  return Mp4TagMaker(*this, diag);
375 }
376 
383 void Mp4Tag::make(ostream &stream, Diagnostics &diag)
384 {
385  prepareMaking(diag).make(stream, diag);
386 }
387 
399 Mp4TagMaker::Mp4TagMaker(Mp4Tag &tag, Diagnostics &diag)
400  : m_tag(tag)
401  ,
402  // meta head, hdlr atom
403  m_metaSize(8 + 37)
404  ,
405  // ilst head
406  m_ilstSize(8)
407  ,
408  // ensure there only one genre atom is written (prefer genre as string)
409  m_omitPreDefinedGenre(m_tag.fields().count(m_tag.hasField(Mp4TagAtomIds::Genre)))
410 {
411  m_maker.reserve(m_tag.fields().size());
412  for (auto &field : m_tag.fields()) {
413  if (!field.second.value().isEmpty() && (!m_omitPreDefinedGenre || field.first != Mp4TagAtomIds::PreDefinedGenre)) {
414  try {
415  m_maker.emplace_back(field.second.prepareMaking(diag));
416  m_ilstSize += m_maker.back().requiredSize();
417  } catch (const Failure &) {
418  }
419  }
420  }
421  if (m_ilstSize != 8) {
422  m_metaSize += m_ilstSize;
423  }
424  if (m_metaSize >= numeric_limits<uint32>::max()) {
425  diag.emplace_back(DiagLevel::Critical, "Making such big tags is not implemented.", "making MP4 tag");
426  throw NotImplementedException();
427  }
428 }
429 
437 void Mp4TagMaker::make(ostream &stream, Diagnostics &diag)
438 {
439  // write meta head
440  BinaryWriter writer(&stream);
441  writer.writeUInt32BE(static_cast<uint32>(m_metaSize));
442  writer.writeUInt32BE(Mp4AtomIds::Meta);
443  // write hdlr atom
444  static const byte hdlrData[37] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x68, 0x64, 0x6C, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
445  0x00, 0x00, 0x6D, 0x64, 0x69, 0x72, 0x61, 0x70, 0x70, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
446  stream.write(reinterpret_cast<const char *>(hdlrData), sizeof(hdlrData));
447  if (m_ilstSize != 8) {
448  // write ilst head
449  writer.writeUInt32BE(static_cast<uint32>(m_ilstSize));
450  writer.writeUInt32BE(Mp4AtomIds::ItunesList);
451  // write fields
452  for (auto &maker : m_maker) {
453  maker.make(stream);
454  }
455  } else {
456  // no fields to be written -> no ilst to be written
457  diag.emplace_back(DiagLevel::Warning, "Tag is empty.", "making MP4 tag");
458  }
459 }
460 
461 } // namespace TagParser
This exception is thrown when the an operation is invoked that has not been implemented yet...
Definition: exceptions.h:53
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:437
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
ImplementationType * firstChild()
Returns the first child of the element.
STL namespace.
uint64 startOffset() const
Returns the start offset in the related stream.
KnownField
Specifies the field.
Definition: tag.h:40
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.
Definition: exceptions.h:18
uint32 headerSize() const
Returns the header size of the element in byte.
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