Tag Parser 12.3.1
C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags
Loading...
Searching...
No Matches
mp4tagfield.cpp
Go to the documentation of this file.
1#include "./mp4tagfield.h"
2#include "./mp4atom.h"
3#include "./mp4container.h"
4#include "./mp4ids.h"
5
6#include "../exceptions.h"
7
8#include <c++utilities/conversion/stringbuilder.h>
9#include <c++utilities/io/binaryreader.h>
10#include <c++utilities/io/binarywriter.h>
11
12#include <algorithm>
13#include <limits>
14#include <memory>
15
16using namespace std;
17using namespace CppUtilities;
18
19namespace TagParser {
20
30 : m_parsedRawDataType(RawDataType::Reserved)
31 , m_countryIndicator(0)
32 , m_langIndicator(0)
33{
34}
35
39Mp4TagField::Mp4TagField(IdentifierType id, const TagValue &value)
40 : TagField<Mp4TagField>(id, value)
41 , m_parsedRawDataType(RawDataType::Reserved)
42 , m_countryIndicator(0)
43 , m_langIndicator(0)
44{
45}
46
56Mp4TagField::Mp4TagField(std::string_view mean, std::string_view name, const TagValue &value)
57 : Mp4TagField(Mp4TagAtomIds::Extended, value)
58{
59 m_name = name;
60 m_mean = mean;
61}
62
74{
75 // prepare reparsing
76 using namespace Mp4AtomIds;
77 using namespace Mp4TagAtomIds;
78 string context("parsing MP4 tag field");
79 ilstChild.parse(diag); // ensure child has been parsed
80 setId(ilstChild.id());
81 context = "parsing MP4 tag field " + ilstChild.idToString();
82 iostream &stream = ilstChild.stream();
83 BinaryReader &reader = ilstChild.container().reader();
84 int dataAtomFound = 0, meanAtomFound = 0, nameAtomFound = 0;
85 for (Mp4Atom *dataAtom = ilstChild.firstChild(); dataAtom; dataAtom = dataAtom->nextSibling()) {
86 try {
87 dataAtom->parse(diag);
88 if (dataAtom->id() == Mp4AtomIds::Data) {
89 if (dataAtom->dataSize() < 8) {
90 diag.emplace_back(DiagLevel::Warning,
91 "Truncated child atom \"data\" in tag atom (ilst child) found. It will be ignored and discarded when applying changes.",
92 context);
93 continue;
94 }
95 auto *val = &value();
96 auto *rawDataType = &m_parsedRawDataType;
97 auto *countryIndicator = &m_countryIndicator;
98 auto *languageIndicator = &m_langIndicator;
99 if (++dataAtomFound > 1) {
100 if (dataAtomFound == 2) {
101 diag.emplace_back(DiagLevel::Warning,
102 "Multiple \"data\" child atom in tag atom (ilst child) found. It will be ignored but preserved when applying changes.",
103 context);
104 }
105 auto &additionalData = m_additionalData.emplace_back();
106 val = &additionalData.value;
107 rawDataType = &additionalData.rawDataType;
108 countryIndicator = &additionalData.countryIndicator;
109 languageIndicator = &additionalData.languageIndicator;
110 }
111 stream.seekg(static_cast<streamoff>(dataAtom->dataOffset()));
112 if (reader.readByte() != 0) {
113 diag.emplace_back(DiagLevel::Warning,
114 "The version indicator byte is not zero, the tag atom might be unsupported and hence not be parsed correctly.", context);
115 }
116 setTypeInfo(*rawDataType = reader.readUInt24BE());
117 try { // try to show warning if parsed raw data type differs from expected raw data type for this atom id
118 const vector<std::uint32_t> expectedRawDataTypes = this->expectedRawDataTypes();
119 if (find(expectedRawDataTypes.cbegin(), expectedRawDataTypes.cend(), m_parsedRawDataType) == expectedRawDataTypes.cend()) {
120 diag.emplace_back(DiagLevel::Warning, "Unexpected data type indicator found.", context);
121 }
122 } catch (const Failure &) {
123 // tag id is unknown, it is not possible to validate parsed data type
124 }
125 *countryIndicator = reader.readUInt16BE(); // FIXME: use locale within the tag value
126 *languageIndicator = reader.readUInt16BE(); // FIXME: use locale within the tag value
127 switch (*rawDataType) {
130 stream.seekg(static_cast<streamoff>(dataAtom->dataOffset() + 8));
131 val->assignText(reader.readString(dataAtom->dataSize() - 8),
133 break;
134 case RawDataType::Gif:
136 case RawDataType::Png:
137 case RawDataType::Bmp: {
138 switch (m_parsedRawDataType) {
139 case RawDataType::Gif:
140 val->setMimeType("image/gif");
141 break;
143 val->setMimeType("image/jpeg");
144 break;
145 case RawDataType::Png:
146 val->setMimeType("image/png");
147 break;
148 case RawDataType::Bmp:
149 val->setMimeType("image/bmp");
150 break;
151 default:;
152 }
153 const auto coverSize = static_cast<streamoff>(dataAtom->dataSize() - 8);
154 auto coverData = make_unique<char[]>(static_cast<size_t>(coverSize));
155 stream.read(coverData.get(), coverSize);
156 val->assignData(std::move(coverData), static_cast<size_t>(coverSize), TagDataType::Picture);
157 break;
158 }
160 int number = 0;
161 if (dataAtom->dataSize() > (8 + 4)) {
162 diag.emplace_back(DiagLevel::Warning, "Data atom stores integer of invalid size. Trying to read data anyways.", context);
163 }
164 if (dataAtom->dataSize() >= (8 + 4)) {
165 number = reader.readInt32BE();
166 } else if (dataAtom->dataSize() == (8 + 2)) {
167 number = reader.readInt16BE();
168 } else if (dataAtom->dataSize() == (8 + 1)) {
169 number = reader.readChar();
170 }
171 switch (ilstChild.id()) {
172 case PreDefinedGenre: // consider number as standard genre index
173 val->assignStandardGenreIndex(number);
174 break;
175 default:
176 val->assignInteger(number);
177 }
178 break;
179 }
181 int number = 0;
182 if (dataAtom->dataSize() > (8 + 4)) {
183 diag.emplace_back(DiagLevel::Warning, "Data atom stores integer of invalid size. Trying to read data anyways.", context);
184 }
185 if (dataAtom->dataSize() >= (8 + 4)) {
186 number = static_cast<int>(reader.readUInt32BE());
187 } else if (dataAtom->dataSize() == (8 + 2)) {
188 number = static_cast<int>(reader.readUInt16BE());
189 } else if (dataAtom->dataSize() == (8 + 1)) {
190 number = static_cast<int>(reader.readByte());
191 }
192 switch (ilstChild.id()) {
193 case PreDefinedGenre: // consider number as standard genre index
194 val->assignStandardGenreIndex(number - 1);
195 break;
196 default:
197 val->assignInteger(number);
198 }
199 break;
200 }
201 default:
202 switch (ilstChild.id()) {
203 // track number, disk number and genre have no specific data type id
204 case TrackPosition:
205 case DiskPosition: {
206 if (dataAtom->dataSize() < (8 + 6)) {
207 diag.emplace_back(DiagLevel::Warning, "Track/disk position is truncated. Trying to read data anyways.", context);
208 }
209 std::uint16_t pos = 0, total = 0;
210 if (dataAtom->dataSize() >= (8 + 4)) {
211 stream.seekg(2, ios_base::cur);
212 pos = reader.readUInt16BE();
213 }
214 if (dataAtom->dataSize() >= (8 + 6)) {
215 total = reader.readUInt16BE();
216 }
217 val->assignPosition(PositionInSet(pos, total));
218 break;
219 }
220 case PreDefinedGenre:
221 if (dataAtom->dataSize() < (8 + 2)) {
222 diag.emplace_back(DiagLevel::Warning, "Genre index is truncated.", context);
223 } else {
224 val->assignStandardGenreIndex(reader.readUInt16BE() - 1);
225 }
226 break;
227 default: // no supported data type, read raw data
228 const auto dataSize = static_cast<streamsize>(dataAtom->dataSize() - 8);
229 auto data = make_unique<char[]>(static_cast<size_t>(dataSize));
230 stream.read(data.get(), dataSize);
231 if (ilstChild.id() == Mp4TagAtomIds::Cover) {
232 val->assignData(std::move(data), static_cast<size_t>(dataSize), TagDataType::Picture);
233 } else {
234 val->assignData(std::move(data), static_cast<size_t>(dataSize), TagDataType::Undefined);
235 }
236 }
237 }
238 } else if (dataAtom->id() == Mp4AtomIds::Mean) {
239 if (dataAtom->dataSize() < 8) {
240 diag.emplace_back(DiagLevel::Warning,
241 "Truncated child atom \"mean\" in tag atom (ilst child) found. It will be ignored and discarded when applying changes.",
242 context);
243 continue;
244 }
245 if (++meanAtomFound > 1) {
246 if (meanAtomFound == 2) {
247 diag.emplace_back(DiagLevel::Warning,
248 "Tag atom contains more than one mean atom. The additional mean atoms will be ignored and discarded when applying "
249 "changes.",
250 context);
251 }
252 continue;
253 }
254 stream.seekg(static_cast<streamoff>(dataAtom->dataOffset() + 4));
255 m_mean = reader.readString(dataAtom->dataSize() - 4);
256 } else if (dataAtom->id() == Mp4AtomIds::Name) {
257 if (dataAtom->dataSize() < 4) {
258 diag.emplace_back(DiagLevel::Warning,
259 "Truncated child atom \"name\" in tag atom (ilst child) found. It will be ignored and discarded when applying changes.",
260 context);
261 continue;
262 }
263 if (++nameAtomFound > 1) {
264 if (nameAtomFound == 2) {
265 diag.emplace_back(DiagLevel::Warning,
266 "Tag atom contains more than one name atom. The addiational name atoms will be ignored and discarded when applying "
267 "changes.",
268 context);
269 }
270 continue;
271 }
272 stream.seekg(static_cast<streamoff>(dataAtom->dataOffset() + 4));
273 m_name = reader.readString(dataAtom->dataSize() - 4);
274 } else {
275 diag.emplace_back(DiagLevel::Warning,
276 "Unknown child atom \"" % dataAtom->idToString()
277 + "\" in tag atom (ilst child) found. It will be ignored and discarded when applying changes.",
278 context);
279 }
280 } catch (const Failure &) {
281 diag.emplace_back(DiagLevel::Warning,
282 "Unable to parse all children atom in tag atom (ilst child) found. Invalid children will be ignored and discarded when applying "
283 "changes.",
284 context);
285 }
286 }
287 if (value().isEmpty()) {
288 diag.emplace_back(DiagLevel::Warning, "The field value is empty.", context);
289 }
290}
291
306
314void Mp4TagField::make(ostream &stream, Diagnostics &diag)
315{
316 prepareMaking(diag).make(stream);
317}
318
322std::vector<std::uint32_t> Mp4TagField::expectedRawDataTypes() const
323{
324 using namespace Mp4TagAtomIds;
325 std::vector<std::uint32_t> res;
326 switch (id()) {
327 case Album:
328 case Artist:
329 case Comment:
330 case Year:
331 case Title:
332 case Genre:
333 case Composer:
334 case Encoder:
335 case Grouping:
336 case Description:
337 case Lyrics:
338 case RecordLabel:
339 case Performers:
340 case Lyricist:
341 res.push_back(RawDataType::Utf8);
342 res.push_back(RawDataType::Utf16);
343 break;
344 case PreDefinedGenre:
345 case TrackPosition:
346 case DiskPosition:
347 res.push_back(RawDataType::Reserved);
348 break;
349 case Bpm:
350 case Rating: // 0 = None, 1 = Explicit, 2 = Clean
351 res.push_back(RawDataType::BeSignedInt);
352 res.push_back(RawDataType::BeUnsignedInt);
353 break;
354 case Cover:
355 res.push_back(RawDataType::Gif);
356 res.push_back(RawDataType::Jpeg);
357 res.push_back(RawDataType::Png);
358 res.push_back(RawDataType::Bmp);
359 break;
360 case Extended:
362 throw Failure();
363 }
364 // assumption that extended "iTunes" tags always use Unicode correct?
365 res.push_back(RawDataType::Utf8);
366 res.push_back(RawDataType::Utf16);
367 break;
368 default:
369 throw Failure();
370 }
371 return res;
372}
373
382{
383 if (isTypeInfoAssigned()) {
384 // obtain raw data type from tag field if present
385 return typeInfo();
386 }
387
388 // there is no raw data type assigned (tag field was not present in original file and
389 // has been inserted by the library's user without type)
390 // -> try to derive appropriate raw data type from atom ID
392}
393
403{
404 using namespace Mp4TagAtomIds;
405 switch (id()) {
406 case Album:
407 case Artist:
408 case Comment:
409 case Year:
410 case Title:
411 case Genre:
412 case Composer:
413 case Encoder:
414 case Grouping:
415 case Description:
416 case Lyrics:
417 case RecordLabel:
418 case Performers:
419 case Lyricist:
420 case AlbumArtist:
421 switch (value.dataEncoding()) {
423 return RawDataType::Utf8;
425 return RawDataType::Utf16;
426 default:;
427 }
428 break;
429 case TrackPosition:
430 case DiskPosition:
432 case PreDefinedGenre:
433 case Bpm:
434 case Rating:
436 case Cover: {
437 const string &mimeType = value.mimeType();
438 if (mimeType == "image/jpg" || mimeType == "image/jpeg") { // "well-known" type
439 return RawDataType::Jpeg;
440 } else if (mimeType == "image/png") {
441 return RawDataType::Png;
442 } else if (mimeType == "image/bmp") {
443 return RawDataType::Bmp;
444 }
445 } break;
446 case Extended:
448 throw Failure();
449 }
450 switch (value.dataEncoding()) {
452 return RawDataType::Utf8;
454 return RawDataType::Utf16;
455 default:;
456 }
457 break;
458 default:;
459 }
460
461 // do not forget to extend Mp4Tag::internallyGetFieldId() and Mp4Tag::internallyGetKnownField() as well
462
463 throw Failure();
464}
465
469void Mp4TagField::internallyClearValue()
470{
472 m_additionalData.clear();
473 m_countryIndicator = 0;
474 m_langIndicator = 0;
475}
476
480void Mp4TagField::internallyClearFurtherData()
481{
482 m_name.clear();
483 m_mean.clear();
484 m_parsedRawDataType = RawDataType::Reserved;
485}
486
488Mp4TagFieldMaker::Data::Data()
489 : convertedData(stringstream::in | stringstream::out | stringstream::binary)
490{
491}
493
505Mp4TagFieldMaker::Mp4TagFieldMaker(Mp4TagField &field, Diagnostics &diag)
506 : m_field(field)
507 , m_writer(nullptr)
508 , m_totalSize(0)
509{
510 if (!m_field.id()) {
511 diag.emplace_back(DiagLevel::Warning, "Invalid tag atom ID.", "making MP4 tag field");
512 throw InvalidDataException();
513 }
514 const string context("making MP4 tag field " + Mp4TagField::fieldIdToString(m_field.id()));
515 if (m_field.value().isEmpty() && (!m_field.mean().empty() || !m_field.name().empty())) {
516 diag.emplace_back(DiagLevel::Critical, "No tag value assigned.", context);
517 throw InvalidDataException();
518 }
519
520 // calculate size for name and mean
521 m_totalSize = 8 + (m_field.name().empty() ? 0 : (12 + m_field.name().size())) + (m_field.mean().empty() ? 0 : (12 + m_field.mean().size()));
522
523 // prepare making data atom and calculate the expected size
524 m_totalSize += prepareDataAtom(field.value(), field.countryIndicator(), field.languageIndicator(), context, diag);
525 for (const auto &additionalData : m_field.additionalData()) {
526 m_totalSize += prepareDataAtom(additionalData.value, additionalData.countryIndicator, additionalData.languageIndicator, context, diag);
527 }
528
529 if (m_totalSize > numeric_limits<std::uint32_t>::max()) {
530 diag.emplace_back(DiagLevel::Critical, "Making a such big MP4 tag field is not possible.", context);
531 throw NotImplementedException();
532 }
533}
534
538std::uint64_t Mp4TagFieldMaker::prepareDataAtom(
539 const TagValue &value, std::uint16_t countryIndicator, std::uint16_t languageIndicator, const std::string &context, Diagnostics &diag)
540{
541 // add new data entry
542 auto &data = m_data.emplace_back();
543 m_writer.setStream(&data.convertedData);
544
545 // assign local info
546 // FIXME: use locale within the tag value instead of just passing through current values
547 data.countryIndicator = countryIndicator;
548 data.languageIndicator = languageIndicator;
549
550 try {
551 // try to use appropriate raw data type
552 data.rawType = m_field.isTypeInfoAssigned() ? m_field.typeInfo() : m_field.appropriateRawDataTypeForValue(value);
553 } catch (const Failure &) {
554 // unable to obtain appropriate raw data type
555 if (m_field.id() == Mp4TagAtomIds::Cover) {
556 // assume JPEG image
557 data.rawType = RawDataType::Jpeg;
558 diag.emplace_back(
559 DiagLevel::Warning, "It was not possible to find an appropriate raw data type id. JPEG image will be assumed.", context);
560 } else {
561 // assume UTF-8 text
562 data.rawType = RawDataType::Utf8;
563 diag.emplace_back(DiagLevel::Warning, "It was not possible to find an appropriate raw data type id. UTF-8 will be assumed.", context);
564 }
565 }
566
567 try {
568 if (!value.isEmpty()) { // there might be only mean and name info, but no data
569 data.convertedData.exceptions(std::stringstream::failbit | std::stringstream::badbit);
570 switch (data.rawType) {
572 if (value.type() != TagDataType::Text || value.dataEncoding() != TagTextEncoding::Utf8) {
573 m_writer.writeString(value.toString(TagTextEncoding::Utf8));
574 }
575 break;
577 if (value.type() != TagDataType::Text || value.dataEncoding() != TagTextEncoding::Utf16LittleEndian) {
578 m_writer.writeString(value.toString(TagTextEncoding::Utf16LittleEndian));
579 }
580 break;
582 int number = value.toInteger();
583 if (number <= numeric_limits<std::int16_t>::max() && number >= numeric_limits<std::int16_t>::min()) {
584 m_writer.writeInt16BE(static_cast<std::int16_t>(number));
585 } else {
586 m_writer.writeInt32BE(number);
587 }
588 break;
589 }
591 int number = value.toInteger();
592 if (number <= numeric_limits<std::uint16_t>::max() && number >= numeric_limits<std::uint16_t>::min()) {
593 m_writer.writeUInt16BE(static_cast<std::uint16_t>(number));
594 } else if (number > 0) {
595 m_writer.writeUInt32BE(static_cast<std::uint32_t>(number));
596 } else {
597 throw ConversionException(
598 "Negative integer can not be assigned to the field with the ID \"" % interpretIntegerAsString<std::uint32_t>(m_field.id())
599 + "\".");
600 }
601 break;
602 }
603 case RawDataType::Bmp:
605 case RawDataType::Png:
606 break; // leave converted data empty to write original data later
607 default:
608 switch (m_field.id()) {
609 // track number and disk number are exceptions
610 // raw data type 0 is used, information is stored as pair of unsigned integers
613 PositionInSet pos = value.toPositionInSet();
614 m_writer.writeInt32BE(pos.position());
615 if (pos.total() <= numeric_limits<std::int16_t>::max()) {
616 m_writer.writeInt16BE(static_cast<std::int16_t>(pos.total()));
617 } else {
618 throw ConversionException(
619 "Integer can not be assigned to the field with the id \"" % interpretIntegerAsString<std::uint32_t>(m_field.id())
620 + "\" because it is to big.");
621 }
622 m_writer.writeUInt16BE(0);
623 break;
624 }
626 m_writer.writeUInt16BE(static_cast<std::uint16_t>(value.toStandardGenreIndex()));
627 break;
628 default:; // leave converted data empty to write original data later
629 }
630 }
631 }
632 } catch (const ConversionException &e) {
633 // it was not possible to perform required conversions
634 if (char_traits<char>::length(e.what())) {
635 diag.emplace_back(DiagLevel::Critical, e.what(), context);
636 } else {
637 diag.emplace_back(DiagLevel::Critical, "The assigned tag value can not be converted to be written appropriately.", context);
638 }
639 throw InvalidDataException();
640 }
641
642 // calculate data size; assign raw data
643 if (value.isEmpty()) {
644 return data.size = 0;
645 } else if (data.convertedData.tellp()) {
646 data.size = static_cast<std::size_t>(data.convertedData.tellp());
647 } else {
648 data.rawData = std::string_view(value.dataPointer(), data.size = value.dataSize());
649 }
650 return data.size += 16;
651}
652
660void Mp4TagFieldMaker::make(ostream &stream)
661{
662 m_writer.setStream(&stream);
663 // size of entire tag atom
664 m_writer.writeUInt32BE(static_cast<std::uint32_t>(m_totalSize));
665 // id of tag atom
666 m_writer.writeUInt32BE(m_field.id());
667 // write "mean" atom
668 if (!m_field.mean().empty()) {
669 m_writer.writeUInt32BE(static_cast<std::uint32_t>(12 + m_field.mean().size()));
670 m_writer.writeUInt32BE(Mp4AtomIds::Mean);
671 m_writer.writeUInt32BE(0);
672 m_writer.writeString(m_field.mean());
673 }
674 // write "name" atom
675 if (!m_field.name().empty()) {
676 m_writer.writeUInt32BE(static_cast<std::uint32_t>(12 + m_field.name().length()));
677 m_writer.writeUInt32BE(Mp4AtomIds::Name);
678 m_writer.writeUInt32BE(0);
679 m_writer.writeString(m_field.name());
680 }
681 // write "data" atoms
682 for (auto &data : m_data) {
683 if (!data.size) {
684 continue;
685 }
686 m_writer.writeUInt32BE(static_cast<std::uint32_t>(data.size)); // size of data atom
687 m_writer.writeUInt32BE(Mp4AtomIds::Data); // id of data atom
688 m_writer.writeByte(0); // version
689 m_writer.writeUInt24BE(data.rawType);
690 m_writer.writeUInt16BE(data.countryIndicator);
691 m_writer.writeUInt16BE(data.languageIndicator);
692 if (data.convertedData.tellp()) {
693 // write converted data
694 stream << data.convertedData.rdbuf();
695 } else {
696 // no conversion was needed, write data directly from tag value
697 stream.write(data.rawData.data(), static_cast<std::streamoff>(data.rawData.size()));
698 }
699 }
700}
701
702} // 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...
const IdentifierType & id() const
Returns the element ID.
std::iostream & stream()
Returns the related stream.
ImplementationType * nextSibling()
Returns the next sibling of the element.
ImplementationType * firstChild()
Returns the first child of the element.
void parse(Diagnostics &diag)
Parses the header information of the element which is read from the related stream at the start offse...
ContainerType & container()
Returns the related container.
The Mp4Atom class helps to parse MP4 files.
std::string idToString() const
Converts the specified atom ID to a printable string.
Definition mp4atom.h:67
The Mp4TagFieldMaker class helps making tag fields.
void make(std::ostream &stream)
Saves the field (specified when constructing the object) to the specified stream.
Mp4TagFieldMaker(Mp4TagFieldMaker &&)=default
The Mp4TagField class is used by Mp4Tag to store the fields.
Mp4TagFieldMaker prepareMaking(Diagnostics &diag)
Prepares making.
const std::vector< AdditionalData > & additionalData() const
Returns additional data (and the corresponding raw data type, country and language).
void reparse(Mp4Atom &ilstChild, Diagnostics &diag)
Parses field information from the specified Mp4Atom.
const std::string & name() const
Returns the "name" for "extended" fields.
Mp4TagField()
Constructs a new Mp4TagField.
std::uint32_t appropriateRawDataTypeForValue(const TagValue &value) const
Returns an appropriate raw data type.
const std::string & mean() const
Returns the "mean" for "extended" fields.
std::uint32_t appropriateRawDataType() const
Returns an appropriate raw data type.
std::uint16_t languageIndicator() const
Returns the language indicator.
std::vector< std::uint32_t > expectedRawDataTypes() const
Returns the expected raw data types for the ID of the field.
std::uint16_t countryIndicator() const
Returns the country indicator.
void make(std::ostream &stream, Diagnostics &diag)
Saves the field to the specified stream.
static std::string fieldIdToString(IdentifierType id)
Returns the string representation for the specified id.
The TagField class is used by FieldMapBasedTag to store the fields.
void setTypeInfo(const TypeInfoType &typeInfo)
const TypeInfoType & typeInfo() const
IdentifierType & id()
Returns the id of the current TagField.
void setId(const IdentifierType &id)
The TagValue class wraps values of different types.
const std::string & mimeType() const
Returns the MIME type.
Definition tagvalue.h:590
TagTextEncoding dataEncoding() const
Returns the data encoding.
Definition tagvalue.h:718
void clearDataAndMetadata()
Wipes assigned data including meta data.
Definition tagvalue.h:512
Contains all classes and functions of the TagInfo library.
Definition aaccodebook.h:10