Tag Parser 11.4.0
C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags
id3v2tag.cpp
Go to the documentation of this file.
1#include "./id3v2tag.h"
2#include "./id3v2frameids.h"
3
4#include "../diagnostics.h"
5#include "../exceptions.h"
6
7#include <c++utilities/conversion/stringbuilder.h>
8#include <c++utilities/conversion/stringconversion.h>
9
10#include <iostream>
11
12using namespace std;
13using namespace CppUtilities;
14
15namespace TagParser {
16
27{
28 switch (field) {
38 case KnownField::Bpm:
47 return m_majorVersion > 3;
53 return true;
54 default:
55 return false;
56 }
57}
58
60{
61 const auto encoding = proposedTextEncoding();
62 for (auto &field : fields()) {
63 auto &value = field.second.value();
64 value.convertDataEncoding(encoding);
66 }
67}
68
72void Id3v2Tag::internallyGetValuesFromField(const Id3v2Tag::FieldType &field, std::vector<const TagValue *> &values) const
73{
74 if (!field.value().isEmpty()) {
75 values.emplace_back(&field.value());
76 }
77 for (const auto &value : field.additionalValues()) {
78 if (!value.isEmpty()) {
79 values.emplace_back(&value);
80 }
81 }
82}
83
90bool Id3v2Tag::internallySetValues(const IdentifierType &id, const std::vector<TagValue> &values)
91{
92 // use default implementation for non-text frames
95 }
96
97 // find existing text frame
98 auto range = fields().equal_range(id);
99 auto frameIterator = range.first;
100
101 // use existing frame or insert new text frame
102 auto valuesIterator = values.cbegin();
103 if (frameIterator != range.second) {
104 ++range.first;
105 // add primary value to existing frame
106 if (valuesIterator != values.cend()) {
107 frameIterator->second.setValue(*valuesIterator);
108 ++valuesIterator;
109 } else {
110 frameIterator->second.value().clearDataAndMetadata();
111 }
112 } else {
113 // skip if there is no existing frame but also no values to be assigned
114 if (valuesIterator == values.cend()) {
115 return true;
116 }
117 // add primary value to new frame
118 frameIterator = fields().insert(make_pair(id, Id3v2Frame(id, *valuesIterator)));
119 ++valuesIterator;
120 }
121
122 // add additional values to frame
123 frameIterator->second.additionalValues() = vector<TagValue>(valuesIterator, values.cend());
124
125 // remove remaining existing values (there are more existing values than specified ones)
126 for (; range.first != range.second; ++range.first) {
127 range.first->second.setValue(TagValue());
128 }
129 return true;
130}
131
133{
134 using namespace Id3v2FrameIds;
135 if (m_majorVersion >= 3) {
136 switch (field) {
138 return lAlbum;
140 return lArtist;
142 return lComment;
144 return lRecordingTime; // (de)serializer takes to convert to/from lYear/lRecordingDates/lDate/lTime
146 return lReleaseTime;
148 return lTitle;
150 return lGenre;
152 return lTrackPosition;
154 return lDiskPosition;
156 return lEncoder;
157 case KnownField::Bpm:
158 return lBpm;
160 return lCover;
162 return lWriter;
164 return lLength;
166 return lLanguage;
168 return lEncoderSettings;
172 return lSynchronizedLyrics;
176 return lRecordLabel;
178 return lComposer;
180 return lPlayCounter;
182 return lRating;
184 return lAlbumArtist;
186 return lRemixedBy;
188 return lCopyright;
190 return lTaggingTime;
192 return lEncodingTime;
195 case KnownField::Mood:
196 return lMood;
197 default:;
198 }
199 } else {
200 switch (field) {
202 return sAlbum;
204 return sArtist;
206 return sComment;
208 return lRecordingTime; // (de)serializer takes to convert to/from sYear/sRecordingDates/sDate/sTime
210 return sTitle;
212 return sGenre;
214 return sTrackPosition;
216 return sDiskPosition;
218 return sEncoder;
219 case KnownField::Bpm:
220 return sBpm;
222 return sCover;
224 return sWriter;
226 return sLength;
228 return sLanguage;
230 return sEncoderSettings;
234 return sSynchronizedLyrics;
238 return sRecordLabel;
240 return sComposer;
242 return sPlayCounter;
244 return sRating;
246 return sAlbumArtist;
248 return sRemixedBy;
250 return sCopyright;
251 default:;
252 }
253 }
254 return 0;
255}
256
257KnownField Id3v2Tag::internallyGetKnownField(const IdentifierType &id) const
258{
259 using namespace Id3v2FrameIds;
260 switch (id) {
261 case lAlbum:
262 return KnownField::Album;
263 case lArtist:
264 return KnownField::Artist;
265 case lComment:
266 return KnownField::Comment;
267 case lRecordingTime:
268 case lYear:
270 case lTitle:
271 return KnownField::Title;
272 case lGenre:
273 return KnownField::Genre;
274 case lTrackPosition:
276 case lDiskPosition:
278 case lEncoder:
279 return KnownField::Encoder;
280 case lBpm:
281 return KnownField::Bpm;
282 case lCover:
283 return KnownField::Cover;
284 case lWriter:
286 case lLanguage:
288 case lLength:
289 return KnownField::Length;
290 case lEncoderSettings:
293 return KnownField::Lyrics;
296 case lAlbumArtist:
298 case lRemixedBy:
300 case lCopyright:
304 case lRecordLabel:
306 case lTaggingTime:
308 case lEncodingTime:
312 case lMood:
313 return KnownField::Mood;
314 case lPlayCounter:
316 case lRating:
317 return KnownField::Rating;
318 case lISRC:
319 return KnownField::ISRC;
320 case sAlbum:
321 return KnownField::Album;
322 case sArtist:
323 return KnownField::Artist;
324 case sComment:
325 return KnownField::Comment;
326 case sYear:
328 case sTitle:
329 return KnownField::Title;
330 case sGenre:
331 return KnownField::Genre;
332 case sTrackPosition:
334 case sEncoder:
335 return KnownField::Encoder;
336 case sBpm:
337 return KnownField::Bpm;
338 case sCover:
339 return KnownField::Cover;
340 case sWriter:
342 case sLanguage:
344 case sLength:
345 return KnownField::Length;
346 case sEncoderSettings:
349 return KnownField::Lyrics;
352 case sAlbumArtist:
354 case sRecordLabel:
356 case sRemixedBy:
358 case sCopyright:
360 case sPlayCounter:
362 case sRating:
363 return KnownField::Rating;
364 case sISRC:
365 return KnownField::ISRC;
366 default:
367 return KnownField::Invalid;
368 }
369}
370
372{
373 using namespace Id3v2FrameIds;
374 switch (id) {
375 case lLength:
376 case sLength:
378 case lBpm:
379 case sBpm:
380 case lYear:
381 case sYear:
382 case lPlayCounter:
383 case sPlayCounter:
385 case lTrackPosition:
386 case sTrackPosition:
387 case lDiskPosition:
389 case lCover:
390 case sCover:
392 case lRating:
393 case sRating:
395 default:
397 return TagDataType::Text;
398 } else {
400 }
401 }
402}
403
410void Id3v2Tag::convertOldRecordDateFields(const std::string &diagContext, Diagnostics &diag)
411{
412 // skip if it is a v2.4.0 tag and lRecordingTime is present
413 if (majorVersion() >= 4 && fields().find(Id3v2FrameIds::lRecordingTime) != fields().cend()) {
414 return;
415 }
416
417 // parse values of lYear/lRecordingDates/lDate/lTime/sYear/sRecordingDates/sDate/sTime fields
418 bool hasAnyValue = false;
419 int year = 1, month = 1, day = 1, hour = 0, minute = 0;
420 if (const auto &v = value(Id3v2FrameIds::lYear)) {
421 hasAnyValue = true;
422 try {
423 year = v.toInteger();
424 } catch (const ConversionException &e) {
425 diag.emplace_back(DiagLevel::Critical, argsToString("Unable to parse year from \"TYER\" frame: ", e.what()), diagContext);
426 }
427 }
428 if (const auto &v = value(Id3v2FrameIds::lDate)) {
429 hasAnyValue = true;
430 try {
431 auto str = v.toString();
432 if (str.size() != 4) {
433 throw ConversionException("format is not DDMM");
434 }
435 day = stringToNumber<unsigned short>(std::string_view(str.data() + 0, 2));
436 month = stringToNumber<unsigned short>(std::string_view(str.data() + 2, 2));
437 } catch (const ConversionException &e) {
438 diag.emplace_back(DiagLevel::Critical, argsToString("Unable to parse month and day from \"TDAT\" frame: ", e.what()), diagContext);
439 }
440 }
441 if (const auto &v = value(Id3v2FrameIds::lTime)) {
442 hasAnyValue = true;
443 try {
444 auto str = v.toString();
445 if (str.size() != 4) {
446 throw ConversionException("format is not HHMM");
447 }
448 hour = stringToNumber<unsigned short>(std::string_view(str.data() + 0, 2));
449 minute = stringToNumber<unsigned short>(std::string_view(str.data() + 2, 2));
450 } catch (const ConversionException &e) {
451 diag.emplace_back(DiagLevel::Critical, argsToString("Unable to parse hour and minute from \"TIME\" frame: ", +e.what()), diagContext);
452 }
453 }
454
455 // set the field values as DateTime
456 if (!hasAnyValue) {
457 return;
458 }
459 try {
460 setValue(Id3v2FrameIds::lRecordingTime, TagValue(DateTime::fromDateAndTime(year, month, day, hour, minute)));
461 } catch (const ConversionException &e) {
462 try {
463 // try to set at least the year
464 setValue(Id3v2FrameIds::lRecordingTime, TagValue(DateTime::fromDate(year)));
465 diag.emplace_back(DiagLevel::Critical,
466 argsToString(
467 "Unable to parse the full date of the recording. Only the 'Year' frame could be parsed; related frames failed: ", e.what()),
468 diagContext);
469 } catch (const ConversionException &) {
470 }
471 diag.emplace_back(
472 DiagLevel::Critical, argsToString("Unable to parse a valid date from the 'Year' frame and related frames: ", e.what()), diagContext);
473 }
474}
475
483void Id3v2Tag::parse(istream &stream, const std::uint64_t maximalSize, Diagnostics &diag)
484{
485 // prepare parsing
486 static const string context("parsing ID3v2 tag");
487 BinaryReader reader(&stream);
488 const auto startOffset = static_cast<std::uint64_t>(stream.tellg());
489
490 // check whether the header is truncated
491 if (maximalSize && maximalSize < 10) {
492 diag.emplace_back(DiagLevel::Critical, "ID3v2 header is truncated (at least 10 bytes expected).", context);
494 }
495
496 // read signature: ID3
497 if (reader.readUInt24BE() != 0x494433u) {
498 diag.emplace_back(DiagLevel::Critical, "Signature is invalid.", context);
499 throw InvalidDataException();
500 }
501 // read header data
502 const std::uint8_t majorVersion = reader.readByte();
503 const std::uint8_t revisionVersion = reader.readByte();
505 m_flags = reader.readByte();
506 m_sizeExcludingHeader = reader.readSynchsafeUInt32BE();
507 m_size = 10 + m_sizeExcludingHeader;
508 if (m_sizeExcludingHeader == 0) {
509 diag.emplace_back(DiagLevel::Warning, "ID3v2 tag seems to be empty.", context);
510 return;
511 }
512
513 // check if the version
514 if (!isVersionSupported()) {
515 diag.emplace_back(DiagLevel::Critical, "The ID3v2 tag couldn't be parsed, because its version is not supported.", context);
517 }
518
519 // read extended header (if present)
520 if (hasExtendedHeader()) {
521 if (maximalSize && maximalSize < 14) {
522 diag.emplace_back(DiagLevel::Critical, "Extended header denoted but not present.", context);
524 }
525 m_extendedHeaderSize = reader.readSynchsafeUInt32BE();
526 if (m_extendedHeaderSize < 6 || m_extendedHeaderSize > m_sizeExcludingHeader || (maximalSize && maximalSize < (10 + m_extendedHeaderSize))) {
527 diag.emplace_back(DiagLevel::Critical, "Extended header is invalid/truncated.", context);
529 }
530 stream.seekg(m_extendedHeaderSize - 4, ios_base::cur);
531 }
532
533 // how many bytes remain for frames and padding?
534 std::uint32_t bytesRemaining = m_sizeExcludingHeader - m_extendedHeaderSize;
535 if (maximalSize && bytesRemaining > maximalSize) {
536 bytesRemaining = static_cast<std::uint32_t>(maximalSize);
537 diag.emplace_back(DiagLevel::Critical, "Frames are truncated.", context);
538 }
539
540 // read frames
541 auto pos = static_cast<std::uint64_t>(stream.tellg());
542 while (bytesRemaining) {
543 // seek to next frame
544 stream.seekg(static_cast<streamoff>(pos));
545 // parse frame
546 Id3v2Frame frame;
547 try {
548 frame.parse(reader, majorVersion, bytesRemaining, diag);
549 if (Id3v2FrameIds::isTextFrame(frame.id()) && fields().count(frame.id()) == 1) {
550 diag.emplace_back(DiagLevel::Warning, "The text frame " % frame.idToString() + " exists more than once.", context);
551 }
552 fields().emplace(frame.id(), move(frame));
553 } catch (const NoDataFoundException &) {
554 if (frame.hasPaddingReached()) {
555 m_paddingSize = startOffset + m_size - pos;
556 break;
557 }
558 } catch (const Failure &) {
559 }
560
561 // calculate next frame offset
562 if (frame.totalSize() <= bytesRemaining) {
563 pos += frame.totalSize();
564 bytesRemaining -= frame.totalSize();
565 } else {
566 pos += bytesRemaining;
567 bytesRemaining = 0;
568 }
569 }
570
571 if (m_handlingFlags & Id3v2HandlingFlags::ConvertRecordDateFields) {
572 convertOldRecordDateFields(context, diag);
573 }
574
575 // check for extended header
576 if (!hasFooter()) {
577 return;
578 }
579 if (maximalSize && m_size + 10 < maximalSize) {
580 // the footer does not provide additional information, just check the signature
581 stream.seekg(static_cast<streamoff>(startOffset + (m_size += 10)));
582 if (reader.readUInt24LE() != 0x494433u) {
583 diag.emplace_back(DiagLevel::Critical, "Footer signature is invalid.", context);
584 }
585 // skip remaining footer
586 stream.seekg(7, ios_base::cur);
587 } else {
588 diag.emplace_back(DiagLevel::Critical, "Footer denoted but not present.", context);
590 }
591}
592
604{
605 return Id3v2TagMaker(*this, diag);
606}
607
615void Id3v2Tag::make(ostream &stream, std::uint32_t padding, Diagnostics &diag)
616{
617 prepareMaking(diag).make(stream, padding, diag);
618}
619
624void Id3v2Tag::setVersion(std::uint8_t majorVersion, std::uint8_t revisionVersion)
625{
626 m_majorVersion = majorVersion;
627 m_revisionVersion = revisionVersion;
628 m_version = argsToString('2', '.', majorVersion, '.', revisionVersion);
629}
630
643bool FrameComparer::operator()(std::uint32_t lhs, std::uint32_t rhs) const
644{
645 if (lhs == rhs) {
646 return false;
647 }
648
649 const bool lhsLong = Id3v2FrameIds::isLongId(lhs);
650 const bool rhsLong = Id3v2FrameIds::isLongId(rhs);
651 if (lhsLong != rhsLong) {
652 if (!lhsLong) {
654 if (!lhs) {
655 return true;
656 }
657 } else if (!rhsLong) {
659 if (!rhs) {
660 return true;
661 }
662 }
663 }
664
666 return true;
667 }
669 return false;
670 }
671 if (lhs == Id3v2FrameIds::lTitle || lhs == Id3v2FrameIds::sTitle) {
672 return true;
673 }
674 if (rhs == Id3v2FrameIds::lTitle || rhs == Id3v2FrameIds::sTitle) {
675 return false;
676 }
677
678 const bool lhstextfield = Id3v2FrameIds::isTextFrame(lhs);
679 const bool rhstextfield = Id3v2FrameIds::isTextFrame(rhs);
680 if (lhstextfield && !rhstextfield) {
681 return true;
682 }
683 if (!lhstextfield && rhstextfield) {
684 return false;
685 }
686
687 if (lhs == Id3v2FrameIds::lCover || lhs == Id3v2FrameIds::sCover) {
688 return false;
689 }
690 if (rhs == Id3v2FrameIds::lCover || rhs == Id3v2FrameIds::sCover) {
691 return true;
692 }
693 return lhs < rhs;
694}
695
706void Id3v2Tag::removeOldRecordDateRelatedFields()
707{
709 fields().erase(field);
710 }
711}
712
716void Id3v2Tag::prepareRecordDataForMaking(const std::string &diagContext, Diagnostics &diag)
717{
718 // get rid of lYear/lRecordingDates/lDate/lTime/sYear/sRecordingDates/sDate/sTime if writing v2.4.0 or newer
719 // note: If the tag was initially v2.3.0 or older the "old" fields have already been converted to lRecordingTime when
720 // parsing and the generic accessors propose using lRecordingTime in any case.
721 if (majorVersion() >= 4) {
722 removeOldRecordDateRelatedFields();
723 return;
724 }
725
726 // convert lRecordingTime to old fields for v2.3.0 and older
727 const auto recordingTimeFieldIterator = fields().find(Id3v2FrameIds::lRecordingTime);
728 // -> If the auto-created lRecordingTime field (see note above) has been completely removed write the old fields as-is.
729 // This allows one to bypass this handling and set the old fields explicitly.
730 if (recordingTimeFieldIterator == fields().cend()) {
731 return;
732 }
733 // -> simply remove all old fields if lRecordingTime is set to an empty value
734 const auto &recordingTime = recordingTimeFieldIterator->second.value();
735 if (recordingTime.isEmpty()) {
736 removeOldRecordDateRelatedFields();
737 return;
738 }
739 // -> convert lRecordingTime (which is supposed to be an ISO string) to a DateTime
740 try {
741 const auto asDateTime = recordingTime.toDateTime();
742 // -> remove any existing old fields to avoid any leftovers
743 removeOldRecordDateRelatedFields();
744 // -> assign old fields from parsed DateTime
745 std::stringstream year, date, time;
746 year << std::setfill('0') << std::setw(4) << asDateTime.year();
747 setValue(Id3v2FrameIds::lYear, TagValue(year.str()));
748 date << std::setfill('0') << std::setw(2) << asDateTime.day() << std::setfill('0') << std::setw(2) << asDateTime.month();
749 setValue(Id3v2FrameIds::lDate, TagValue(date.str()));
750 time << std::setfill('0') << std::setw(2) << asDateTime.hour() << std::setfill('0') << std::setw(2) << asDateTime.minute();
751 setValue(Id3v2FrameIds::lTime, TagValue(time.str()));
752 if (asDateTime.second() || asDateTime.millisecond()) {
753 diag.emplace_back(DiagLevel::Warning,
754 "The recording time field (TRDA) has been truncated to full minutes when converting to corresponding fields for older ID3v2 "
755 "versions.",
756 diagContext);
757 }
758 } catch (const ConversionException &e) {
759 try {
760 diag.emplace_back(DiagLevel::Critical,
761 argsToString("Unable to convert recording time field (TRDA) with the value \"", recordingTime.toString(),
762 "\" to corresponding fields for older ID3v2 versions: ", e.what()),
763 diagContext);
764 } catch (const ConversionException &) {
765 diag.emplace_back(DiagLevel::Critical,
766 argsToString("Unable to convert recording time field (TRDA) to corresponding fields for older ID3v2 versions: ", e.what()),
767 diagContext);
768 }
769 }
770 // -> get rid of lRecordingTime
772}
773
778Id3v2TagMaker::Id3v2TagMaker(Id3v2Tag &tag, Diagnostics &diag)
779 : m_tag(tag)
780 , m_framesSize(0)
781{
782 static const string context("making ID3v2 tag");
783
784 // check if version is supported
785 // (the version could have been changed using setVersion())
786 if (!tag.isVersionSupported()) {
787 diag.emplace_back(DiagLevel::Critical, "The ID3v2 tag version isn't supported.", context);
788 throw VersionNotSupportedException();
789 }
790
791 if (m_tag.m_handlingFlags & Id3v2HandlingFlags::ConvertRecordDateFields) {
792 tag.prepareRecordDataForMaking(context, diag);
793 }
794
795 // prepare frames
796 m_maker.reserve(tag.fields().size());
797 for (auto &pair : tag.fields()) {
798 try {
799 m_maker.emplace_back(pair.second.prepareMaking(tag.majorVersion(), diag));
800 m_framesSize += m_maker.back().requiredSize();
801 } catch (const Failure &) {
802 }
803 }
804
805 // calculate required size
806 // -> header + size of frames
807 m_requiredSize = 10 + m_framesSize;
808}
809
817void Id3v2TagMaker::make(std::ostream &stream, std::uint32_t padding, Diagnostics &diag)
818{
819 CPP_UTILITIES_UNUSED(diag)
820
821 BinaryWriter writer(&stream);
822
823 // write header
824 // -> signature
825 writer.writeUInt24BE(0x494433u);
826 // -> version
827 writer.writeByte(m_tag.majorVersion());
828 writer.writeByte(m_tag.revisionVersion());
829 // -> flags, but without extended header or compression bit set
830 writer.writeByte(m_tag.flags() & 0xBF);
831 // -> size (excluding header)
832 writer.writeSynchsafeUInt32BE(m_framesSize + padding);
833
834 // write frames
835 for (auto &maker : m_maker) {
836 maker.make(writer);
837 }
838
839 // write padding
840 for (; padding; --padding) {
841 stream.put(0);
842 }
843}
844
845} // 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.
typename FieldMapBasedTagTraits< Id3v2Tag >::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.
bool internallySetValues(const IdentifierType &id, const std::vector< TagValue > &values)
Default implementation for setValues().
typename FieldMapBasedTagTraits< Id3v2Tag >::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.
The Id3v2Frame class is used by Id3v2Tag to store the fields.
Definition: id3v2frame.h:86
void parse(CppUtilities::BinaryReader &reader, std::uint32_t version, std::uint32_t maximalSize, Diagnostics &diag)
Parses a frame from the stream read using the specified reader.
Definition: id3v2frame.cpp:151
std::uint32_t totalSize() const
Returns the total size of the frame in bytes.
Definition: id3v2frame.h:221
bool hasPaddingReached() const
Returns whether the padding has reached.
Definition: id3v2frame.h:197
The Id3v2TagMaker class helps writing ID3v2 tags.
Definition: id3v2tag.h:35
void make(std::ostream &stream, std::uint32_t padding, Diagnostics &diag)
Saves the tag (specified when constructing the object) to the specified stream.
Definition: id3v2tag.cpp:817
std::uint8_t revisionVersion() const
Returns the revision version if known; otherwise returns 0.
Definition: id3v2tag.h:204
void make(std::ostream &targetStream, std::uint32_t padding, Diagnostics &diag)
Writes tag information to the specified stream.
Definition: id3v2tag.cpp:615
bool hasExtendedHeader() const
Returns an indication whether an extended header is used.
Definition: id3v2tag.h:239
bool isVersionSupported() const
Returns an indication whether the version is supported by the Id3v2Tag class.
Definition: id3v2tag.h:215
bool supportsMultipleValues(KnownField field) const override
Allows multiple values for some fields.
Definition: id3v2tag.cpp:26
void setVersion(std::uint8_t majorVersion, std::uint8_t revisionVersion)
Sets the version to the specified majorVersion and the specified revisionVersion.
Definition: id3v2tag.cpp:624
KnownField internallyGetKnownField(const IdentifierType &id) const
Definition: id3v2tag.cpp:257
std::uint8_t flags() const
Returns the flags read from the ID3v2 header.
Definition: id3v2tag.h:223
std::uint8_t majorVersion() const
Returns the major version if known; otherwise returns 0.
Definition: id3v2tag.h:196
bool hasFooter() const
Returns an indication whether a footer is present.
Definition: id3v2tag.h:255
void internallyGetValuesFromField(const FieldType &field, std::vector< const TagValue * > &values) const
Adds additional values as well.
Definition: id3v2tag.cpp:72
bool internallySetValues(const IdentifierType &id, const std::vector< TagValue > &values)
Uses default implementation for non-text frames and applies special handling to text frames.
Definition: id3v2tag.cpp:90
TagTextEncoding proposedTextEncoding() const override
Returns the proposed text encoding.
Definition: id3v2tag.h:149
TagDataType internallyGetProposedDataType(const std::uint32_t &id) const
Definition: id3v2tag.cpp:371
Id3v2TagMaker prepareMaking(Diagnostics &diag)
Prepares making.
Definition: id3v2tag.cpp:603
void ensureTextValuesAreProperlyEncoded() override
Ensures the encoding of all assigned text values is supported by the tag by converting the character ...
Definition: id3v2tag.cpp:59
IdentifierType internallyGetFieldId(KnownField field) const
Definition: id3v2tag.cpp:132
friend class Id3v2TagMaker
Definition: id3v2tag.h:80
void parse(std::istream &sourceStream, const std::uint64_t maximalSize, Diagnostics &diag)
Parses tag information from the specified stream.
Definition: id3v2tag.cpp:483
The exception that is thrown when the data to be parsed or to be made seems invalid and therefore can...
Definition: exceptions.h:25
The exception that is thrown when the data to be parsed holds no parsable information (e....
Definition: exceptions.h:18
IdentifierType & id()
Returns the id of the current TagField.
std::string idToString() const
Returns the id of the current TagField as string.
The TagValue class wraps values of different types.
Definition: tagvalue.h:142
void convertDataEncoding(TagTextEncoding encoding)
Converts the currently assigned text value to the specified encoding.
Definition: tagvalue.cpp:732
bool isEmpty() const
Returns whether no or an empty value is assigned.
Definition: tagvalue.h:597
void convertDescriptionEncoding(TagTextEncoding encoding)
Converts the assigned description to use the specified encoding.
Definition: tagvalue.cpp:784
std::string m_version
Definition: tag.h:200
std::uint64_t m_size
Definition: tag.h:201
The exception that is thrown when the data to be parsed is truncated and therefore can not be parsed ...
Definition: exceptions.h:39
The exception that is thrown when an operation fails because the detected or specified version is not...
Definition: exceptions.h:53
constexpr bool isLongId(std::uint32_t id)
Returns an indication whether the specified id is a long frame id.
Definition: id3v2frameids.h:95
constexpr bool isTextFrame(std::uint32_t id)
Returns an indication whether the specified id is a text frame id.
TAG_PARSER_EXPORT std::uint32_t convertToLongId(std::uint32_t id)
Converts the specified short frame ID to the equivalent long frame ID.
constexpr TAG_PARSER_EXPORT std::string_view year()
constexpr TAG_PARSER_EXPORT std::string_view date()
Contains all classes and functions of the TagInfo library.
Definition: aaccodebook.h:10
KnownField
Specifies the field.
Definition: tag.h:28
TagDataType
Specifies the data type.
Definition: tagvalue.h:119
bool operator()(std::uint32_t lhs, std::uint32_t rhs) const
Returns true if lhs goes before rhs; otherwise returns false.
Definition: id3v2tag.cpp:643