Tag Parser  7.1.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  break;
36  updateOnly = true; // set record label via extended field only if extended field is already present
37  break;
38  default:
39  mean = nullptr;
40  }
41 }
42 
48 bool Mp4Tag::canEncodingBeUsed(TagTextEncoding encoding) const
49 {
50  switch (encoding) {
52  return true;
53  case TagTextEncoding::Utf16BigEndian:
54  return true;
55  default:
56  return false;
57  }
58 }
59 
60 const TagValue &Mp4Tag::value(KnownField field) const
61 {
62  switch (field) {
63  case KnownField::Genre: {
65  if (!value.isEmpty()) {
66  return value;
67  } else {
69  }
70  }
71  case KnownField::EncoderSettings:
75  if (!value.isEmpty()) {
76  return value;
77  } else {
79  }
80  }
81  default:
82  return FieldMapBasedTag<Mp4Tag>::value(field);
83  }
84 }
85 
86 std::vector<const TagValue *> Mp4Tag::values(KnownField field) const
87 {
88  auto values = FieldMapBasedTag<Mp4Tag>::values(field);
89  const Mp4ExtendedFieldId extendedId(field);
90  if (extendedId) {
91  auto range = fields().equal_range(Mp4TagAtomIds::Extended);
92  for (auto i = range.first; i != range.second; ++i) {
93  if (extendedId.matches(i->second)) {
94  values.emplace_back(&i->second.value());
95  }
96  }
97  }
98  return values;
99 }
100 
106 const TagValue &Mp4Tag::value(const char *mean, const char *name) const
107 {
108  auto range = fields().equal_range(Mp4TagAtomIds::Extended);
109  for (auto i = range.first; i != range.second; ++i) {
110  if (i->second.mean() == mean && i->second.name() == name) {
111  return i->second.value();
112  }
113  }
114  return TagValue::empty();
115 }
116 
120 const TagValue &Mp4Tag::value(const string mean, const string name) const
121 {
122  return (this->*static_cast<const TagValue &(Mp4Tag::*)(const string &, const string &)const>(&Mp4Tag::value))(mean, name);
123 }
124 
125 Mp4Tag::IdentifierType Mp4Tag::internallyGetFieldId(KnownField field) const
126 {
127  using namespace Mp4TagAtomIds;
128  switch (field) {
129  case KnownField::Album:
130  return Album;
131  case KnownField::Artist:
132  return Artist;
133  case KnownField::Comment:
134  return Comment;
135  case KnownField::Year:
136  return Year;
137  case KnownField::Title:
138  return Title;
139  case KnownField::Genre:
140  return Genre;
142  return TrackPosition;
144  return DiskPosition;
146  return Composer;
147  case KnownField::Encoder:
148  return Encoder;
149  case KnownField::Bpm:
150  return Bpm;
151  case KnownField::Cover:
152  return Cover;
153  case KnownField::Rating:
154  return Rating;
156  return Grouping;
158  return Description;
159  case KnownField::Lyrics:
160  return Lyrics;
162  return RecordLabel;
164  return Performers;
166  return Lyricist;
167  default:
168  return 0;
169  }
170 }
171 
172 KnownField Mp4Tag::internallyGetKnownField(const IdentifierType &id) const
173 {
174  using namespace Mp4TagAtomIds;
175  switch (id) {
176  case Album:
177  return KnownField::Album;
178  case Artist:
179  return KnownField::Artist;
180  case Comment:
181  return KnownField::Comment;
182  case Year:
183  return KnownField::Year;
184  case Title:
185  return KnownField::Title;
186  case PreDefinedGenre:
187  case Genre:
188  return KnownField::Genre;
189  case TrackPosition:
191  case DiskPosition:
193  case Composer:
194  return KnownField::Composer;
195  case Encoder:
196  return KnownField::Encoder;
197  case Bpm:
198  return KnownField::Bpm;
199  case Cover:
200  return KnownField::Cover;
201  case Rating:
202  return KnownField::Rating;
203  case Grouping:
204  return KnownField::Grouping;
205  case Description:
207  case Lyrics:
208  return KnownField::Lyrics;
209  case RecordLabel:
211  case Performers:
212  return KnownField::Performers;
213  case Lyricist:
214  return KnownField::Lyricist;
215  default:
216  return KnownField::Invalid;
217  }
218 }
219 
220 bool Mp4Tag::setValue(KnownField field, const TagValue &value)
221 {
222  switch (field) {
223  case KnownField::Genre:
224  switch (value.type()) {
225  case TagDataType::StandardGenreIndex:
226  fields().erase(Mp4TagAtomIds::Genre);
228  default:
229  fields().erase(Mp4TagAtomIds::PreDefinedGenre);
231  }
232  case KnownField::EncoderSettings:
235  if (!this->value(Mp4TagExtendedMeanIds::iTunes, Mp4TagExtendedNameIds::label).isEmpty()) {
237  }
238  FALLTHROUGH;
239  default:
240  return FieldMapBasedTag<Mp4Tag>::setValue(field, value);
241  }
242 }
243 
244 bool Mp4Tag::setValues(KnownField field, const std::vector<TagValue> &values)
245 {
246  const Mp4ExtendedFieldId extendedId(field);
247  if (extendedId) {
248  auto valuesIterator = values.cbegin();
249  auto range = fields().equal_range(Mp4TagAtomIds::Extended);
250  for (; valuesIterator != values.cend() && range.first != range.second;) {
251  if (!valuesIterator->isEmpty()) {
252  if (extendedId.matches(range.first->second) && (!extendedId.updateOnly || !range.first->second.value().isEmpty())) {
253  range.first->second.setValue(*valuesIterator);
254  ++valuesIterator;
255  }
256  ++range.first;
257  } else {
258  ++valuesIterator;
259  }
260  }
261  for (; valuesIterator != values.cend(); ++valuesIterator) {
262  Mp4TagField tagField(Mp4TagAtomIds::Extended, *valuesIterator);
263  tagField.setMean(extendedId.mean);
264  tagField.setName(extendedId.name);
265  fields().insert(std::make_pair(Mp4TagAtomIds::Extended, move(tagField)));
266  }
267  for (; range.first != range.second; ++range.first) {
268  range.first->second.setValue(TagValue());
269  }
270  }
271  return FieldMapBasedTag<Mp4Tag>::setValues(field, values);
272 }
273 
281 bool Mp4Tag::setValue(const char *mean, const char *name, const TagValue &value)
282 {
283  auto range = fields().equal_range(Mp4TagAtomIds::Extended);
284  for (auto i = range.first; i != range.second; ++i) {
285  if (i->second.mean() == mean && i->second.name() == name) {
286  i->second.setValue(value);
287  return true;
288  }
289  }
290  fields().insert(make_pair(Mp4TagAtomIds::Extended, FieldType(mean, name, value)));
291  return true;
292 }
293 
297 bool Mp4Tag::setValue(const string mean, const string name, const TagValue &value)
298 {
299  return (this->*static_cast<bool (Mp4Tag::*)(const string &, const string &, const TagValue &)>(&Mp4Tag::setValue))(mean, name, value);
300 }
301 
302 bool Mp4Tag::hasField(KnownField field) const
303 {
304  switch (field) {
305  case KnownField::Genre:
307  default:
309  }
310 }
311 
319 void Mp4Tag::parse(Mp4Atom &metaAtom, Diagnostics &diag)
320 {
321  static const string context("parsing MP4 tag");
322  istream &stream = metaAtom.container().stream();
323  BinaryReader &reader = metaAtom.container().reader();
324  if (metaAtom.totalSize() > numeric_limits<uint32>::max()) {
325  diag.emplace_back(DiagLevel::Critical, "Can't handle such big \"meta\" atoms.", context);
326  throw NotImplementedException();
327  }
328  m_size = static_cast<uint32>(metaAtom.totalSize());
329  Mp4Atom *subAtom = nullptr;
330  try {
331  metaAtom.childById(Mp4AtomIds::HandlerReference, diag);
332  } catch (const Failure &) {
333  diag.emplace_back(DiagLevel::Critical, "Unable to parse child atoms of meta atom (stores hdlr and ilst atoms).", context);
334  }
335  if (subAtom) {
336  stream.seekg(static_cast<streamoff>(subAtom->startOffset() + subAtom->headerSize()));
337  int versionByte = reader.readByte();
338  if (versionByte != 0) {
339  diag.emplace_back(DiagLevel::Warning, "Version is unknown.", context);
340  }
341  if (reader.readUInt24BE()) {
342  diag.emplace_back(DiagLevel::Warning, "Flags (hdlr atom) aren't set to 0.", context);
343  }
344  if (reader.readInt32BE()) {
345  diag.emplace_back(DiagLevel::Warning, "Predefined 32-bit integer (hdlr atom) ins't set to 0.", context);
346  }
347  uint64 handlerType = reader.readUInt64BE();
348  if (/*(((handlerType & 0xFFFFFFFF00000000) >> 32) != 0x6D647461) && */ (handlerType != 0x6d6469726170706c)) {
349  diag.emplace_back(DiagLevel::Warning, "Handler type (value in hdlr atom) is unknown. Trying to parse meta information anyhow.", context);
350  }
351  m_version = ConversionUtilities::numberToString(versionByte);
352  } else {
353  m_version.clear();
354  }
355  try {
356  subAtom = metaAtom.childById(Mp4AtomIds::ItunesList, diag);
357  } catch (const Failure &) {
358  diag.emplace_back(DiagLevel::Critical, "Unable to parse child atoms of meta atom (stores hdlr and ilst atoms).", context);
359  }
360  if (!subAtom) {
361  diag.emplace_back(DiagLevel::Warning, "No ilst atom found (stores attached meta information).", context);
362  throw NoDataFoundException();
363  }
364  for (auto *child = subAtom->firstChild(); child; child = child->nextSibling()) {
365  Mp4TagField tagField;
366  try {
367  child->parse(diag);
368  tagField.reparse(*child, diag);
369  fields().emplace(child->id(), move(tagField));
370  } catch (const Failure &) {
371  }
372  }
373 }
374 
385 Mp4TagMaker Mp4Tag::prepareMaking(Diagnostics &diag)
386 {
387  return Mp4TagMaker(*this, diag);
388 }
389 
396 void Mp4Tag::make(ostream &stream, Diagnostics &diag)
397 {
398  prepareMaking(diag).make(stream, diag);
399 }
400 
412 Mp4TagMaker::Mp4TagMaker(Mp4Tag &tag, Diagnostics &diag)
413  : m_tag(tag)
414  ,
415  // meta head, hdlr atom
416  m_metaSize(8 + 37)
417  ,
418  // ilst head
419  m_ilstSize(8)
420  ,
421  // ensure there only one genre atom is written (prefer genre as string)
422  m_omitPreDefinedGenre(m_tag.fields().count(m_tag.hasField(Mp4TagAtomIds::Genre)))
423 {
424  m_maker.reserve(m_tag.fields().size());
425  for (auto &field : m_tag.fields()) {
426  if (!field.second.value().isEmpty() && (!m_omitPreDefinedGenre || field.first != Mp4TagAtomIds::PreDefinedGenre)) {
427  try {
428  m_maker.emplace_back(field.second.prepareMaking(diag));
429  m_ilstSize += m_maker.back().requiredSize();
430  } catch (const Failure &) {
431  }
432  }
433  }
434  if (m_ilstSize != 8) {
435  m_metaSize += m_ilstSize;
436  }
437  if (m_metaSize >= numeric_limits<uint32>::max()) {
438  diag.emplace_back(DiagLevel::Critical, "Making such big tags is not implemented.", "making MP4 tag");
439  throw NotImplementedException();
440  }
441 }
442 
450 void Mp4TagMaker::make(ostream &stream, Diagnostics &diag)
451 {
452  // write meta head
453  BinaryWriter writer(&stream);
454  writer.writeUInt32BE(static_cast<uint32>(m_metaSize));
455  writer.writeUInt32BE(Mp4AtomIds::Meta);
456  // write hdlr atom
457  static const byte hdlrData[37] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x68, 0x64, 0x6C, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
458  0x00, 0x00, 0x6D, 0x64, 0x69, 0x72, 0x61, 0x70, 0x70, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
459  stream.write(reinterpret_cast<const char *>(hdlrData), sizeof(hdlrData));
460  if (m_ilstSize != 8) {
461  // write ilst head
462  writer.writeUInt32BE(static_cast<uint32>(m_ilstSize));
463  writer.writeUInt32BE(Mp4AtomIds::ItunesList);
464  // write fields
465  for (auto &maker : m_maker) {
466  maker.make(stream);
467  }
468  } else {
469  // no fields to be written -> no ilst to be written
470  diag.emplace_back(DiagLevel::Warning, "Tag is empty.", "making MP4 tag");
471  }
472 }
473 
474 } // namespace TagParser
FieldMapBasedTagTraits< Mp4Tag >::FieldType::IdentifierType IdentifierType
Definition: fieldbasedtag.h:36
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:450
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
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.
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:384
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
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:348
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:64
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:23
Contains all classes and functions of the TagInfo library.
Definition: aaccodebook.h:9
The Diagnostics class is a container for DiagMessage.
Definition: diagnostics.h:154