Tag Parser  9.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 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  mean = nullptr;
40  name = nullptr;
41  updateOnly = false;
42  }
43 }
44 
50 bool Mp4Tag::canEncodingBeUsed(TagTextEncoding encoding) const
51 {
52  switch (encoding) {
54  return true;
55  case TagTextEncoding::Utf16BigEndian:
56  return true;
57  default:
58  return false;
59  }
60 }
61 
62 const TagValue &Mp4Tag::value(KnownField field) const
63 {
64  switch (field) {
65  case KnownField::Genre: {
67  if (!value.isEmpty()) {
68  return value;
69  } else {
71  }
72  }
73  case KnownField::EncoderSettings:
77  if (!value.isEmpty()) {
78  return value;
79  } else {
81  }
82  }
83  default:
84  return FieldMapBasedTag<Mp4Tag>::value(field);
85  }
86 }
87 
88 std::vector<const TagValue *> Mp4Tag::values(KnownField field) const
89 {
90  auto values = FieldMapBasedTag<Mp4Tag>::values(field);
91  const Mp4ExtendedFieldId extendedId(field);
92  if (extendedId) {
93  auto range = fields().equal_range(Mp4TagAtomIds::Extended);
94  for (auto i = range.first; i != range.second; ++i) {
95  if (extendedId.matches(i->second)) {
96  values.emplace_back(&i->second.value());
97  }
98  }
99  }
100  return values;
101 }
102 
108 const TagValue &Mp4Tag::value(const char *mean, const char *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::Year:
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 }
167 
168 KnownField Mp4Tag::internallyGetKnownField(const IdentifierType &id) const
169 {
170  using namespace Mp4TagAtomIds;
171  switch (id) {
172  case Album:
173  return KnownField::Album;
174  case Artist:
175  return KnownField::Artist;
176  case Comment:
177  return KnownField::Comment;
178  case Year:
179  return KnownField::Year;
180  case Title:
181  return KnownField::Title;
182  case PreDefinedGenre:
183  case Genre:
184  return KnownField::Genre;
185  case TrackPosition:
187  case DiskPosition:
189  case Composer:
190  return KnownField::Composer;
191  case Encoder:
192  return KnownField::Encoder;
193  case Bpm:
194  return KnownField::Bpm;
195  case Cover:
196  return KnownField::Cover;
197  case Rating:
198  return KnownField::Rating;
199  case Grouping:
200  return KnownField::Grouping;
201  case Description:
203  case Lyrics:
204  return KnownField::Lyrics;
205  case RecordLabel:
207  case Performers:
208  return KnownField::Performers;
209  case Lyricist:
210  return KnownField::Lyricist;
211  case AlbumArtist:
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 
292 bool Mp4Tag::hasField(KnownField field) const
293 {
294  switch (field) {
295  case KnownField::Genre:
297  default:
299  }
300 }
301 
309 void Mp4Tag::parse(Mp4Atom &metaAtom, Diagnostics &diag)
310 {
311  static const string context("parsing MP4 tag");
312  istream &stream = metaAtom.container().stream();
313  BinaryReader &reader = metaAtom.container().reader();
314  if (metaAtom.totalSize() > numeric_limits<std::uint32_t>::max()) {
315  diag.emplace_back(DiagLevel::Critical, "Can't handle such big \"meta\" atoms.", context);
316  throw NotImplementedException();
317  }
318  m_size = static_cast<std::uint32_t>(metaAtom.totalSize());
319  Mp4Atom *subAtom = nullptr;
320  try {
321  metaAtom.childById(Mp4AtomIds::HandlerReference, diag);
322  } catch (const Failure &) {
323  diag.emplace_back(DiagLevel::Critical, "Unable to parse child atoms of meta atom (stores hdlr and ilst atoms).", context);
324  }
325  if (subAtom) {
326  stream.seekg(static_cast<streamoff>(subAtom->startOffset() + subAtom->headerSize()));
327  int versionByte = reader.readByte();
328  if (versionByte != 0) {
329  diag.emplace_back(DiagLevel::Warning, "Version is unknown.", context);
330  }
331  if (reader.readUInt24BE()) {
332  diag.emplace_back(DiagLevel::Warning, "Flags (hdlr atom) aren't set to 0.", context);
333  }
334  if (reader.readInt32BE()) {
335  diag.emplace_back(DiagLevel::Warning, "Predefined 32-bit integer (hdlr atom) ins't set to 0.", context);
336  }
337  std::uint64_t handlerType = reader.readUInt64BE();
338  if (/*(((handlerType & 0xFFFFFFFF00000000) >> 32) != 0x6D647461) && */ (handlerType != 0x6d6469726170706c)) {
339  diag.emplace_back(DiagLevel::Warning, "Handler type (value in hdlr atom) is unknown. Trying to parse meta information anyhow.", context);
340  }
341  m_version = numberToString(versionByte);
342  } else {
343  m_version.clear();
344  }
345  try {
346  subAtom = metaAtom.childById(Mp4AtomIds::ItunesList, diag);
347  } catch (const Failure &) {
348  diag.emplace_back(DiagLevel::Critical, "Unable to parse child atoms of meta atom (stores hdlr and ilst atoms).", context);
349  }
350  if (!subAtom) {
351  diag.emplace_back(DiagLevel::Warning, "No ilst atom found (stores attached meta information).", context);
352  throw NoDataFoundException();
353  }
354  for (auto *child = subAtom->firstChild(); child; child = child->nextSibling()) {
355  Mp4TagField tagField;
356  try {
357  child->parse(diag);
358  tagField.reparse(*child, diag);
359  fields().emplace(child->id(), move(tagField));
360  } catch (const Failure &) {
361  }
362  }
363 }
364 
375 Mp4TagMaker Mp4Tag::prepareMaking(Diagnostics &diag)
376 {
377  return Mp4TagMaker(*this, diag);
378 }
379 
386 void Mp4Tag::make(ostream &stream, Diagnostics &diag)
387 {
388  prepareMaking(diag).make(stream, diag);
389 }
390 
402 Mp4TagMaker::Mp4TagMaker(Mp4Tag &tag, Diagnostics &diag)
403  : m_tag(tag)
404  ,
405  // meta head, hdlr atom
406  m_metaSize(8 + 37)
407  ,
408  // ilst head
409  m_ilstSize(8)
410  ,
411  // ensure there only one genre atom is written (prefer genre as string)
412  m_omitPreDefinedGenre(m_tag.fields().count(m_tag.hasField(Mp4TagAtomIds::Genre)))
413 {
414  m_maker.reserve(m_tag.fields().size());
415  for (auto &field : m_tag.fields()) {
416  if (!field.second.value().isEmpty() && (!m_omitPreDefinedGenre || field.first != Mp4TagAtomIds::PreDefinedGenre)) {
417  try {
418  m_maker.emplace_back(field.second.prepareMaking(diag));
419  m_ilstSize += m_maker.back().requiredSize();
420  } catch (const Failure &) {
421  }
422  }
423  }
424  if (m_ilstSize != 8) {
425  m_metaSize += m_ilstSize;
426  }
427  if (m_metaSize >= numeric_limits<std::uint32_t>::max()) {
428  diag.emplace_back(DiagLevel::Critical, "Making such big tags is not implemented.", "making MP4 tag");
429  throw NotImplementedException();
430  }
431 }
432 
440 void Mp4TagMaker::make(ostream &stream, Diagnostics &diag)
441 {
442  // write meta head
443  BinaryWriter writer(&stream);
444  writer.writeUInt32BE(static_cast<std::uint32_t>(m_metaSize));
445  writer.writeUInt32BE(Mp4AtomIds::Meta);
446  // write hdlr atom
447  static const std::uint8_t hdlrData[37] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x68, 0x64, 0x6C, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00,
448  0x00, 0x00, 0x00, 0x6D, 0x64, 0x69, 0x72, 0x61, 0x70, 0x70, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
449  stream.write(reinterpret_cast<const char *>(hdlrData), sizeof(hdlrData));
450  if (m_ilstSize != 8) {
451  // write ilst head
452  writer.writeUInt32BE(static_cast<std::uint32_t>(m_ilstSize));
453  writer.writeUInt32BE(Mp4AtomIds::ItunesList);
454  // write fields
455  for (auto &maker : m_maker) {
456  maker.make(stream);
457  }
458  } else {
459  // no fields to be written -> no ilst to be written
460  diag.emplace_back(DiagLevel::Warning, "Tag is empty.", "making MP4 tag");
461  }
462 }
463 
464 } // namespace TagParser
TagParser::Mp4TagAtomIds::Extended
Definition: mp4ids.h:99
mp4ids.h
TagParser::Mp4TagAtomIds::Album
Definition: mp4ids.h:86
mp4atom.h
TagParser::RawDataType::Utf8
Definition: mp4tagfield.h:21
TagParser::Mp4TagAtomIds::Lyrics
Definition: mp4ids.h:105
TagParser::Mp4TagAtomIds::Encoder
Definition: mp4ids.h:97
TagParser::FieldMapBasedTag< Mp4Tag >::IdentifierType
typename FieldMapBasedTagTraits< Mp4Tag >::FieldType::IdentifierType IdentifierType
Definition: fieldbasedtag.h:36
TagParser::TagTextEncoding
TagTextEncoding
Specifies the text encoding.
Definition: tagvalue.h:25
TagParser::Mp4ExtendedFieldId::updateOnly
bool updateOnly
Whether only existing fields should be updated but no new extended field should be created.
Definition: mp4tag.h:25
TagParser::Mp4TagAtomIds::Cover
Definition: mp4ids.h:94
TagParser::Mp4TagExtendedNameIds::label
const char * label
Definition: mp4ids.cpp:31
TagParser::DiagLevel::Warning
TagParser::Mp4TagMaker::make
void make(std::ostream &stream, Diagnostics &diag)
Saves the tag (specified when constructing the object) to the specified stream.
Definition: mp4tag.cpp:440
TagParser::Diagnostics
The Diagnostics class is a container for DiagMessage.
Definition: diagnostics.h:156
TagParser::Mp4TagAtomIds::TrackPosition
Definition: mp4ids.h:116
TagParser::Mp4TagExtendedMeanIds::iTunes
const char * iTunes
Definition: mp4ids.cpp:23
TagParser::FieldMapBasedTag< Mp4Tag >::FieldType
typename FieldMapBasedTagTraits< Mp4Tag >::FieldType FieldType
Definition: fieldbasedtag.h:35
TagParser
Contains all classes and functions of the TagInfo library.
Definition: aaccodebook.h:10
TagParser::Mp4ExtendedFieldId::name
const char * name
name parameter
Definition: mp4tag.h:23
TagParser::Mp4ExtendedFieldId
The Mp4ExtendedFieldId specifies parameter for an extended field denoted via Mp4TagAtomIds::Extended.
Definition: mp4tag.h:13
TagParser::Mp4ExtendedFieldId::mean
const char * mean
mean parameter, usually Mp4TagExtendedMeanIds::iTunes
Definition: mp4tag.h:21
TagParser::MatroskaIds::Title
Definition: matroskaid.h:54
TagParser::Mp4TagMaker
The Mp4TagMaker class helps writing MP4 tags.
Definition: mp4tag.h:54
TagParser::Mp4TagAtomIds::AlbumArtist
Definition: mp4ids.h:87
TagParser::Mp4TagAtomIds::Year
Definition: mp4ids.h:122
TagParser::Mp4TagAtomIds::PreDefinedGenre
Definition: mp4ids.h:110
TagParser::Failure
The class inherits from std::exception and serves as base class for exceptions thrown by the elements...
Definition: exceptions.h:11
TagParser::TagValue::isEmpty
bool isEmpty() const
Returns whether an empty value is assigned.
Definition: tagvalue.h:449
TagParser::Mp4TagAtomIds::Lyricist
Definition: mp4ids.h:104
TagParser::Mp4TagExtendedNameIds::cdec
const char * cdec
Definition: mp4ids.cpp:30
TagParser::Mp4AtomIds::Meta
Definition: mp4ids.h:37
TagParser::Mp4AtomIds::HandlerReference
Definition: mp4ids.h:29
TagParser::DiagLevel::Critical
TagParser::Mp4TagAtomIds::Description
Definition: mp4ids.h:95
CppUtilities
Definition: abstractcontainer.h:15
TagParser::Mp4TagAtomIds::Genre
Definition: mp4ids.h:101
TagParser::Mp4TagAtomIds::Rating
Definition: mp4ids.h:113
TagParser::Mp4TagField::setName
void setName(const std::string &name)
Sets the "name" for the "extended" field.
Definition: mp4tagfield.h:153
TagParser::GenericFileElement::childById
ImplementationType * childById(const IdentifierType &id, Diagnostics &diag)
Returns the first child with the specified id.
Definition: genericfileelement.h:602
TagParser::KnownField
KnownField
Specifies the field.
Definition: tag.h:42
TagParser::Mp4TagField::reparse
void reparse(Mp4Atom &ilstChild, Diagnostics &diag)
Parses field information from the specified Mp4Atom.
Definition: mp4tagfield.cpp:73
TagParser::Mp4TagAtomIds::Grouping
Definition: mp4ids.h:102
TagParser::FieldMapBasedTag
The FieldMapBasedTag provides a generic implementation of Tag which stores the tag fields using std::...
Definition: fieldbasedtag.h:31
TagParser::NoDataFoundException
The exception that is thrown when the data to be parsed holds no parsable information (e....
Definition: exceptions.h:18
TagParser::TagValue
The TagValue class wraps values of different types. It is meant to be assigned to a tag field.
Definition: tagvalue.h:75
TagParser::Mp4TagAtomIds::Bpm
Definition: mp4ids.h:89
TagParser::Mp4Tag
Implementation of TagParser::Tag for the MP4 container.
Definition: mp4tag.h:97
TagParser::Mp4TagField::setMean
void setMean(const std::string &mean)
Sets the "mean" for the "extended" field.
Definition: mp4tagfield.h:169
TagParser::Mp4ExtendedFieldId::matches
bool matches(const Mp4TagField &field) const
Returns whether the current parameter match the specified field.
Definition: mp4tag.h:49
TagParser::Mp4TagAtomIds::Artist
Definition: mp4ids.h:88
TagParser::GenericFileElement::container
ContainerType & container()
Returns the related container.
Definition: genericfileelement.h:220
TagParser::TagValue::type
TagDataType type() const
Returns the type of the assigned value.
Definition: tagvalue.h:395
mp4container.h
TagParser::Mp4TagAtomIds::RecordLabel
Definition: mp4ids.h:114
TagParser::Mp4Atom
The Mp4Atom class helps to parse MP4 files.
Definition: mp4atom.h:38
TagParser::Mp4TagAtomIds::DiskPosition
Definition: mp4ids.h:96
TagParser::Mp4TagAtomIds::Composer
Definition: mp4ids.h:92
TagParser::Mp4TagField
The Mp4TagField class is used by Mp4Tag to store the fields.
Definition: mp4tagfield.h:98
TagParser::Mp4TagAtomIds::Comment
Definition: mp4ids.h:91
TagParser::Mp4AtomIds::ItunesList
Definition: mp4ids.h:31
TagParser::Mp4TagAtomIds::Performers
Definition: mp4ids.h:107
TagParser::GenericFileElement::totalSize
std::uint64_t totalSize() const
Returns the total size of the element.
Definition: genericfileelement.h:343
TagParser::NotImplementedException
This exception is thrown when the an operation is invoked that has not been implemented yet.
Definition: exceptions.h:60
mp4tag.h