Tag Parser  9.4.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::RecordDate:
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  // do not forget to extend Mp4Tag::internallyGetKnownField() and Mp4TagField::appropriateRawDataType() as well
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::RecordDate;
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  case AlbumArtist:
215  default:
216  return KnownField::Invalid;
217  }
218  // do not forget to extend Mp4Tag::internallyGetFieldId() and Mp4TagField::appropriateRawDataType() as well
219 }
220 
221 bool Mp4Tag::setValue(KnownField field, const TagValue &value)
222 {
223  switch (field) {
224  case KnownField::Genre:
225  switch (value.type()) {
226  case TagDataType::StandardGenreIndex:
227  fields().erase(Mp4TagAtomIds::Genre);
229  default:
230  fields().erase(Mp4TagAtomIds::PreDefinedGenre);
232  }
233  case KnownField::EncoderSettings:
236  if (!this->value(Mp4TagExtendedMeanIds::iTunes, Mp4TagExtendedNameIds::label).isEmpty()) {
238  }
239  [[fallthrough]];
240  default:
241  return FieldMapBasedTag<Mp4Tag>::setValue(field, value);
242  }
243 }
244 
245 bool Mp4Tag::setValues(KnownField field, const std::vector<TagValue> &values)
246 {
247  const Mp4ExtendedFieldId extendedId(field);
248  if (extendedId) {
249  auto valuesIterator = values.cbegin();
250  auto range = fields().equal_range(Mp4TagAtomIds::Extended);
251  for (; valuesIterator != values.cend() && range.first != range.second;) {
252  if (!valuesIterator->isEmpty()) {
253  if (extendedId.matches(range.first->second) && (!extendedId.updateOnly || !range.first->second.value().isEmpty())) {
254  range.first->second.setValue(*valuesIterator);
255  ++valuesIterator;
256  }
257  ++range.first;
258  } else {
259  ++valuesIterator;
260  }
261  }
262  for (; valuesIterator != values.cend(); ++valuesIterator) {
263  Mp4TagField tagField(Mp4TagAtomIds::Extended, *valuesIterator);
264  tagField.setMean(extendedId.mean);
265  tagField.setName(extendedId.name);
266  fields().insert(std::make_pair(Mp4TagAtomIds::Extended, move(tagField)));
267  }
268  for (; range.first != range.second; ++range.first) {
269  range.first->second.setValue(TagValue());
270  }
271  }
272  return FieldMapBasedTag<Mp4Tag>::setValues(field, values);
273 }
274 
282 bool Mp4Tag::setValue(const char *mean, const char *name, const TagValue &value)
283 {
284  auto range = fields().equal_range(Mp4TagAtomIds::Extended);
285  for (auto i = range.first; i != range.second; ++i) {
286  if (i->second.mean() == mean && i->second.name() == name) {
287  i->second.setValue(value);
288  return true;
289  }
290  }
291  fields().insert(make_pair(Mp4TagAtomIds::Extended, FieldType(mean, name, value)));
292  return true;
293 }
294 
295 bool Mp4Tag::hasField(KnownField field) const
296 {
297  switch (field) {
298  case KnownField::Genre:
300  default:
302  }
303 }
304 
312 void Mp4Tag::parse(Mp4Atom &metaAtom, Diagnostics &diag)
313 {
314  static const string context("parsing MP4 tag");
315  istream &stream = metaAtom.container().stream();
316  BinaryReader &reader = metaAtom.container().reader();
317  if (metaAtom.totalSize() > numeric_limits<std::uint32_t>::max()) {
318  diag.emplace_back(DiagLevel::Critical, "Can't handle such big \"meta\" atoms.", context);
319  throw NotImplementedException();
320  }
321  m_size = static_cast<std::uint32_t>(metaAtom.totalSize());
322  Mp4Atom *subAtom = nullptr;
323  try {
324  metaAtom.childById(Mp4AtomIds::HandlerReference, diag);
325  } catch (const Failure &) {
326  diag.emplace_back(DiagLevel::Critical, "Unable to parse child atoms of meta atom (stores hdlr and ilst atoms).", context);
327  }
328  if (subAtom) {
329  stream.seekg(static_cast<streamoff>(subAtom->startOffset() + subAtom->headerSize()));
330  int versionByte = reader.readByte();
331  if (versionByte != 0) {
332  diag.emplace_back(DiagLevel::Warning, "Version is unknown.", context);
333  }
334  if (reader.readUInt24BE()) {
335  diag.emplace_back(DiagLevel::Warning, "Flags (hdlr atom) aren't set to 0.", context);
336  }
337  if (reader.readInt32BE()) {
338  diag.emplace_back(DiagLevel::Warning, "Predefined 32-bit integer (hdlr atom) ins't set to 0.", context);
339  }
340  std::uint64_t handlerType = reader.readUInt64BE();
341  if (/*(((handlerType & 0xFFFFFFFF00000000) >> 32) != 0x6D647461) && */ (handlerType != 0x6d6469726170706c)) {
342  diag.emplace_back(DiagLevel::Warning, "Handler type (value in hdlr atom) is unknown. Trying to parse meta information anyhow.", context);
343  }
344  m_version = numberToString(versionByte);
345  } else {
346  m_version.clear();
347  }
348  try {
349  subAtom = metaAtom.childById(Mp4AtomIds::ItunesList, diag);
350  } catch (const Failure &) {
351  diag.emplace_back(DiagLevel::Critical, "Unable to parse child atoms of meta atom (stores hdlr and ilst atoms).", context);
352  }
353  if (!subAtom) {
354  diag.emplace_back(DiagLevel::Warning, "No ilst atom found (stores attached meta information).", context);
355  throw NoDataFoundException();
356  }
357  for (auto *child = subAtom->firstChild(); child; child = child->nextSibling()) {
358  Mp4TagField tagField;
359  try {
360  child->parse(diag);
361  tagField.reparse(*child, diag);
362  fields().emplace(child->id(), move(tagField));
363  } catch (const Failure &) {
364  }
365  }
366 }
367 
378 Mp4TagMaker Mp4Tag::prepareMaking(Diagnostics &diag)
379 {
380  return Mp4TagMaker(*this, diag);
381 }
382 
389 void Mp4Tag::make(ostream &stream, Diagnostics &diag)
390 {
391  prepareMaking(diag).make(stream, diag);
392 }
393 
405 Mp4TagMaker::Mp4TagMaker(Mp4Tag &tag, Diagnostics &diag)
406  : m_tag(tag)
407  ,
408  // meta head, hdlr atom
409  m_metaSize(8 + 37)
410  ,
411  // ilst head
412  m_ilstSize(8)
413  ,
414  // ensure there only one genre atom is written (prefer genre as string)
415  m_omitPreDefinedGenre(m_tag.fields().count(m_tag.hasField(Mp4TagAtomIds::Genre)))
416 {
417  m_maker.reserve(m_tag.fields().size());
418  for (auto &field : m_tag.fields()) {
419  if (!field.second.value().isEmpty() && (!m_omitPreDefinedGenre || field.first != Mp4TagAtomIds::PreDefinedGenre)) {
420  try {
421  m_maker.emplace_back(field.second.prepareMaking(diag));
422  m_ilstSize += m_maker.back().requiredSize();
423  } catch (const Failure &) {
424  }
425  }
426  }
427  if (m_ilstSize != 8) {
428  m_metaSize += m_ilstSize;
429  }
430  if (m_metaSize >= numeric_limits<std::uint32_t>::max()) {
431  diag.emplace_back(DiagLevel::Critical, "Making such big tags is not implemented.", "making MP4 tag");
432  throw NotImplementedException();
433  }
434 }
435 
443 void Mp4TagMaker::make(ostream &stream, Diagnostics &diag)
444 {
445  // write meta head
446  BinaryWriter writer(&stream);
447  writer.writeUInt32BE(static_cast<std::uint32_t>(m_metaSize));
448  writer.writeUInt32BE(Mp4AtomIds::Meta);
449  // write hdlr atom
450  static const std::uint8_t hdlrData[37] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x68, 0x64, 0x6C, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00,
451  0x00, 0x00, 0x00, 0x6D, 0x64, 0x69, 0x72, 0x61, 0x70, 0x70, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
452  stream.write(reinterpret_cast<const char *>(hdlrData), sizeof(hdlrData));
453  if (m_ilstSize != 8) {
454  // write ilst head
455  writer.writeUInt32BE(static_cast<std::uint32_t>(m_ilstSize));
456  writer.writeUInt32BE(Mp4AtomIds::ItunesList);
457  // write fields
458  for (auto &maker : m_maker) {
459  maker.make(stream);
460  }
461  } else {
462  // no fields to be written -> no ilst to be written
463  diag.emplace_back(DiagLevel::Warning, "Tag is empty.", "making MP4 tag");
464  }
465 }
466 
467 } // namespace TagParser
TagParser::Mp4TagAtomIds::Extended
@ Extended
Definition: mp4ids.h:99
mp4ids.h
TagParser::Mp4TagAtomIds::Album
@ Album
Definition: mp4ids.h:86
mp4atom.h
TagParser::RawDataType::Utf8
@ Utf8
Definition: mp4tagfield.h:21
TagParser::Mp4TagAtomIds::Lyrics
@ Lyrics
Definition: mp4ids.h:105
TagParser::Mp4TagAtomIds::Encoder
@ 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
@ Cover
Definition: mp4ids.h:94
TagParser::Mp4TagExtendedNameIds::label
const char * label
Definition: mp4ids.cpp:32
TagParser::GenericFileElement::startOffset
std::uint64_t startOffset() const
Returns the start offset in the related stream.
Definition: genericfileelement.h:261
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:443
TagParser::Diagnostics
The Diagnostics class is a container for DiagMessage.
Definition: diagnostics.h:156
TagParser::Mp4TagAtomIds::TrackPosition
@ TrackPosition
Definition: mp4ids.h:116
TagParser::Mp4TagExtendedMeanIds::iTunes
const char * iTunes
Definition: mp4ids.cpp:24
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
@ Title
Definition: matroskaid.h:54
TagParser::GenericFileElement::firstChild
ImplementationType * firstChild()
Returns the first child of the element.
Definition: genericfileelement.h:460
TagParser::Mp4TagMaker
The Mp4TagMaker class helps writing MP4 tags.
Definition: mp4tag.h:54
TagParser::Mp4TagAtomIds::AlbumArtist
@ AlbumArtist
Definition: mp4ids.h:87
TagParser::Mp4TagAtomIds::Year
@ Year
Definition: mp4ids.h:122
TagParser::Mp4TagAtomIds::PreDefinedGenre
@ 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 no or an empty value is assigned.
Definition: tagvalue.h:464
TagParser::Mp4TagAtomIds::Lyricist
@ Lyricist
Definition: mp4ids.h:104
TagParser::Mp4TagExtendedNameIds::cdec
const char * cdec
Definition: mp4ids.cpp:31
TagParser::Mp4AtomIds::Meta
@ Meta
Definition: mp4ids.h:37
TagParser::Mp4AtomIds::HandlerReference
@ HandlerReference
Definition: mp4ids.h:29
TagParser::Mp4TagAtomIds::Description
@ Description
Definition: mp4ids.h:95
CppUtilities
Definition: abstractcontainer.h:15
TagParser::Mp4TagAtomIds::Genre
@ Genre
Definition: mp4ids.h:101
TagParser::GenericFileElement::headerSize
std::uint32_t headerSize() const
Returns the header size of the element in byte.
Definition: genericfileelement.h:304
TagParser::Mp4TagAtomIds::Rating
@ 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
@ 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.
Definition: tagvalue.h:75
TagParser::Mp4TagAtomIds::Bpm
@ 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
@ 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:405
mp4container.h
TagParser::Mp4TagAtomIds::RecordLabel
@ RecordLabel
Definition: mp4ids.h:114
TagParser::Mp4Atom
The Mp4Atom class helps to parse MP4 files.
Definition: mp4atom.h:38
TagParser::Mp4TagAtomIds::DiskPosition
@ DiskPosition
Definition: mp4ids.h:96
TagParser::Mp4TagAtomIds::Composer
@ 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
@ Comment
Definition: mp4ids.h:91
TagParser::Mp4AtomIds::ItunesList
@ ItunesList
Definition: mp4ids.h:31
TagParser::Mp4TagAtomIds::Performers
@ 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