Tag Parser  6.5.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 "./mp4container.h"
2 #include "./mp4tag.h"
3 #include "./mp4ids.h"
4 #include "./mp4atom.h"
5 
6 #include "../exceptions.h"
7 
8 #include <c++utilities/io/binarywriter.h>
9 #include <c++utilities/conversion/stringconversion.h>
10 
11 using namespace std;
12 using namespace IoUtilities;
13 using namespace ConversionUtilities;
14 
15 namespace Media {
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  } case KnownField::EncoderSettings:
72  if(!value.isEmpty()) {
73  return value;
74  } else {
76  }
77  }
78  default:
80  }
81 }
82 
83 std::vector<const TagValue *> Mp4Tag::values(KnownField field) const
84 {
85  auto values = FieldMapBasedTag<fieldType>::values(field);
86  const Mp4ExtendedFieldId extendedId(field);
87  if(extendedId) {
88  auto range = fields().equal_range(Mp4TagAtomIds::Extended);
89  for(auto i = range.first; i != range.second; ++i) {
90  if(extendedId.matches(i->second)) {
91  values.emplace_back(&i->second.value());
92  }
93  }
94  }
95  return values;
96 }
97 
103 const TagValue &Mp4Tag::value(const char *mean, const char *name) const
104 {
105  auto range = fields().equal_range(Mp4TagAtomIds::Extended);
106  for(auto i = range.first; i != range.second; ++i) {
107  if(i->second.mean() == mean && i->second.name() == name) {
108  return i->second.value();
109  }
110  }
111  return TagValue::empty();
112 }
113 
117 const TagValue &Mp4Tag::value(const string mean, const string name) const
118 {
119  return (this->*static_cast<const TagValue &(Mp4Tag::*)(const string &, const string &) const>(&Mp4Tag::value))(mean, name);
120 }
121 
122 uint32 Mp4Tag::fieldId(KnownField field) const
123 {
124  using namespace Mp4TagAtomIds;
125  switch(field) {
126  case KnownField::Album: return Album;
127  case KnownField::Artist: return Artist;
128  case KnownField::Comment: return Comment;
129  case KnownField::Year: return Year;
130  case KnownField::Title: return Title;
131  case KnownField::Genre: return Genre;
134  case KnownField::Composer: return Composer;
135  case KnownField::Encoder: return Encoder;
136  case KnownField::Bpm: return Bpm;
137  case KnownField::Cover: return Cover;
138  case KnownField::Rating: return Rating;
139  case KnownField::Grouping: return Grouping;
141  case KnownField::Lyrics: return Lyrics;
143  case KnownField::Performers: return Performers;
144  case KnownField::Lyricist: return Lyricist;
145  default: return 0;
146  }
147 }
148 
149 KnownField Mp4Tag::knownField(const uint32 &id) const
150 {
151  using namespace Mp4TagAtomIds;
152  switch(id) {
153  case Album: return KnownField::Album;
154  case Artist: return KnownField::Artist;
155  case Comment: return KnownField::Comment;
156  case Year: return KnownField::Year;
157  case Title: return KnownField::Title;
158  case PreDefinedGenre: case Genre: return KnownField::Genre;
161  case Composer: return KnownField::Composer;
162  case Encoder: return KnownField::Encoder;
163  case Bpm: return KnownField::Bpm;
164  case Cover: return KnownField::Cover;
165  case Rating: return KnownField::Rating;
166  case Grouping: return KnownField::Grouping;
168  case Lyrics: return KnownField::Lyrics;
170  case Performers: return KnownField::Performers;
171  case Lyricist: return KnownField::Lyricist;
172  default: return KnownField::Invalid;
173  }
174 }
175 
176 bool Mp4Tag::setValue(KnownField field, const TagValue &value)
177 {
178  switch(field) {
179  case KnownField::Genre:
180  switch(value.type()) {
181  case TagDataType::StandardGenreIndex:
182  fields().erase(Mp4TagAtomIds::Genre);
184  default:
185  fields().erase(Mp4TagAtomIds::PreDefinedGenre);
187  }
188  case KnownField::EncoderSettings:
191  if(!this->value(Mp4TagExtendedMeanIds::iTunes, Mp4TagExtendedNameIds::label).isEmpty()) {
193  }
194  FALLTHROUGH;
195  default:
196  return FieldMapBasedTag<fieldType>::setValue(field, value);
197  }
198 }
199 
200 bool Mp4Tag::setValues(KnownField field, const std::vector<TagValue> &values)
201 {
202  const Mp4ExtendedFieldId extendedId(field);
203  if(extendedId) {
204  auto valuesIterator = values.cbegin();
205  auto range = fields().equal_range(Mp4TagAtomIds::Extended);
206  for(; valuesIterator != values.cend() && range.first != range.second;) {
207  if(!valuesIterator->isEmpty()) {
208  if(extendedId.matches(range.first->second)
209  && (!extendedId.updateOnly || !range.first->second.value().isEmpty())) {
210  range.first->second.setValue(*valuesIterator);
211  ++valuesIterator;
212  }
213  ++range.first;
214  } else {
215  ++valuesIterator;
216  }
217  }
218  for(; valuesIterator != values.cend(); ++valuesIterator) {
219  Mp4TagField tagField(Mp4TagAtomIds::Extended, *valuesIterator);
220  tagField.setMean(extendedId.mean);
221  tagField.setName(extendedId.name);
222  fields().insert(std::make_pair(Mp4TagAtomIds::Extended, move(tagField)));
223  }
224  for(; range.first != range.second; ++range.first) {
225  range.first->second.setValue(TagValue());
226  }
227  }
228  return FieldMapBasedTag<fieldType>::setValues(field, values);
229 }
230 
238 bool Mp4Tag::setValue(const char *mean, const char *name, const TagValue &value)
239 {
240  auto range = fields().equal_range(Mp4TagAtomIds::Extended);
241  for(auto i = range.first; i != range.second; ++i) {
242  if(i->second.mean() == mean && i->second.name() == name) {
243  i->second.setValue(value);
244  return true;
245  }
246  }
247  fields().insert(make_pair(Mp4TagAtomIds::Extended, fieldType(mean, name, value)));
248  return true;
249 }
250 
254 bool Mp4Tag::setValue(const string mean, const string name, const TagValue &value)
255 {
256  return (this->*static_cast<bool(Mp4Tag::*)(const string &, const string &, const TagValue &)>(&Mp4Tag::setValue))(mean, name, value);
257 }
258 
259 bool Mp4Tag::hasField(KnownField field) const
260 {
261  switch(field) {
262  case KnownField::Genre:
265  default:
267  }
268 }
269 
277 void Mp4Tag::parse(Mp4Atom &metaAtom)
278 {
279  invalidateStatus();
280  static const string context("parsing MP4 tag");
281  istream &stream = metaAtom.container().stream();
282  BinaryReader &reader = metaAtom.container().reader();
283  m_size = metaAtom.totalSize();
284  Mp4Atom *subAtom = nullptr;
285  try {
287  } catch(const Failure &) {
288  addNotification(NotificationType::Critical, "Unable to parse child atoms of meta atom (stores hdlr and ilst atoms).", context);
289  }
290  if(subAtom) {
291  stream.seekg(subAtom->startOffset() + subAtom->headerSize());
292  int versionByte = reader.readByte();
293  if(versionByte != 0) {
294  addNotification(NotificationType::Warning, "Version is unknown.", context);
295  }
296  if(reader.readUInt24BE()) {
297  addNotification(NotificationType::Warning, "Flags (hdlr atom) aren't set to 0.", context);
298  }
299  if(reader.readInt32BE()) {
300  addNotification(NotificationType::Warning, "Predefined 32-bit integer (hdlr atom) ins't set to 0.", context);
301  }
302  uint64 handlerType = reader.readUInt64BE();
303  if(/*(((handlerType & 0xFFFFFFFF00000000) >> 32) != 0x6D647461) && */(handlerType != 0x6d6469726170706c)) {
304  addNotification(NotificationType::Warning, "Handler type (value in hdlr atom) is unknown. Trying to parse meta information anyhow.", context);
305  }
306  m_version = ConversionUtilities::numberToString(versionByte);
307  } else {
308  //addParsingNotification(NotificationType::Warning, "No hdlr atom found (handler of meta information). Trying to parse meta information anyhow.");
309  m_version.clear();
310  }
311  try {
312  subAtom = metaAtom.childById(Mp4AtomIds::ItunesList);
313  } catch(const Failure &) {
314  addNotification(NotificationType::Critical, "Unable to parse child atoms of meta atom (stores hdlr and ilst atoms).", context);
315  }
316  if(subAtom) {
317  Mp4TagField tagField;
318  for(Mp4Atom *child : *subAtom) {
319  try {
320  child->parse();
321  tagField.invalidateNotifications();
322  tagField.reparse(*child);
323  fields().insert(pair<fieldType::identifierType, fieldType>(child->id(), tagField));
324  } catch(const Failure &) {
325  }
326  addNotifications(context, *child);
327  addNotifications(context, tagField);
328  }
329  } else {
330  addNotification(NotificationType::Warning, "No ilst atom found (stores attached meta information).", context);
331  throw NoDataFoundException();
332  }
333 }
334 
345 Mp4TagMaker Mp4Tag::prepareMaking()
346 {
347  return Mp4TagMaker(*this);
348 }
349 
357 void Mp4Tag::make(ostream &stream)
358 {
359  prepareMaking().make(stream);
360 }
361 
373 Mp4TagMaker::Mp4TagMaker(Mp4Tag &tag) :
374  m_tag(tag),
375  // meta head, hdlr atom
376  m_metaSize(8 + 37),
377  // ilst head
378  m_ilstSize(8),
379  // ensure there only one genre atom is written (prefer genre as string)
380  m_omitPreDefinedGenre(m_tag.fields().count(m_tag.hasField(Mp4TagAtomIds::Genre)))
381 {
382  m_tag.invalidateStatus();
383  m_maker.reserve(m_tag.fields().size());
384  for(auto &field : m_tag.fields()) {
385  if(!field.second.value().isEmpty() &&
386  (!m_omitPreDefinedGenre || field.first != Mp4TagAtomIds::PreDefinedGenre)) {
387  try {
388  m_maker.emplace_back(field.second.prepareMaking());
389  m_ilstSize += m_maker.back().requiredSize();
390  } catch(const Failure &) {
391  // nothing to do here; notifications will be added anyways
392  }
393  m_tag.addNotifications(field.second);
394  }
395  }
396  if(m_ilstSize != 8) {
397  m_metaSize += m_ilstSize;
398  }
399 }
400 
408 void Mp4TagMaker::make(ostream &stream)
409 {
410  // write meta head
411  BinaryWriter writer(&stream);
412  writer.writeUInt32BE(m_metaSize);
413  writer.writeUInt32BE(Mp4AtomIds::Meta);
414  // write hdlr atom
415  static const byte hdlrData[37] = {
416  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x68, 0x64, 0x6C, 0x72, 0x00, 0x00,
417  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6D, 0x64, 0x69, 0x72, 0x61, 0x70, 0x70, 0x6C,
418  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
419  };
420  stream.write(reinterpret_cast<const char *>(hdlrData), sizeof(hdlrData));
421  if(m_ilstSize != 8) {
422  // write ilst head
423  writer.writeUInt32BE(m_ilstSize);
424  writer.writeUInt32BE(Mp4AtomIds::ItunesList);
425  // write fields
426  for(auto &maker : m_maker) {
427  maker.make(stream);
428  }
429  } else {
430  // no fields to be written -> no ilst to be written
431  m_tag.addNotification(NotificationType::Warning, "Tag is empty.", "making MP4 tag");
432  }
433 }
434 
435 }
implementationType * childById(const identifierType &id)
Returns the first child with the specified id.
uint64 startOffset() const
Returns the start offset in the related stream.
The TagValue class wraps values of different types.
Definition: tagvalue.h:64
The Mp4ExtendedFieldId specifies parameter for an extended field denoted via Mp4TagAtomIds::Extended...
Definition: mp4tag.h:14
bool matches(const Mp4TagField &field) const
Returns whether the current parameter match the specified field.
Definition: mp4tag.h:50
bool updateOnly
Whether only existing fields should be updated but no new extended field should be created...
Definition: mp4tag.h:27
The Mp4TagMaker class helps writing MP4 tags.
Definition: mp4tag.h:55
uint32 headerSize() const
Returns the header size of the element in byte.
bool isEmpty() const
Returns an indication whether an value is assigned.
Definition: tagvalue.h:344
void setName(const std::string &name)
Sets the "name" for the "extended" field.
Definition: mp4tagfield.h:172
void setMean(const std::string &mean)
Sets the "mean" for the "extended" field.
Definition: mp4tagfield.h:188
KnownField
Specifies the field.
Definition: tag.h:42
uint64 totalSize() const
Returns the total size of the element.
STL namespace.
void addNotification(const Notification &notification)
This method is meant to be called by the derived class to add a notification.
TagTextEncoding
Specifies the text encoding.
Definition: tagvalue.h:22
Contains utility classes helping to read and write streams.
TagDataType type() const
Returns the type of the assigned value.
Definition: tagvalue.h:308
Implementation of Media::Tag for the MP4 container.
Definition: mp4tag.h:90
The class inherits from std::exception and serves as base class for exceptions thrown by the elements...
Definition: exceptions.h:11
The FieldMapBasedTag provides a generic implementation of Tag which stores the tag fields using std::...
Definition: fieldbasedtag.h:25
void make(std::ostream &stream)
Saves the tag (specified when constructing the object) to the specified stream.
Definition: mp4tag.cpp:408
void invalidateNotifications()
Invalidates the object&#39;s notifications.
The Mp4Atom class helps to parse MP4 files.
Definition: mp4atom.h:57
const char * mean
mean parameter, usually Mp4TagExtendedMeanIds::iTunes
Definition: mp4tag.h:23
The exception that is thrown when the data to be parsed holds no parsable information.
Definition: exceptions.h:19
Contains all classes and functions of the TagInfo library.
Definition: exceptions.h:9
const char * name
name parameter
Definition: mp4tag.h:25
The Mp4TagField class is used by Mp4Tag to store the fields.
Definition: mp4tagfield.h:114
containerType & container()
Returns the related container.
void reparse(Mp4Atom &ilstChild)
Parses field information from the specified Mp4Atom.
Definition: mp4tagfield.cpp:72