Tag Parser 12.2.0
C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags
Loading...
Searching...
No Matches
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
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;
166 return Conductor;
168 return Director;
170 return Publisher;
172 return SoundEngineer;
174 return Producer;
176 return ExecutiveProducer;
178 return ArtDirector;
180 return Arranger;
181 default:
182 return 0;
183 }
184 // do not forget to extend Mp4Tag::internallyGetKnownField() and Mp4TagField::appropriateRawDataType() as well
185}
186
187KnownField Mp4Tag::internallyGetKnownField(const IdentifierType &id) const
188{
189 using namespace Mp4TagAtomIds;
190 switch (id) {
191 case Album:
192 return KnownField::Album;
193 case Artist:
194 return KnownField::Artist;
195 case Comment:
196 return KnownField::Comment;
197 case Year:
199 case Title:
200 return KnownField::Title;
201 case PreDefinedGenre:
202 case Genre:
203 return KnownField::Genre;
204 case TrackPosition:
206 case DiskPosition:
208 case Composer:
210 case Encoder:
211 return KnownField::Encoder;
212 case Bpm:
213 return KnownField::Bpm;
214 case Cover:
215 return KnownField::Cover;
216 case Rating:
218 case Grouping:
220 case Description:
222 case Lyrics:
223 return KnownField::Lyrics;
224 case RecordLabel:
226 case Performers:
228 case Lyricist:
230 case AlbumArtist:
232 case Copyright:
234 case Conductor:
236 case Director:
238 case Publisher:
240 case SoundEngineer:
242 case Producer:
246 case ArtDirector:
248 case Arranger:
250 default:
251 return KnownField::Invalid;
252 }
253 // do not forget to extend Mp4Tag::internallyGetFieldId() and Mp4TagField::appropriateRawDataType() as well
254}
255
259void Mp4Tag::internallyGetValuesFromField(const Mp4Tag::FieldType &field, std::vector<const TagValue *> &values) const
260{
261 if (!field.value().isEmpty()) {
262 values.emplace_back(&field.value());
263 }
264 for (const auto &value : field.additionalData()) {
265 if (!value.value.isEmpty()) {
266 values.emplace_back(&value.value);
267 }
268 }
269}
270
294
295bool Mp4Tag::setValues(KnownField field, const std::vector<TagValue> &values)
296{
297 if (const auto extendedId = Mp4ExtendedFieldId(field)) {
298 auto valuesIterator = values.cbegin();
299 auto range = fields().equal_range(Mp4TagAtomIds::Extended);
300 for (; valuesIterator != values.cend() && range.first != range.second;) {
301 if (!valuesIterator->isEmpty()) {
302 auto &extendedField = range.first->second;
303 if (extendedId.matches(extendedField) && (!extendedId.updateOnly || !extendedField.value().isEmpty())) {
304 extendedField.clearValue();
305 extendedField.setValue(*valuesIterator);
306 // note: Not sure which extended tag fields support multiple data atoms and which don't. Let's simply use
307 // only one data atom per extended field here and get rid of any possibly assigned additional data
308 // atoms.
309 ++valuesIterator;
310 }
311 ++range.first;
312 } else {
313 ++valuesIterator;
314 }
315 }
316 for (; valuesIterator != values.cend(); ++valuesIterator) {
317 if (valuesIterator->isEmpty()) {
318 fields().emplace(std::piecewise_construct, std::forward_as_tuple(Mp4TagAtomIds::Extended),
319 std::forward_as_tuple(extendedId.mean, extendedId.name, *valuesIterator));
320 }
321 }
322 for (; range.first != range.second; ++range.first) {
323 range.first->second.clearValue();
324 }
325 }
327}
328
336bool Mp4Tag::setValue(std::string_view mean, std::string_view name, const TagValue &value)
337{
338 auto range = fields().equal_range(Mp4TagAtomIds::Extended);
339 for (auto i = range.first; i != range.second; ++i) {
340 if (i->second.mean() == mean && i->second.name() == name) {
341 i->second.setValue(value);
342 return true;
343 }
344 }
345 fields().insert(make_pair(Mp4TagAtomIds::Extended, FieldType(mean, name, value)));
346 return true;
347}
348
358
366void Mp4Tag::parse(Mp4Atom &metaAtom, Diagnostics &diag)
367{
368 static const string context("parsing MP4 tag");
369 m_size = metaAtom.totalSize();
370 istream &stream = metaAtom.container().stream();
371 BinaryReader &reader = metaAtom.container().reader();
372 if (metaAtom.totalSize() > numeric_limits<std::uint32_t>::max()) {
373 diag.emplace_back(DiagLevel::Critical, "Can't handle such big \"meta\" atoms.", context);
375 }
376 Mp4Atom *subAtom = nullptr;
377 try {
379 } catch (const Failure &) {
380 diag.emplace_back(DiagLevel::Critical, "Unable to parse child atoms of meta atom (stores hdlr and ilst atoms).", context);
381 }
382 if (subAtom) {
383 stream.seekg(static_cast<streamoff>(subAtom->startOffset() + subAtom->headerSize()));
384 int versionByte = reader.readByte();
385 if (versionByte != 0) {
386 diag.emplace_back(DiagLevel::Warning, "Version is unknown.", context);
387 }
388 if (reader.readUInt24BE()) {
389 diag.emplace_back(DiagLevel::Warning, "Flags (hdlr atom) aren't set to 0.", context);
390 }
391 if (reader.readInt32BE()) {
392 diag.emplace_back(DiagLevel::Warning, "Predefined 32-bit integer (hdlr atom) isn't set to 0.", context);
393 }
394 std::uint64_t handlerType = reader.readUInt64BE();
395 if (/*(((handlerType & 0xFFFFFFFF00000000) >> 32) != 0x6D647461) && */ (handlerType != 0x6d6469726170706c)) {
396 diag.emplace_back(DiagLevel::Warning, "Handler type (value in hdlr atom) is unknown. Trying to parse meta information anyhow.", context);
397 }
398 m_version = numberToString(versionByte);
399 } else {
400 m_version.clear();
401 }
402 try {
403 subAtom = metaAtom.childById(Mp4AtomIds::ItunesList, diag);
404 } catch (const Failure &) {
405 diag.emplace_back(DiagLevel::Critical, "Unable to parse child atoms of meta atom (stores hdlr and ilst atoms).", context);
406 }
407 if (!subAtom) {
408 diag.emplace_back(DiagLevel::Warning, "No ilst atom found (stores attached meta information).", context);
409 throw NoDataFoundException();
410 }
411 for (auto *child = subAtom->firstChild(); child; child = child->nextSibling()) {
412 Mp4TagField tagField;
413 try {
414 child->parse(diag);
415 tagField.reparse(*child, diag);
416 fields().emplace(child->id(), std::move(tagField));
417 } catch (const Failure &) {
418 }
419 }
420}
421
433{
434 return Mp4TagMaker(*this, diag);
435}
436
443void Mp4Tag::make(ostream &stream, Diagnostics &diag)
444{
445 prepareMaking(diag).make(stream, diag);
446}
447
459Mp4TagMaker::Mp4TagMaker(Mp4Tag &tag, Diagnostics &diag)
460 : m_tag(tag)
461 ,
462 // meta head, hdlr atom
463 m_metaSize(8 + 37)
464 ,
465 // ilst head
466 m_ilstSize(8)
467 ,
468 // ensure there only one genre atom is written (prefer genre as string)
469 m_omitPreDefinedGenre(m_tag.fields().count(m_tag.hasField(Mp4TagAtomIds::Genre)))
470{
471 m_maker.reserve(m_tag.fields().size());
472 for (auto &field : m_tag.fields()) {
473 if (!field.second.value().isEmpty() && (!m_omitPreDefinedGenre || field.first != Mp4TagAtomIds::PreDefinedGenre)) {
474 try {
475 m_ilstSize += m_maker.emplace_back(field.second.prepareMaking(diag)).requiredSize();
476 } catch (const Failure &) {
477 }
478 }
479 }
480 if (m_ilstSize != 8) {
481 m_metaSize += m_ilstSize;
482 }
483 if (m_metaSize >= numeric_limits<std::uint32_t>::max()) {
484 diag.emplace_back(DiagLevel::Critical, "Making such big tags is not implemented.", "making MP4 tag");
485 throw NotImplementedException();
486 }
487}
488
496void Mp4TagMaker::make(ostream &stream, Diagnostics &diag)
497{
498 // write meta head
499 BinaryWriter writer(&stream);
500 writer.writeUInt32BE(static_cast<std::uint32_t>(m_metaSize));
501 writer.writeUInt32BE(Mp4AtomIds::Meta);
502 // write hdlr atom
503 static const std::uint8_t hdlrData[37] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x68, 0x64, 0x6C, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00,
504 0x00, 0x00, 0x00, 0x6D, 0x64, 0x69, 0x72, 0x61, 0x70, 0x70, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
505 stream.write(reinterpret_cast<const char *>(hdlrData), sizeof(hdlrData));
506 if (m_ilstSize != 8) {
507 // write ilst head
508 writer.writeUInt32BE(static_cast<std::uint32_t>(m_ilstSize));
509 writer.writeUInt32BE(Mp4AtomIds::ItunesList);
510 // write fields
511 for (auto &maker : m_maker) {
512 maker.make(stream);
513 }
514 } else {
515 // no fields to be written -> no ilst to be written
516 diag.emplace_back(DiagLevel::Warning, "Tag is empty.", "making MP4 tag");
517 }
518}
519
520} // 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...
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 * 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.
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.
void make(std::ostream &stream, Diagnostics &diag)
Saves the tag (specified when constructing the object) to the specified stream.
Definition mp4tag.cpp:496
Implementation of TagParser::Tag for the MP4 container.
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:349
bool setValue(KnownField field, const TagValue &value) override
Assigns the given value to the specified field.
Definition mp4tag.cpp:271
IdentifierType internallyGetFieldId(KnownField field) const
Definition mp4tag.cpp:119
Mp4TagMaker prepareMaking(Diagnostics &diag)
Prepares making.
Definition mp4tag.cpp:432
bool setValues(KnownField field, const std::vector< TagValue > &values) override
Assigns the given values to the field with the specified id.
Definition mp4tag.cpp:295
void parse(Mp4Atom &metaAtom, Diagnostics &diag)
Parses tag information from the specified metaAtom.
Definition mp4tag.cpp:366
void make(std::ostream &stream, Diagnostics &diag)
Writes tag information to the specified stream.
Definition mp4tag.cpp:443
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:187
void internallyGetValuesFromField(const FieldType &field, std::vector< const TagValue * > &values) const
Adds values from additional data atoms as well.
Definition mp4tag.cpp:259
The exception that is thrown when the data to be parsed holds no parsable information (e....
This exception is thrown when the an operation is invoked that has not been implemented yet.
The TagValue class wraps values of different types.
TagDataType type() const
Returns the type of the assigned value.
Definition tagvalue.h:433
static const TagValue & empty()
Returns a default-constructed TagValue where TagValue::isNull() and TagValue::isEmpty() both return t...
bool isEmpty() const
Returns whether no or an empty value is assigned.
Definition tagvalue.h:490
std::string m_version
Definition tag.h:204
std::uint64_t m_size
Definition tag.h:205
Contains all classes and functions of the TagInfo library.
Definition aaccodebook.h:10
KnownField
Specifies the field.
Definition tag.h:29
TagTextEncoding
Specifies the text encoding.
Definition tagvalue.h:29
The Mp4ExtendedFieldId specifies parameter for an extended field denoted via Mp4TagAtomIds::Extended.
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