Tag Parser 12.4.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;
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;
186 return Rating;
187 default:
188 return 0;
189 }
190 // do not forget to extend Mp4Tag::internallyGetKnownField() and Mp4TagField::appropriateRawDataType() as well
191}
192
193KnownField Mp4Tag::internallyGetKnownField(const IdentifierType &id) const
194{
195 using namespace Mp4TagAtomIds;
196 switch (id) {
197 case Album:
198 return KnownField::Album;
199 case Artist:
200 return KnownField::Artist;
201 case Comment:
202 return KnownField::Comment;
203 case Year:
205 case Title:
206 return KnownField::Title;
207 case PreDefinedGenre:
208 case Genre:
209 return KnownField::Genre;
210 case TrackPosition:
212 case DiskPosition:
214 case Composer:
216 case Encoder:
217 return KnownField::Encoder;
218 case EncodedBy:
220 case Bpm:
221 return KnownField::Bpm;
222 case Cover:
223 return KnownField::Cover;
224 case Grouping:
226 case Description:
228 case Lyrics:
229 return KnownField::Lyrics;
230 case RecordLabel:
232 case Performers:
234 case Lyricist:
236 case AlbumArtist:
238 case Copyright:
240 case Conductor:
242 case Director:
244 case Publisher:
246 case SoundEngineer:
248 case Producer:
252 case ArtDirector:
254 case Arranger:
256 case StoreDescription:
260 case Rating:
262 default:
263 return KnownField::Invalid;
264 }
265 // do not forget to extend Mp4Tag::internallyGetFieldId() and Mp4TagField::appropriateRawDataType() as well
266}
267
271void Mp4Tag::internallyGetValuesFromField(const Mp4Tag::FieldType &field, std::vector<const TagValue *> &values) const
272{
273 if (!field.value().isEmpty()) {
274 values.emplace_back(&field.value());
275 }
276 for (const auto &value : field.additionalData()) {
277 if (!value.value.isEmpty()) {
278 values.emplace_back(&value.value);
279 }
280 }
281}
282
306
307bool Mp4Tag::setValues(KnownField field, const std::vector<TagValue> &values)
308{
309 if (const auto extendedId = Mp4ExtendedFieldId(field)) {
310 auto valuesIterator = values.cbegin();
311 auto range = fields().equal_range(Mp4TagAtomIds::Extended);
312 for (; valuesIterator != values.cend() && range.first != range.second;) {
313 if (!valuesIterator->isEmpty()) {
314 auto &extendedField = range.first->second;
315 if (extendedId.matches(extendedField) && (!extendedId.updateOnly || !extendedField.value().isEmpty())) {
316 extendedField.clearValue();
317 extendedField.setValue(*valuesIterator);
318 // note: Not sure which extended tag fields support multiple data atoms and which don't. Let's simply use
319 // only one data atom per extended field here and get rid of any possibly assigned additional data
320 // atoms.
321 ++valuesIterator;
322 }
323 ++range.first;
324 } else {
325 ++valuesIterator;
326 }
327 }
328 for (; valuesIterator != values.cend(); ++valuesIterator) {
329 if (valuesIterator->isEmpty()) {
330 fields().emplace(std::piecewise_construct, std::forward_as_tuple(Mp4TagAtomIds::Extended),
331 std::forward_as_tuple(extendedId.mean, extendedId.name, *valuesIterator));
332 }
333 }
334 for (; range.first != range.second; ++range.first) {
335 range.first->second.clearValue();
336 }
337 }
339}
340
348bool Mp4Tag::setValue(std::string_view mean, std::string_view name, const TagValue &value)
349{
350 auto range = fields().equal_range(Mp4TagAtomIds::Extended);
351 for (auto i = range.first; i != range.second; ++i) {
352 if (i->second.mean() == mean && i->second.name() == name) {
353 i->second.setValue(value);
354 return true;
355 }
356 }
357 fields().insert(make_pair(Mp4TagAtomIds::Extended, FieldType(mean, name, value)));
358 return true;
359}
360
370
378void Mp4Tag::parse(Mp4Atom &metaAtom, Diagnostics &diag)
379{
380 static const string context("parsing MP4 tag");
381 m_size = metaAtom.totalSize();
382 istream &stream = metaAtom.container().stream();
383 BinaryReader &reader = metaAtom.container().reader();
384 if (metaAtom.totalSize() > numeric_limits<std::uint32_t>::max()) {
385 diag.emplace_back(DiagLevel::Critical, "Can't handle such big \"meta\" atoms.", context);
387 }
388 Mp4Atom *subAtom = nullptr;
389 try {
391 } catch (const Failure &) {
392 diag.emplace_back(DiagLevel::Critical, "Unable to parse child atoms of meta atom (stores hdlr and ilst atoms).", context);
393 }
394 if (subAtom) {
395 stream.seekg(static_cast<streamoff>(subAtom->startOffset() + subAtom->headerSize()));
396 int versionByte = reader.readByte();
397 if (versionByte != 0) {
398 diag.emplace_back(DiagLevel::Warning, "Version is unknown.", context);
399 }
400 if (reader.readUInt24BE()) {
401 diag.emplace_back(DiagLevel::Warning, "Flags (hdlr atom) aren't set to 0.", context);
402 }
403 if (reader.readInt32BE()) {
404 diag.emplace_back(DiagLevel::Warning, "Predefined 32-bit integer (hdlr atom) isn't set to 0.", context);
405 }
406 std::uint64_t handlerType = reader.readUInt64BE();
407 if (/*(((handlerType & 0xFFFFFFFF00000000) >> 32) != 0x6D647461) && */ (handlerType != 0x6d6469726170706c)) {
408 diag.emplace_back(DiagLevel::Warning, "Handler type (value in hdlr atom) is unknown. Trying to parse meta information anyhow.", context);
409 }
410 m_version = numberToString(versionByte);
411 } else {
412 m_version.clear();
413 }
414 try {
415 subAtom = metaAtom.childById(Mp4AtomIds::ItunesList, diag);
416 } catch (const Failure &) {
417 diag.emplace_back(DiagLevel::Critical, "Unable to parse child atoms of meta atom (stores hdlr and ilst atoms).", context);
418 }
419 if (!subAtom) {
420 diag.emplace_back(DiagLevel::Warning, "No ilst atom found (stores attached meta information).", context);
421 throw NoDataFoundException();
422 }
423 for (auto *child = subAtom->firstChild(); child; child = child->nextSibling()) {
424 Mp4TagField tagField;
425 try {
426 child->parse(diag);
427 tagField.reparse(*child, diag);
428 fields().emplace(child->id(), std::move(tagField));
429 } catch (const Failure &) {
430 }
431 }
432}
433
445{
446 return Mp4TagMaker(*this, diag);
447}
448
455void Mp4Tag::make(ostream &stream, Diagnostics &diag)
456{
457 prepareMaking(diag).make(stream, diag);
458}
459
471Mp4TagMaker::Mp4TagMaker(Mp4Tag &tag, Diagnostics &diag)
472 : m_tag(tag)
473 ,
474 // meta head, hdlr atom
475 m_metaSize(8 + 37)
476 ,
477 // ilst head
478 m_ilstSize(8)
479 ,
480 // ensure there only one genre atom is written (prefer genre as string)
481 m_omitPreDefinedGenre(m_tag.fields().count(m_tag.hasField(Mp4TagAtomIds::Genre)))
482{
483 m_maker.reserve(m_tag.fields().size());
484 for (auto &field : m_tag.fields()) {
485 if (!field.second.value().isEmpty() && (!m_omitPreDefinedGenre || field.first != Mp4TagAtomIds::PreDefinedGenre)) {
486 try {
487 m_ilstSize += m_maker.emplace_back(field.second.prepareMaking(diag)).requiredSize();
488 } catch (const Failure &) {
489 }
490 }
491 }
492 if (m_ilstSize != 8) {
493 m_metaSize += m_ilstSize;
494 }
495 if (m_metaSize >= numeric_limits<std::uint32_t>::max()) {
496 diag.emplace_back(DiagLevel::Critical, "Making such big tags is not implemented.", "making MP4 tag");
497 throw NotImplementedException();
498 }
499}
500
508void Mp4TagMaker::make(ostream &stream, Diagnostics &diag)
509{
510 // write meta head
511 BinaryWriter writer(&stream);
512 writer.writeUInt32BE(static_cast<std::uint32_t>(m_metaSize));
513 writer.writeUInt32BE(Mp4AtomIds::Meta);
514 // write hdlr atom
515 static const std::uint8_t hdlrData[37] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x68, 0x64, 0x6C, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00,
516 0x00, 0x00, 0x00, 0x6D, 0x64, 0x69, 0x72, 0x61, 0x70, 0x70, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
517 stream.write(reinterpret_cast<const char *>(hdlrData), sizeof(hdlrData));
518 if (m_ilstSize != 8) {
519 // write ilst head
520 writer.writeUInt32BE(static_cast<std::uint32_t>(m_ilstSize));
521 writer.writeUInt32BE(Mp4AtomIds::ItunesList);
522 // write fields
523 for (auto &maker : m_maker) {
524 maker.make(stream);
525 }
526 } else {
527 // no fields to be written -> no ilst to be written
528 diag.emplace_back(DiagLevel::Warning, "Tag is empty.", "making MP4 tag");
529 }
530}
531
532} // 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 * 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.
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:508
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:361
bool setValue(KnownField field, const TagValue &value) override
Assigns the given value to the specified field.
Definition mp4tag.cpp:283
IdentifierType internallyGetFieldId(KnownField field) const
Definition mp4tag.cpp:119
Mp4TagMaker prepareMaking(Diagnostics &diag)
Prepares making.
Definition mp4tag.cpp:444
bool setValues(KnownField field, const std::vector< TagValue > &values) override
Assigns the given values to the field with the specified id.
Definition mp4tag.cpp:307
void parse(Mp4Atom &metaAtom, Diagnostics &diag)
Parses tag information from the specified metaAtom.
Definition mp4tag.cpp:378
void make(std::ostream &stream, Diagnostics &diag)
Writes tag information to the specified stream.
Definition mp4tag.cpp:455
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:193
void internallyGetValuesFromField(const FieldType &field, std::vector< const TagValue * > &values) const
Adds values from additional data atoms as well.
Definition mp4tag.cpp:271
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:216
std::uint64_t m_size
Definition tag.h:217
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.
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