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