Tag Parser  7.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 IoUtilities;
13 using namespace ConversionUtilities;
14 
15 namespace TagParser {
16 
26 Mp4ExtendedFieldId::Mp4ExtendedFieldId(KnownField field)
27 {
28  switch (field) {
29  case KnownField::EncoderSettings:
31  break;
34  updateOnly = true; // set record label via extended field only if extended field is already present
35  break;
36  default:
37  mean = nullptr;
38  }
39 }
40 
46 bool Mp4Tag::canEncodingBeUsed(TagTextEncoding encoding) const
47 {
48  switch (encoding) {
50  return true;
51  case TagTextEncoding::Utf16BigEndian:
52  return true;
53  default:
54  return false;
55  }
56 }
57 
58 const TagValue &Mp4Tag::value(KnownField field) const
59 {
60  switch (field) {
61  case KnownField::Genre: {
63  if (!value.isEmpty()) {
64  return value;
65  } else {
67  }
68  }
69  case KnownField::EncoderSettings:
73  if (!value.isEmpty()) {
74  return value;
75  } else {
77  }
78  }
79  default:
80  return FieldMapBasedTag<Mp4Tag>::value(field);
81  }
82 }
83 
84 std::vector<const TagValue *> Mp4Tag::values(KnownField field) const
85 {
86  auto values = FieldMapBasedTag<Mp4Tag>::values(field);
87  const Mp4ExtendedFieldId extendedId(field);
88  if (extendedId) {
89  auto range = fields().equal_range(Mp4TagAtomIds::Extended);
90  for (auto i = range.first; i != range.second; ++i) {
91  if (extendedId.matches(i->second)) {
92  values.emplace_back(&i->second.value());
93  }
94  }
95  }
96  return values;
97 }
98 
104 const TagValue &Mp4Tag::value(const char *mean, const char *name) const
105 {
106  auto range = fields().equal_range(Mp4TagAtomIds::Extended);
107  for (auto i = range.first; i != range.second; ++i) {
108  if (i->second.mean() == mean && i->second.name() == name) {
109  return i->second.value();
110  }
111  }
112  return TagValue::empty();
113 }
114 
118 const TagValue &Mp4Tag::value(const string mean, const string name) const
119 {
120  return (this->*static_cast<const TagValue &(Mp4Tag::*)(const string &, const string &)const>(&Mp4Tag::value))(mean, name);
121 }
122 
123 Mp4Tag::IdentifierType Mp4Tag::internallyGetFieldId(KnownField field) const
124 {
125  using namespace Mp4TagAtomIds;
126  switch (field) {
127  case KnownField::Album:
128  return Album;
129  case KnownField::Artist:
130  return Artist;
131  case KnownField::Comment:
132  return Comment;
133  case KnownField::Year:
134  return Year;
135  case KnownField::Title:
136  return Title;
137  case KnownField::Genre:
138  return Genre;
140  return TrackPosition;
142  return DiskPosition;
144  return Composer;
145  case KnownField::Encoder:
146  return Encoder;
147  case KnownField::Bpm:
148  return Bpm;
149  case KnownField::Cover:
150  return Cover;
151  case KnownField::Rating:
152  return Rating;
154  return Grouping;
156  return Description;
157  case KnownField::Lyrics:
158  return Lyrics;
160  return RecordLabel;
162  return Performers;
164  return Lyricist;
165  default:
166  return 0;
167  }
168 }
169 
170 KnownField Mp4Tag::internallyGetKnownField(const IdentifierType &id) const
171 {
172  using namespace Mp4TagAtomIds;
173  switch (id) {
174  case Album:
175  return KnownField::Album;
176  case Artist:
177  return KnownField::Artist;
178  case Comment:
179  return KnownField::Comment;
180  case Year:
181  return KnownField::Year;
182  case Title:
183  return KnownField::Title;
184  case PreDefinedGenre:
185  case Genre:
186  return KnownField::Genre;
187  case TrackPosition:
189  case DiskPosition:
191  case Composer:
192  return KnownField::Composer;
193  case Encoder:
194  return KnownField::Encoder;
195  case Bpm:
196  return KnownField::Bpm;
197  case Cover:
198  return KnownField::Cover;
199  case Rating:
200  return KnownField::Rating;
201  case Grouping:
202  return KnownField::Grouping;
203  case Description:
205  case Lyrics:
206  return KnownField::Lyrics;
207  case RecordLabel:
209  case Performers:
210  return KnownField::Performers;
211  case Lyricist:
212  return KnownField::Lyricist;
213  default:
214  return KnownField::Invalid;
215  }
216 }
217 
218 bool Mp4Tag::setValue(KnownField field, const TagValue &value)
219 {
220  switch (field) {
221  case KnownField::Genre:
222  switch (value.type()) {
223  case TagDataType::StandardGenreIndex:
224  fields().erase(Mp4TagAtomIds::Genre);
226  default:
227  fields().erase(Mp4TagAtomIds::PreDefinedGenre);
229  }
230  case KnownField::EncoderSettings:
233  if (!this->value(Mp4TagExtendedMeanIds::iTunes, Mp4TagExtendedNameIds::label).isEmpty()) {
235  }
236  FALLTHROUGH;
237  default:
238  return FieldMapBasedTag<Mp4Tag>::setValue(field, value);
239  }
240 }
241 
242 bool Mp4Tag::setValues(KnownField field, const std::vector<TagValue> &values)
243 {
244  const Mp4ExtendedFieldId extendedId(field);
245  if (extendedId) {
246  auto valuesIterator = values.cbegin();
247  auto range = fields().equal_range(Mp4TagAtomIds::Extended);
248  for (; valuesIterator != values.cend() && range.first != range.second;) {
249  if (!valuesIterator->isEmpty()) {
250  if (extendedId.matches(range.first->second) && (!extendedId.updateOnly || !range.first->second.value().isEmpty())) {
251  range.first->second.setValue(*valuesIterator);
252  ++valuesIterator;
253  }
254  ++range.first;
255  } else {
256  ++valuesIterator;
257  }
258  }
259  for (; valuesIterator != values.cend(); ++valuesIterator) {
260  Mp4TagField tagField(Mp4TagAtomIds::Extended, *valuesIterator);
261  tagField.setMean(extendedId.mean);
262  tagField.setName(extendedId.name);
263  fields().insert(std::make_pair(Mp4TagAtomIds::Extended, move(tagField)));
264  }
265  for (; range.first != range.second; ++range.first) {
266  range.first->second.setValue(TagValue());
267  }
268  }
269  return FieldMapBasedTag<Mp4Tag>::setValues(field, values);
270 }
271 
279 bool Mp4Tag::setValue(const char *mean, const char *name, const TagValue &value)
280 {
281  auto range = fields().equal_range(Mp4TagAtomIds::Extended);
282  for (auto i = range.first; i != range.second; ++i) {
283  if (i->second.mean() == mean && i->second.name() == name) {
284  i->second.setValue(value);
285  return true;
286  }
287  }
288  fields().insert(make_pair(Mp4TagAtomIds::Extended, FieldType(mean, name, value)));
289  return true;
290 }
291 
295 bool Mp4Tag::setValue(const string mean, const string name, const TagValue &value)
296 {
297  return (this->*static_cast<bool (Mp4Tag::*)(const string &, const string &, const TagValue &)>(&Mp4Tag::setValue))(mean, name, value);
298 }
299 
300 bool Mp4Tag::hasField(KnownField field) const
301 {
302  switch (field) {
303  case KnownField::Genre:
305  default:
307  }
308 }
309 
317 void Mp4Tag::parse(Mp4Atom &metaAtom, Diagnostics &diag)
318 {
319  static const string context("parsing MP4 tag");
320  istream &stream = metaAtom.container().stream();
321  BinaryReader &reader = metaAtom.container().reader();
322  m_size = metaAtom.totalSize();
323  Mp4Atom *subAtom = nullptr;
324  try {
325  metaAtom.childById(Mp4AtomIds::HandlerReference, diag);
326  } catch (const Failure &) {
327  diag.emplace_back(DiagLevel::Critical, "Unable to parse child atoms of meta atom (stores hdlr and ilst atoms).", context);
328  }
329  if (subAtom) {
330  stream.seekg(subAtom->startOffset() + subAtom->headerSize());
331  int versionByte = reader.readByte();
332  if (versionByte != 0) {
333  diag.emplace_back(DiagLevel::Warning, "Version is unknown.", context);
334  }
335  if (reader.readUInt24BE()) {
336  diag.emplace_back(DiagLevel::Warning, "Flags (hdlr atom) aren't set to 0.", context);
337  }
338  if (reader.readInt32BE()) {
339  diag.emplace_back(DiagLevel::Warning, "Predefined 32-bit integer (hdlr atom) ins't set to 0.", context);
340  }
341  uint64 handlerType = reader.readUInt64BE();
342  if (/*(((handlerType & 0xFFFFFFFF00000000) >> 32) != 0x6D647461) && */ (handlerType != 0x6d6469726170706c)) {
343  diag.emplace_back(DiagLevel::Warning, "Handler type (value in hdlr atom) is unknown. Trying to parse meta information anyhow.", context);
344  }
345  m_version = ConversionUtilities::numberToString(versionByte);
346  } else {
347  m_version.clear();
348  }
349  try {
350  subAtom = metaAtom.childById(Mp4AtomIds::ItunesList, diag);
351  } catch (const Failure &) {
352  diag.emplace_back(DiagLevel::Critical, "Unable to parse child atoms of meta atom (stores hdlr and ilst atoms).", context);
353  }
354  if (subAtom) {
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  } else {
365  diag.emplace_back(DiagLevel::Warning, "No ilst atom found (stores attached meta information).", context);
366  throw NoDataFoundException();
367  }
368 }
369 
380 Mp4TagMaker Mp4Tag::prepareMaking(Diagnostics &diag)
381 {
382  return Mp4TagMaker(*this, diag);
383 }
384 
391 void Mp4Tag::make(ostream &stream, Diagnostics &diag)
392 {
393  prepareMaking(diag).make(stream, diag);
394 }
395 
407 Mp4TagMaker::Mp4TagMaker(Mp4Tag &tag, Diagnostics &diag)
408  : m_tag(tag)
409  ,
410  // meta head, hdlr atom
411  m_metaSize(8 + 37)
412  ,
413  // ilst head
414  m_ilstSize(8)
415  ,
416  // ensure there only one genre atom is written (prefer genre as string)
417  m_omitPreDefinedGenre(m_tag.fields().count(m_tag.hasField(Mp4TagAtomIds::Genre)))
418 {
419  m_maker.reserve(m_tag.fields().size());
420  for (auto &field : m_tag.fields()) {
421  if (!field.second.value().isEmpty() && (!m_omitPreDefinedGenre || field.first != Mp4TagAtomIds::PreDefinedGenre)) {
422  try {
423  m_maker.emplace_back(field.second.prepareMaking(diag));
424  m_ilstSize += m_maker.back().requiredSize();
425  } catch (const Failure &) {
426  }
427  }
428  }
429  if (m_ilstSize != 8) {
430  m_metaSize += m_ilstSize;
431  }
432 }
433 
441 void Mp4TagMaker::make(ostream &stream, Diagnostics &diag)
442 {
443  // write meta head
444  BinaryWriter writer(&stream);
445  writer.writeUInt32BE(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(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
FieldMapBasedTagTraits< Mp4Tag >::FieldType::IdentifierType IdentifierType
Definition: fieldbasedtag.h:36
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
bool matches(const Mp4TagField &field) const
Returns whether the current parameter match the specified field.
Definition: mp4tag.h:49
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
uint64 totalSize() const
Returns the total size of the element.
const char * name
name parameter
Definition: mp4tag.h:23
bool isEmpty() const
Returns an indication whether an value is assigned.
Definition: tagvalue.h:367
Contains utility classes helping to read and write streams.
ContainerType & container()
Returns the related container.
FieldMapBasedTagTraits< Mp4Tag >::FieldType FieldType
Definition: fieldbasedtag.h:35
void setMean(const std::string &mean)
Sets the "mean" for the "extended" field.
Definition: mp4tagfield.h:168
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:331
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
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:22