Tag Parser 11.1.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
11using namespace std;
12using namespace CppUtilities;
13
14namespace TagParser {
15
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
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;
143 case KnownField::Bpm:
144 return Bpm;
146 return Cover;
148 return Rating;
150 return Grouping;
152 return Description;
154 return Lyrics;
156 return RecordLabel;
158 return Performers;
160 return Lyricist;
162 return AlbumArtist;
164 return Copyright;
165 default:
166 return 0;
167 }
168 // do not forget to extend Mp4Tag::internallyGetKnownField() and Mp4TagField::appropriateRawDataType() as well
169}
170
171KnownField Mp4Tag::internallyGetKnownField(const IdentifierType &id) const
172{
173 using namespace Mp4TagAtomIds;
174 switch (id) {
175 case Album:
176 return KnownField::Album;
177 case Artist:
178 return KnownField::Artist;
179 case Comment:
180 return KnownField::Comment;
181 case Year:
183 case Title:
184 return KnownField::Title;
185 case PreDefinedGenre:
186 case Genre:
187 return KnownField::Genre;
188 case TrackPosition:
190 case DiskPosition:
192 case Composer:
194 case Encoder:
195 return KnownField::Encoder;
196 case Bpm:
197 return KnownField::Bpm;
198 case Cover:
199 return KnownField::Cover;
200 case Rating:
201 return KnownField::Rating;
202 case Grouping:
204 case Description:
206 case Lyrics:
207 return KnownField::Lyrics;
208 case RecordLabel:
210 case Performers:
212 case Lyricist:
214 case AlbumArtist:
216 case Copyright:
218 default:
219 return KnownField::Invalid;
220 }
221 // do not forget to extend Mp4Tag::internallyGetFieldId() and Mp4TagField::appropriateRawDataType() as well
222}
223
227void Mp4Tag::internallyGetValuesFromField(const Mp4Tag::FieldType &field, std::vector<const TagValue *> &values) const
228{
229 if (!field.value().isEmpty()) {
230 values.emplace_back(&field.value());
231 }
232 for (const auto &value : field.additionalData()) {
233 if (!value.value.isEmpty()) {
234 values.emplace_back(&value.value);
235 }
236 }
237}
238
239bool Mp4Tag::setValue(KnownField field, const TagValue &value)
240{
241 switch (field) {
243 switch (value.type()) {
247 default:
250 }
256 }
257 [[fallthrough]];
258 default:
260 }
261}
262
263bool Mp4Tag::setValues(KnownField field, const std::vector<TagValue> &values)
264{
265 if (const auto extendedId = Mp4ExtendedFieldId(field)) {
266 auto valuesIterator = values.cbegin();
267 auto range = fields().equal_range(Mp4TagAtomIds::Extended);
268 for (; valuesIterator != values.cend() && range.first != range.second;) {
269 if (!valuesIterator->isEmpty()) {
270 auto &extendedField = range.first->second;
271 if (extendedId.matches(extendedField) && (!extendedId.updateOnly || !extendedField.value().isEmpty())) {
272 extendedField.clearValue();
273 extendedField.setValue(*valuesIterator);
274 // note: Not sure which extended tag fields support multiple data atoms and which don't. Let's simply use
275 // only one data atom per extended field here and get rid of any possibly assigned additional data
276 // atoms.
277 ++valuesIterator;
278 }
279 ++range.first;
280 } else {
281 ++valuesIterator;
282 }
283 }
284 for (; valuesIterator != values.cend(); ++valuesIterator) {
285 if (valuesIterator->isEmpty()) {
286 fields().emplace(std::piecewise_construct, std::forward_as_tuple(Mp4TagAtomIds::Extended),
287 std::forward_as_tuple(extendedId.mean, extendedId.name, *valuesIterator));
288 }
289 }
290 for (; range.first != range.second; ++range.first) {
291 range.first->second.clearValue();
292 }
293 }
295}
296
304bool Mp4Tag::setValue(std::string_view mean, std::string_view name, const TagValue &value)
305{
306 auto range = fields().equal_range(Mp4TagAtomIds::Extended);
307 for (auto i = range.first; i != range.second; ++i) {
308 if (i->second.mean() == mean && i->second.name() == name) {
309 i->second.setValue(value);
310 return true;
311 }
312 }
313 fields().insert(make_pair(Mp4TagAtomIds::Extended, FieldType(mean, name, value)));
314 return true;
315}
316
318{
319 switch (field) {
322 default:
324 }
325}
326
334void Mp4Tag::parse(Mp4Atom &metaAtom, Diagnostics &diag)
335{
336 static const string context("parsing MP4 tag");
337 m_size = metaAtom.totalSize();
338 istream &stream = metaAtom.container().stream();
339 BinaryReader &reader = metaAtom.container().reader();
340 if (metaAtom.totalSize() > numeric_limits<std::uint32_t>::max()) {
341 diag.emplace_back(DiagLevel::Critical, "Can't handle such big \"meta\" atoms.", context);
343 }
344 Mp4Atom *subAtom = nullptr;
345 try {
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 stream.seekg(static_cast<streamoff>(subAtom->startOffset() + subAtom->headerSize()));
352 int versionByte = reader.readByte();
353 if (versionByte != 0) {
354 diag.emplace_back(DiagLevel::Warning, "Version is unknown.", context);
355 }
356 if (reader.readUInt24BE()) {
357 diag.emplace_back(DiagLevel::Warning, "Flags (hdlr atom) aren't set to 0.", context);
358 }
359 if (reader.readInt32BE()) {
360 diag.emplace_back(DiagLevel::Warning, "Predefined 32-bit integer (hdlr atom) isn't set to 0.", context);
361 }
362 std::uint64_t handlerType = reader.readUInt64BE();
363 if (/*(((handlerType & 0xFFFFFFFF00000000) >> 32) != 0x6D647461) && */ (handlerType != 0x6d6469726170706c)) {
364 diag.emplace_back(DiagLevel::Warning, "Handler type (value in hdlr atom) is unknown. Trying to parse meta information anyhow.", context);
365 }
366 m_version = numberToString(versionByte);
367 } else {
368 m_version.clear();
369 }
370 try {
371 subAtom = metaAtom.childById(Mp4AtomIds::ItunesList, diag);
372 } catch (const Failure &) {
373 diag.emplace_back(DiagLevel::Critical, "Unable to parse child atoms of meta atom (stores hdlr and ilst atoms).", context);
374 }
375 if (!subAtom) {
376 diag.emplace_back(DiagLevel::Warning, "No ilst atom found (stores attached meta information).", context);
377 throw NoDataFoundException();
378 }
379 for (auto *child = subAtom->firstChild(); child; child = child->nextSibling()) {
380 Mp4TagField tagField;
381 try {
382 child->parse(diag);
383 tagField.reparse(*child, diag);
384 fields().emplace(child->id(), move(tagField));
385 } catch (const Failure &) {
386 }
387 }
388}
389
401{
402 return Mp4TagMaker(*this, diag);
403}
404
411void Mp4Tag::make(ostream &stream, Diagnostics &diag)
412{
413 prepareMaking(diag).make(stream, diag);
414}
415
427Mp4TagMaker::Mp4TagMaker(Mp4Tag &tag, Diagnostics &diag)
428 : m_tag(tag)
429 ,
430 // meta head, hdlr atom
431 m_metaSize(8 + 37)
432 ,
433 // ilst head
434 m_ilstSize(8)
435 ,
436 // ensure there only one genre atom is written (prefer genre as string)
437 m_omitPreDefinedGenre(m_tag.fields().count(m_tag.hasField(Mp4TagAtomIds::Genre)))
438{
439 m_maker.reserve(m_tag.fields().size());
440 for (auto &field : m_tag.fields()) {
441 if (!field.second.value().isEmpty() && (!m_omitPreDefinedGenre || field.first != Mp4TagAtomIds::PreDefinedGenre)) {
442 try {
443 m_ilstSize += m_maker.emplace_back(field.second.prepareMaking(diag)).requiredSize();
444 } catch (const Failure &) {
445 }
446 }
447 }
448 if (m_ilstSize != 8) {
449 m_metaSize += m_ilstSize;
450 }
451 if (m_metaSize >= numeric_limits<std::uint32_t>::max()) {
452 diag.emplace_back(DiagLevel::Critical, "Making such big tags is not implemented.", "making MP4 tag");
453 throw NotImplementedException();
454 }
455}
456
464void Mp4TagMaker::make(ostream &stream, Diagnostics &diag)
465{
466 // write meta head
467 BinaryWriter writer(&stream);
468 writer.writeUInt32BE(static_cast<std::uint32_t>(m_metaSize));
469 writer.writeUInt32BE(Mp4AtomIds::Meta);
470 // write hdlr atom
471 static const std::uint8_t hdlrData[37] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x68, 0x64, 0x6C, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00,
472 0x00, 0x00, 0x00, 0x6D, 0x64, 0x69, 0x72, 0x61, 0x70, 0x70, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
473 stream.write(reinterpret_cast<const char *>(hdlrData), sizeof(hdlrData));
474 if (m_ilstSize != 8) {
475 // write ilst head
476 writer.writeUInt32BE(static_cast<std::uint32_t>(m_ilstSize));
477 writer.writeUInt32BE(Mp4AtomIds::ItunesList);
478 // write fields
479 for (auto &maker : m_maker) {
480 maker.make(stream);
481 }
482 } else {
483 // no fields to be written -> no ilst to be written
484 diag.emplace_back(DiagLevel::Warning, "Tag is empty.", "making MP4 tag");
485 }
486}
487
488} // namespace TagParser
The Diagnostics class is a container for DiagMessage.
Definition: diagnostics.h:156
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
Definition: fieldbasedtag.h:36
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
Returns the fields of the tag by providing direct access to the field map of the tag.
typename FieldMapBasedTagTraits< Mp4Tag >::FieldType FieldType
Definition: fieldbasedtag.h:35
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.
Definition: mp4tagfield.h:112
void reparse(Mp4Atom &ilstChild, Diagnostics &diag)
Parses field information from the specified Mp4Atom.
Definition: mp4tagfield.cpp:73
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:464
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:317
bool setValue(KnownField field, const TagValue &value) override
Assigns the given value to the specified field.
Definition: mp4tag.cpp:239
IdentifierType internallyGetFieldId(KnownField field) const
Definition: mp4tag.cpp:119
Mp4TagMaker prepareMaking(Diagnostics &diag)
Prepares making.
Definition: mp4tag.cpp:400
bool setValues(KnownField field, const std::vector< TagValue > &values) override
Assigns the given values to the specified field.
Definition: mp4tag.cpp:263
void parse(Mp4Atom &metaAtom, Diagnostics &diag)
Parses tag information from the specified metaAtom.
Definition: mp4tag.cpp:334
void make(std::ostream &stream, Diagnostics &diag)
Writes tag information to the specified stream.
Definition: mp4tag.cpp:411
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:171
void internallyGetValuesFromField(const FieldType &field, std::vector< const TagValue * > &values) const
Adds values from additional data atoms as well.
Definition: mp4tag.cpp:227
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:95
TagDataType type() const
Returns the type of the assigned value.
Definition: tagvalue.h:468
static const TagValue & empty()
Returns a default-constructed TagValue where TagValue::isNull() and TagValue::isEmpty() both return t...
Definition: tagvalue.cpp:958
bool isEmpty() const
Returns whether no or an empty value is assigned.
Definition: tagvalue.h:525
std::string m_version
Definition: tag.h:213
std::uint64_t m_size
Definition: tag.h:214
Contains all classes and functions of the TagInfo library.
Definition: aaccodebook.h:10
KnownField
Specifies the field.
Definition: tag.h:42
TagTextEncoding
Specifies the text encoding.
Definition: tagvalue.h:28
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