Tag Parser  9.1.2
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  // do not forget to extend Mp4Tag::internallyGetKnownField() and Mp4TagField::appropriateRawDataType() as well
167 }
168 
169 KnownField Mp4Tag::internallyGetKnownField(const IdentifierType &id) const
170 {
171  using namespace Mp4TagAtomIds;
172  switch (id) {
173  case Album:
174  return KnownField::Album;
175  case Artist:
176  return KnownField::Artist;
177  case Comment:
178  return KnownField::Comment;
179  case Year:
180  return KnownField::Year;
181  case Title:
182  return KnownField::Title;
183  case PreDefinedGenre:
184  case Genre:
185  return KnownField::Genre;
186  case TrackPosition:
188  case DiskPosition:
190  case Composer:
191  return KnownField::Composer;
192  case Encoder:
193  return KnownField::Encoder;
194  case Bpm:
195  return KnownField::Bpm;
196  case Cover:
197  return KnownField::Cover;
198  case Rating:
199  return KnownField::Rating;
200  case Grouping:
201  return KnownField::Grouping;
202  case Description:
204  case Lyrics:
205  return KnownField::Lyrics;
206  case RecordLabel:
208  case Performers:
209  return KnownField::Performers;
210  case Lyricist:
211  return KnownField::Lyricist;
212  case AlbumArtist:
214  default:
215  return KnownField::Invalid;
216  }
217  // do not forget to extend Mp4Tag::internallyGetFieldId() and Mp4TagField::appropriateRawDataType() as well
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 
294 bool Mp4Tag::hasField(KnownField field) const
295 {
296  switch (field) {
297  case KnownField::Genre:
299  default:
301  }
302 }
303 
311 void Mp4Tag::parse(Mp4Atom &metaAtom, Diagnostics &diag)
312 {
313  static const string context("parsing MP4 tag");
314  istream &stream = metaAtom.container().stream();
315  BinaryReader &reader = metaAtom.container().reader();
316  if (metaAtom.totalSize() > numeric_limits<std::uint32_t>::max()) {
317  diag.emplace_back(DiagLevel::Critical, "Can't handle such big \"meta\" atoms.", context);
318  throw NotImplementedException();
319  }
320  m_size = static_cast<std::uint32_t>(metaAtom.totalSize());
321  Mp4Atom *subAtom = nullptr;
322  try {
323  metaAtom.childById(Mp4AtomIds::HandlerReference, diag);
324  } catch (const Failure &) {
325  diag.emplace_back(DiagLevel::Critical, "Unable to parse child atoms of meta atom (stores hdlr and ilst atoms).", context);
326  }
327  if (subAtom) {
328  stream.seekg(static_cast<streamoff>(subAtom->startOffset() + subAtom->headerSize()));
329  int versionByte = reader.readByte();
330  if (versionByte != 0) {
331  diag.emplace_back(DiagLevel::Warning, "Version is unknown.", context);
332  }
333  if (reader.readUInt24BE()) {
334  diag.emplace_back(DiagLevel::Warning, "Flags (hdlr atom) aren't set to 0.", context);
335  }
336  if (reader.readInt32BE()) {
337  diag.emplace_back(DiagLevel::Warning, "Predefined 32-bit integer (hdlr atom) ins't set to 0.", context);
338  }
339  std::uint64_t handlerType = reader.readUInt64BE();
340  if (/*(((handlerType & 0xFFFFFFFF00000000) >> 32) != 0x6D647461) && */ (handlerType != 0x6d6469726170706c)) {
341  diag.emplace_back(DiagLevel::Warning, "Handler type (value in hdlr atom) is unknown. Trying to parse meta information anyhow.", context);
342  }
343  m_version = numberToString(versionByte);
344  } else {
345  m_version.clear();
346  }
347  try {
348  subAtom = metaAtom.childById(Mp4AtomIds::ItunesList, diag);
349  } catch (const Failure &) {
350  diag.emplace_back(DiagLevel::Critical, "Unable to parse child atoms of meta atom (stores hdlr and ilst atoms).", context);
351  }
352  if (!subAtom) {
353  diag.emplace_back(DiagLevel::Warning, "No ilst atom found (stores attached meta information).", context);
354  throw NoDataFoundException();
355  }
356  for (auto *child = subAtom->firstChild(); child; child = child->nextSibling()) {
357  Mp4TagField tagField;
358  try {
359  child->parse(diag);
360  tagField.reparse(*child, diag);
361  fields().emplace(child->id(), move(tagField));
362  } catch (const Failure &) {
363  }
364  }
365 }
366 
377 Mp4TagMaker Mp4Tag::prepareMaking(Diagnostics &diag)
378 {
379  return Mp4TagMaker(*this, diag);
380 }
381 
388 void Mp4Tag::make(ostream &stream, Diagnostics &diag)
389 {
390  prepareMaking(diag).make(stream, diag);
391 }
392 
404 Mp4TagMaker::Mp4TagMaker(Mp4Tag &tag, Diagnostics &diag)
405  : m_tag(tag)
406  ,
407  // meta head, hdlr atom
408  m_metaSize(8 + 37)
409  ,
410  // ilst head
411  m_ilstSize(8)
412  ,
413  // ensure there only one genre atom is written (prefer genre as string)
414  m_omitPreDefinedGenre(m_tag.fields().count(m_tag.hasField(Mp4TagAtomIds::Genre)))
415 {
416  m_maker.reserve(m_tag.fields().size());
417  for (auto &field : m_tag.fields()) {
418  if (!field.second.value().isEmpty() && (!m_omitPreDefinedGenre || field.first != Mp4TagAtomIds::PreDefinedGenre)) {
419  try {
420  m_maker.emplace_back(field.second.prepareMaking(diag));
421  m_ilstSize += m_maker.back().requiredSize();
422  } catch (const Failure &) {
423  }
424  }
425  }
426  if (m_ilstSize != 8) {
427  m_metaSize += m_ilstSize;
428  }
429  if (m_metaSize >= numeric_limits<std::uint32_t>::max()) {
430  diag.emplace_back(DiagLevel::Critical, "Making such big tags is not implemented.", "making MP4 tag");
431  throw NotImplementedException();
432  }
433 }
434 
442 void Mp4TagMaker::make(ostream &stream, Diagnostics &diag)
443 {
444  // write meta head
445  BinaryWriter writer(&stream);
446  writer.writeUInt32BE(static_cast<std::uint32_t>(m_metaSize));
447  writer.writeUInt32BE(Mp4AtomIds::Meta);
448  // write hdlr atom
449  static const std::uint8_t hdlrData[37] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x68, 0x64, 0x6C, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00,
450  0x00, 0x00, 0x00, 0x6D, 0x64, 0x69, 0x72, 0x61, 0x70, 0x70, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
451  stream.write(reinterpret_cast<const char *>(hdlrData), sizeof(hdlrData));
452  if (m_ilstSize != 8) {
453  // write ilst head
454  writer.writeUInt32BE(static_cast<std::uint32_t>(m_ilstSize));
455  writer.writeUInt32BE(Mp4AtomIds::ItunesList);
456  // write fields
457  for (auto &maker : m_maker) {
458  maker.make(stream);
459  }
460  } else {
461  // no fields to be written -> no ilst to be written
462  diag.emplace_back(DiagLevel::Warning, "Tag is empty.", "making MP4 tag");
463  }
464 }
465 
466 } // 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:31
TagParser::DiagLevel::Warning
@ Warning
TagParser::GenericFileElement::container
ContainerType & container()
Returns the related container.
Definition: genericfileelement.h:220
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:442
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: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
@ Title
Definition: matroskaid.h:54
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 an empty value is assigned.
Definition: tagvalue.h:449
TagParser::Mp4TagAtomIds::Lyricist
@ Lyricist
Definition: mp4ids.h:104
TagParser::Mp4TagExtendedNameIds::cdec
const char * cdec
Definition: mp4ids.cpp:30
TagParser::Mp4AtomIds::Meta
@ Meta
Definition: mp4ids.h:37
TagParser::Mp4AtomIds::HandlerReference
@ HandlerReference
Definition: mp4ids.h:29
TagParser::DiagLevel::Critical
@ Critical
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::GenericFileElement::firstChild
ImplementationType * firstChild()
Returns the first child of the element.
Definition: genericfileelement.h:460
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
@ 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::TagValue::type
TagDataType type() const
Returns the type of the assigned value.
Definition: tagvalue.h:395
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