Tag Parser 12.5.0
C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
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
11using namespace std;
12using namespace CppUtilities;
13
14namespace TagParser {
15
20
26{
27 switch (field) {
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 }
40}
41
46
48{
49 switch (encoding) {
51 return true;
53 return true;
54 default:
55 return false;
56 }
57}
58
60{
61 switch (field) {
62 case KnownField::Genre: {
64 if (!value.isEmpty()) {
65 return value;
66 } else {
68 }
69 }
74 if (!value.isEmpty()) {
75 return value;
76 } else {
78 }
79 }
80 default:
82 }
83}
84
85std::vector<const TagValue *> Mp4Tag::values(KnownField field) const
86{
88 if (const auto extendedId = Mp4ExtendedFieldId(field)) {
89 auto range = fields().equal_range(Mp4TagAtomIds::Extended);
90 for (auto i = range.first; i != range.second; ++i) {
91 const auto &extendedField = i->second;
92 if (extendedId.matches(extendedField)) {
93 values.emplace_back(&extendedField.value());
94 for (const auto &additionalData : extendedField.additionalData()) {
95 values.emplace_back(&additionalData.value);
96 }
97 }
98 }
99 }
100 return values;
101}
102
108const TagValue &Mp4Tag::value(std::string_view mean, std::string_view 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
120{
121 using namespace Mp4TagAtomIds;
122 switch (field) {
124 return Album;
126 return Artist;
128 return Comment;
130 return Year;
132 return Title;
134 return Genre;
136 return TrackPosition;
138 return DiskPosition;
140 return Composer;
142 return Encoder;
144 return EncodedBy;
145 case KnownField::Bpm:
146 return Bpm;
148 return Cover;
150 return Grouping;
152 return Description;
154 return Lyrics;
156 return RecordLabel;
158 return Performers;
160 return Lyricist;
162 return AlbumArtist;
164 return Copyright;
166 return Conductor;
168 return Director;
170 return Publisher;
172 return SoundEngineer;
174 return Producer;
176 return ExecutiveProducer;
178 return ArtDirector;
180 return Arranger;
182 return StoreDescription;
184 return LongDescription;
188 return Rating;
189 default:
190 return 0;
191 }
192 // do not forget to extend Mp4Tag::internallyGetKnownField() and Mp4TagField::appropriateRawDataType() as well
193}
194
196{
197 using namespace Mp4TagAtomIds;
198 switch (id) {
199 case Album:
200 return KnownField::Album;
201 case Artist:
202 return KnownField::Artist;
203 case Comment:
204 return KnownField::Comment;
205 case Year:
207 case Title:
208 return KnownField::Title;
209 case PreDefinedGenre:
210 case Genre:
211 return KnownField::Genre;
212 case TrackPosition:
214 case DiskPosition:
216 case Composer:
218 case Encoder:
219 return KnownField::Encoder;
220 case EncodedBy:
222 case Bpm:
223 return KnownField::Bpm;
224 case Cover:
225 return KnownField::Cover;
226 case Grouping:
228 case Description:
230 case Lyrics:
231 return KnownField::Lyrics;
232 case RecordLabel:
234 case Performers:
236 case Lyricist:
238 case AlbumArtist:
240 case Copyright:
242 case Conductor:
244 case Director:
246 case Publisher:
248 case SoundEngineer:
250 case Producer:
254 case ArtDirector:
256 case Arranger:
258 case StoreDescription:
260 case LongDescription:
264 case Rating:
266 default:
267 return KnownField::Invalid;
268 }
269 // do not forget to extend Mp4Tag::internallyGetFieldId() and Mp4TagField::appropriateRawDataType() as well
270}
271
275void Mp4Tag::internallyGetValuesFromField(const Mp4Tag::FieldType &field, std::vector<const TagValue *> &values) const
276{
277 if (!field.value().isEmpty()) {
278 values.emplace_back(&field.value());
279 }
280 for (const auto &value : field.additionalData()) {
281 if (!value.value.isEmpty()) {
282 values.emplace_back(&value.value);
283 }
284 }
285}
286
310
311bool Mp4Tag::setValues(KnownField field, const std::vector<TagValue> &values)
312{
313 if (const auto extendedId = Mp4ExtendedFieldId(field)) {
314 auto valuesIterator = values.cbegin();
315 auto range = fields().equal_range(Mp4TagAtomIds::Extended);
316 for (; valuesIterator != values.cend() && range.first != range.second;) {
317 if (!valuesIterator->isEmpty()) {
318 auto &extendedField = range.first->second;
319 if (extendedId.matches(extendedField) && (!extendedId.updateOnly || !extendedField.value().isEmpty())) {
320 extendedField.clearValue();
321 extendedField.setValue(*valuesIterator);
322 // note: Not sure which extended tag fields support multiple data atoms and which don't. Let's simply use
323 // only one data atom per extended field here and get rid of any possibly assigned additional data
324 // atoms.
325 ++valuesIterator;
326 }
327 ++range.first;
328 } else {
329 ++valuesIterator;
330 }
331 }
332 for (; valuesIterator != values.cend(); ++valuesIterator) {
333 if (valuesIterator->isEmpty()) {
334 fields().emplace(std::piecewise_construct, std::forward_as_tuple(Mp4TagAtomIds::Extended),
335 std::forward_as_tuple(extendedId.mean, extendedId.name, *valuesIterator));
336 }
337 }
338 for (; range.first != range.second; ++range.first) {
339 range.first->second.clearValue();
340 }
341 }
343}
344
352bool Mp4Tag::setValue(std::string_view mean, std::string_view name, const TagValue &value)
353{
354 auto range = fields().equal_range(Mp4TagAtomIds::Extended);
355 for (auto i = range.first; i != range.second; ++i) {
356 if (i->second.mean() == mean && i->second.name() == name) {
357 i->second.setValue(value);
358 return true;
359 }
360 }
361 fields().insert(make_pair(Mp4TagAtomIds::Extended, FieldType(mean, name, value)));
362 return true;
363}
364
374
382void Mp4Tag::parse(Mp4Atom &metaAtom, Diagnostics &diag)
383{
384 static const string context("parsing MP4 tag");
385 m_size = metaAtom.totalSize();
386 istream &stream = metaAtom.container().stream();
387 BinaryReader &reader = metaAtom.container().reader();
388 if (metaAtom.totalSize() > numeric_limits<std::uint32_t>::max()) {
389 diag.emplace_back(DiagLevel::Critical, "Can't handle such big \"meta\" atoms.", context);
391 }
392 Mp4Atom *subAtom = nullptr;
393 try {
395 } catch (const Failure &) {
396 diag.emplace_back(DiagLevel::Critical, "Unable to parse child atoms of meta atom (stores hdlr and ilst atoms).", context);
397 }
398 if (subAtom) {
399 stream.seekg(static_cast<streamoff>(subAtom->startOffset() + subAtom->headerSize()));
400 int versionByte = reader.readByte();
401 if (versionByte != 0) {
402 diag.emplace_back(DiagLevel::Warning, "Version is unknown.", context);
403 }
404 if (reader.readUInt24BE()) {
405 diag.emplace_back(DiagLevel::Warning, "Flags (hdlr atom) aren't set to 0.", context);
406 }
407 if (reader.readInt32BE()) {
408 diag.emplace_back(DiagLevel::Warning, "Predefined 32-bit integer (hdlr atom) isn't set to 0.", context);
409 }
410 std::uint64_t handlerType = reader.readUInt64BE();
411 if (/*(((handlerType & 0xFFFFFFFF00000000) >> 32) != 0x6D647461) && */ (handlerType != 0x6d6469726170706c)) {
412 diag.emplace_back(DiagLevel::Warning, "Handler type (value in hdlr atom) is unknown. Trying to parse meta information anyhow.", context);
413 }
414 m_version = numberToString(versionByte);
415 } else {
416 m_version.clear();
417 }
418 try {
419 subAtom = metaAtom.childById(Mp4AtomIds::ItunesList, diag);
420 } catch (const Failure &) {
421 diag.emplace_back(DiagLevel::Critical, "Unable to parse child atoms of meta atom (stores hdlr and ilst atoms).", context);
422 }
423 if (!subAtom) {
424 diag.emplace_back(DiagLevel::Warning, "No ilst atom found (stores attached meta information).", context);
425 throw NoDataFoundException();
426 }
427 for (auto *child = subAtom->firstChild(); child; child = child->nextSibling()) {
428 Mp4TagField tagField;
429 try {
430 child->parse(diag);
431 tagField.reparse(*child, diag);
432 fields().emplace(child->id(), std::move(tagField));
433 } catch (const Failure &) {
434 }
435 }
436}
437
449{
450 return Mp4TagMaker(*this, diag);
451}
452
459void Mp4Tag::make(ostream &stream, Diagnostics &diag)
460{
461 prepareMaking(diag).make(stream, diag);
462}
463
470
475Mp4TagMaker::Mp4TagMaker(Mp4Tag &tag, Diagnostics &diag)
476 : m_tag(tag)
477 ,
478 // meta head, hdlr atom
479 m_metaSize(8 + 37)
480 ,
481 // ilst head
482 m_ilstSize(8)
483 ,
484 // ensure there only one genre atom is written (prefer genre as string)
485 m_omitPreDefinedGenre(m_tag.fields().count(m_tag.hasField(Mp4TagAtomIds::Genre)))
486{
487 m_maker.reserve(m_tag.fields().size());
488 for (auto &field : m_tag.fields()) {
489 if (!field.second.value().isEmpty() && (!m_omitPreDefinedGenre || field.first != Mp4TagAtomIds::PreDefinedGenre)) {
490 try {
491 m_ilstSize += m_maker.emplace_back(field.second.prepareMaking(diag)).requiredSize();
492 } catch (const Failure &) {
493 }
494 }
495 }
496 if (m_ilstSize != 8) {
497 m_metaSize += m_ilstSize;
498 }
499 if (m_metaSize >= numeric_limits<std::uint32_t>::max()) {
500 diag.emplace_back(DiagLevel::Critical, "Making such big tags is not implemented.", "making MP4 tag");
502 }
503}
504
512void Mp4TagMaker::make(ostream &stream, Diagnostics &diag)
513{
514 // write meta head
515 BinaryWriter writer(&stream);
516 writer.writeUInt32BE(static_cast<std::uint32_t>(m_metaSize));
517 writer.writeUInt32BE(Mp4AtomIds::Meta);
518 // write hdlr atom
519 static const std::uint8_t hdlrData[37] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x68, 0x64, 0x6C, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00,
520 0x00, 0x00, 0x00, 0x6D, 0x64, 0x69, 0x72, 0x61, 0x70, 0x70, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
521 stream.write(reinterpret_cast<const char *>(hdlrData), sizeof(hdlrData));
522 if (m_ilstSize != 8) {
523 // write ilst head
524 writer.writeUInt32BE(static_cast<std::uint32_t>(m_ilstSize));
525 writer.writeUInt32BE(Mp4AtomIds::ItunesList);
526 // write fields
527 for (auto &maker : m_maker) {
528 maker.make(stream);
529 }
530 } else {
531 // no fields to be written -> no ilst to be written
532 diag.emplace_back(DiagLevel::Warning, "Tag is empty.", "making MP4 tag");
533 }
534}
535
536} // namespace TagParser
The Diagnostics class is a container for DiagMessage.
The class inherits from std::exception and serves as base class for exceptions thrown by the elements...
Definition exceptions.h:11
bool setValue(const IdentifierType &id, const TagValue &value)
Assigns the given value to the field with the specified id.
bool hasField(KnownField field) const
Returns an indication whether the specified field is present.
typename FieldMapBasedTagTraits< Mp4Tag >::FieldType::IdentifierType IdentifierType
const TagValue & value(const IdentifierType &id) const
Returns the value of the field with the specified id.
const std::multimap< IdentifierType, FieldType, Compare > & fields() const
typename FieldMapBasedTagTraits< Mp4Tag >::FieldType FieldType
std::vector< const TagValue * > values(const IdentifierType &id) const
Returns the values of the field with the specified id.
bool setValues(const IdentifierType &id, const std::vector< TagValue > &values)
Assigns the given values to the field with the specified id.
std::uint64_t startOffset() const
Returns the start offset in the related stream.
std::uint32_t headerSize() const
Returns the header size of the element in byte.
ImplementationType * childById(const IdentifierType &id, Diagnostics &diag)
Returns the first child with the specified id.
ImplementationType * nextSibling()
Returns the next sibling of the element.
ImplementationType * firstChild()
Returns the first child of the element.
std::uint64_t totalSize() const
Returns the total size of the element.
ContainerType & container()
Returns the related container.
The Mp4Atom class helps to parse MP4 files.
Definition mp4atom.h:38
The Mp4TagField class is used by Mp4Tag to store the fields.
void reparse(Mp4Atom &ilstChild, Diagnostics &diag)
Parses field information from the specified Mp4Atom.
The Mp4TagMaker class helps writing MP4 tags.
Definition mp4tag.h:54
void make(std::ostream &stream, Diagnostics &diag)
Saves the tag (specified when constructing the object) to the specified stream.
Definition mp4tag.cpp:512
Implementation of TagParser::Tag for the MP4 container.
Definition mp4tag.h:97
std::vector< const TagValue * > values(KnownField field) const override
Returns the values of the specified field.
Definition mp4tag.cpp:85
const TagValue & value(KnownField value) const override
Returns the value of the specified field.
Definition mp4tag.cpp:59
bool hasField(KnownField value) const override
Returns an indication whether the specified field is present.
Definition mp4tag.cpp:365
bool setValue(KnownField field, const TagValue &value) override
Assigns the given value to the specified field.
Definition mp4tag.cpp:287
IdentifierType internallyGetFieldId(KnownField field) const
Definition mp4tag.cpp:119
Mp4TagMaker prepareMaking(Diagnostics &diag)
Prepares making.
Definition mp4tag.cpp:448
bool setValues(KnownField field, const std::vector< TagValue > &values) override
Assigns the given values to the field with the specified id.
Definition mp4tag.cpp:311
void parse(Mp4Atom &metaAtom, Diagnostics &diag)
Parses tag information from the specified metaAtom.
Definition mp4tag.cpp:382
void make(std::ostream &stream, Diagnostics &diag)
Writes tag information to the specified stream.
Definition mp4tag.cpp:459
bool canEncodingBeUsed(TagTextEncoding encoding) const override
Returns an indication whether the specified encoding can be used to provide string values for the tag...
Definition mp4tag.cpp:47
KnownField internallyGetKnownField(const IdentifierType &id) const
Definition mp4tag.cpp:195
void internallyGetValuesFromField(const FieldType &field, std::vector< const TagValue * > &values) const
Adds values from additional data atoms as well.
Definition mp4tag.cpp:275
The exception that is thrown when the data to be parsed holds no parsable information (e....
Definition exceptions.h:18
This exception is thrown when the an operation is invoked that has not been implemented yet.
Definition exceptions.h:60
The TagValue class wraps values of different types.
Definition tagvalue.h:147
static const TagValue & empty()
Returns a default-constructed TagValue where TagValue::isNull() and TagValue::isEmpty() both return t...
std::string m_version
Definition tag.h:216
std::uint64_t m_size
Definition tag.h:217
Encapsulates IDs of MP4 atoms holding tag information.
Definition mp4ids.h:85
Contains all classes and functions of the TagInfo library.
Definition aaccodebook.h:10
KnownField
Specifies the field.
Definition tag.h:37
TagTextEncoding
Specifies the text encoding.
Definition tagvalue.h:29
The Mp4ExtendedFieldId specifies parameter for an extended field denoted via Mp4TagAtomIds::Extended.
Definition mp4tag.h:13
bool updateOnly
Whether only existing fields should be updated but no new extended field should be created.
Definition mp4tag.h:25
std::string_view mean
mean parameter, usually Mp4TagExtendedMeanIds::iTunes
Definition mp4tag.h:21
Mp4ExtendedFieldId(std::string_view mean, std::string_view name, bool updateOnly=false)
Constructs a new instance with the specified parameter.
Definition mp4tag.h:31
std::string_view name
name parameter
Definition mp4tag.h:23