Tag Parser 12.1.0
C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags
Loading...
Searching...
No Matches
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 converts 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;
170 return lUnsynchronizedLyrics;
172 return lSynchronizedLyrics;
174 return lContentGroupDescription;
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;
194 return lOriginalReleaseTime;
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 converts 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;
232 return sUnsynchronizedLyrics;
234 return sSynchronizedLyrics;
236 return sContentGroupDescription;
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:
292 case lUnsynchronizedLyrics:
293 return KnownField::Lyrics;
294 case lSynchronizedLyrics:
296 case lAlbumArtist:
298 case lRemixedBy:
300 case lCopyright:
302 case lContentGroupDescription:
304 case lRecordLabel:
306 case lTaggingTime:
308 case lEncodingTime:
310 case lOriginalReleaseTime:
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:
348 case sUnsynchronizedLyrics:
349 return KnownField::Lyrics;
350 case sSynchronizedLyrics:
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 auto expr = DateTimeExpression();
419 auto year = 1, month = 1, day = 1, hour = 0, minute = 0;
420 if (const auto &v = value(Id3v2FrameIds::lYear)) {
421 expr.parts |= DateTimeParts::Year;
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 expr.parts |= DateTimeParts::Day | DateTimeParts::Month;
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 expr.parts |= DateTimeParts::Hour | DateTimeParts::Minute;
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 (expr.parts == DateTimeParts::None) {
457 return;
458 }
459 try {
460 expr.value = DateTime::fromDateAndTime(year, month, day, hour, minute);
462 } catch (const ConversionException &e) {
463 try {
464 // try to set at least the year
465 expr.parts = DateTimeParts::Year;
466 expr.value = DateTime::fromDate(year);
468 diag.emplace_back(DiagLevel::Critical,
469 argsToString(
470 "Unable to parse the full date of the recording. Only the 'Year' frame could be parsed; related frames failed: ", e.what()),
471 diagContext);
472 } catch (const ConversionException &) {
473 }
474 diag.emplace_back(
475 DiagLevel::Critical, argsToString("Unable to parse a valid date from the 'Year' frame and related frames: ", e.what()), diagContext);
476 }
477}
478
486void Id3v2Tag::parse(istream &stream, const std::uint64_t maximalSize, Diagnostics &diag)
487{
488 // prepare parsing
489 static const string context("parsing ID3v2 tag");
490 BinaryReader reader(&stream);
491 const auto startOffset = static_cast<std::uint64_t>(stream.tellg());
492
493 // check whether the header is truncated
494 if (maximalSize && maximalSize < 10) {
495 diag.emplace_back(DiagLevel::Critical, "ID3v2 header is truncated (at least 10 bytes expected).", context);
497 }
498
499 // read signature: ID3
500 if (reader.readUInt24BE() != 0x494433u) {
501 diag.emplace_back(DiagLevel::Critical, "Signature is invalid.", context);
502 throw InvalidDataException();
503 }
504 // read header data
505 const std::uint8_t majorVersion = reader.readByte();
506 const std::uint8_t revisionVersion = reader.readByte();
508 m_flags = reader.readByte();
509 m_sizeExcludingHeader = reader.readSynchsafeUInt32BE();
510 m_size = 10 + m_sizeExcludingHeader;
511 if (m_sizeExcludingHeader == 0) {
512 diag.emplace_back(DiagLevel::Warning, "ID3v2 tag seems to be empty.", context);
513 return;
514 }
515
516 // check if the version
517 if (!isVersionSupported()) {
518 diag.emplace_back(DiagLevel::Critical, "The ID3v2 tag couldn't be parsed, because its version is not supported.", context);
520 }
521
522 // read extended header (if present)
523 if (hasExtendedHeader()) {
524 if (maximalSize && maximalSize < 14) {
525 diag.emplace_back(DiagLevel::Critical, "Extended header denoted but not present.", context);
527 }
528 m_extendedHeaderSize = reader.readSynchsafeUInt32BE();
529 if (m_extendedHeaderSize < 6 || m_extendedHeaderSize > m_sizeExcludingHeader || (maximalSize && maximalSize < (10 + m_extendedHeaderSize))) {
530 diag.emplace_back(DiagLevel::Critical, "Extended header is invalid/truncated.", context);
532 }
533 stream.seekg(m_extendedHeaderSize - 4, ios_base::cur);
534 }
535
536 // how many bytes remain for frames and padding?
537 std::uint32_t bytesRemaining = m_sizeExcludingHeader - m_extendedHeaderSize;
538 if (maximalSize && bytesRemaining > maximalSize) {
539 bytesRemaining = static_cast<std::uint32_t>(maximalSize);
540 diag.emplace_back(DiagLevel::Critical, "Frames are truncated.", context);
541 }
542
543 // read frames
544 auto pos = static_cast<std::uint64_t>(stream.tellg());
545 while (bytesRemaining) {
546 // seek to next frame
547 stream.seekg(static_cast<streamoff>(pos));
548 // parse frame
549 Id3v2Frame frame;
550 try {
551 frame.parse(reader, majorVersion, bytesRemaining, diag);
552 if (Id3v2FrameIds::isTextFrame(frame.id()) && fields().count(frame.id()) == 1) {
553 diag.emplace_back(DiagLevel::Warning, "The text frame " % frame.idToString() + " exists more than once.", context);
554 }
555 fields().emplace(frame.id(), std::move(frame));
556 } catch (const NoDataFoundException &) {
557 if (frame.hasPaddingReached()) {
558 m_paddingSize = startOffset + m_size - pos;
559 break;
560 }
561 } catch (const Failure &) {
562 }
563
564 // calculate next frame offset
565 if (frame.totalSize() <= bytesRemaining) {
566 pos += frame.totalSize();
567 bytesRemaining -= frame.totalSize();
568 } else {
569 pos += bytesRemaining;
570 bytesRemaining = 0;
571 }
572 }
573
574 if (m_handlingFlags & Id3v2HandlingFlags::ConvertRecordDateFields) {
575 convertOldRecordDateFields(context, diag);
576 }
577
578 // check for extended header
579 if (!hasFooter()) {
580 return;
581 }
582 if (maximalSize && m_size + 10 < maximalSize) {
583 // the footer does not provide additional information, just check the signature
584 stream.seekg(static_cast<streamoff>(startOffset + (m_size += 10)));
585 if (reader.readUInt24LE() != 0x494433u) {
586 diag.emplace_back(DiagLevel::Critical, "Footer signature is invalid.", context);
587 }
588 // skip remaining footer
589 stream.seekg(7, ios_base::cur);
590 } else {
591 diag.emplace_back(DiagLevel::Critical, "Footer denoted but not present.", context);
593 }
594}
595
607{
608 return Id3v2TagMaker(*this, diag);
609}
610
618void Id3v2Tag::make(ostream &stream, std::uint32_t padding, Diagnostics &diag)
619{
620 prepareMaking(diag).make(stream, padding, diag);
621}
622
627void Id3v2Tag::setVersion(std::uint8_t majorVersion, std::uint8_t revisionVersion)
628{
629 m_majorVersion = majorVersion;
630 m_revisionVersion = revisionVersion;
631 m_version = argsToString('2', '.', majorVersion, '.', revisionVersion);
632}
633
646bool FrameComparer::operator()(std::uint32_t lhs, std::uint32_t rhs) const
647{
648 if (lhs == rhs) {
649 return false;
650 }
651
652 const bool lhsLong = Id3v2FrameIds::isLongId(lhs);
653 const bool rhsLong = Id3v2FrameIds::isLongId(rhs);
654 if (lhsLong != rhsLong) {
655 if (!lhsLong) {
657 if (!lhs) {
658 return true;
659 }
660 } else if (!rhsLong) {
662 if (!rhs) {
663 return true;
664 }
665 }
666 }
667
669 return true;
670 }
672 return false;
673 }
674 if (lhs == Id3v2FrameIds::lTitle || lhs == Id3v2FrameIds::sTitle) {
675 return true;
676 }
677 if (rhs == Id3v2FrameIds::lTitle || rhs == Id3v2FrameIds::sTitle) {
678 return false;
679 }
680
681 const bool lhstextfield = Id3v2FrameIds::isTextFrame(lhs);
682 const bool rhstextfield = Id3v2FrameIds::isTextFrame(rhs);
683 if (lhstextfield && !rhstextfield) {
684 return true;
685 }
686 if (!lhstextfield && rhstextfield) {
687 return false;
688 }
689
690 if (lhs == Id3v2FrameIds::lCover || lhs == Id3v2FrameIds::sCover) {
691 return false;
692 }
693 if (rhs == Id3v2FrameIds::lCover || rhs == Id3v2FrameIds::sCover) {
694 return true;
695 }
696 return lhs < rhs;
697}
698
709void Id3v2Tag::removeOldRecordDateRelatedFields()
710{
712 fields().erase(field);
713 }
714}
715
719void Id3v2Tag::prepareRecordDataForMaking(const std::string &diagContext, Diagnostics &diag)
720{
721 // get rid of lYear/lRecordingDates/lDate/lTime/sYear/sRecordingDates/sDate/sTime if writing v2.4.0 or newer
722 // note: If the tag was initially v2.3.0 or older the "old" fields have already been converted to lRecordingTime when
723 // parsing and the generic accessors propose using lRecordingTime in any case.
724 if (majorVersion() >= 4) {
725 removeOldRecordDateRelatedFields();
726 return;
727 }
728
729 // convert lRecordingTime to old fields for v2.3.0 and older
730 const auto recordingTimeFieldIterator = fields().find(Id3v2FrameIds::lRecordingTime);
731 // -> If the auto-created lRecordingTime field (see note above) has been completely removed write the old fields as-is.
732 // This allows one to bypass this handling and set the old fields explicitly.
733 if (recordingTimeFieldIterator == fields().cend()) {
734 return;
735 }
736 // -> simply remove all old fields if lRecordingTime is set to an empty value
737 const auto &recordingTime = recordingTimeFieldIterator->second.value();
738 if (recordingTime.isEmpty()) {
739 removeOldRecordDateRelatedFields();
740 return;
741 }
742 // -> convert lRecordingTime (which is supposed to be an ISO string) to a DateTime
743 try {
744 const auto dateTimeExpr = recordingTime.toDateTimeExpression();
745 const auto &asDateTime = dateTimeExpr.value;
746 // -> remove any existing old fields to avoid any leftovers
747 removeOldRecordDateRelatedFields();
748 // -> assign old fields from parsed DateTime
749 std::stringstream year, date, time;
750 if (dateTimeExpr.parts & DateTimeParts::Year) {
751 year << std::setfill('0') << std::setw(4) << asDateTime.year();
752 setValue(Id3v2FrameIds::lYear, TagValue(year.str()));
753 }
754 if (dateTimeExpr.parts & (DateTimeParts::Day | DateTimeParts::Month)) {
755 date << std::setfill('0') << std::setw(2) << asDateTime.day() << std::setfill('0') << std::setw(2) << asDateTime.month();
756 setValue(Id3v2FrameIds::lDate, TagValue(date.str()));
757 }
758 if (dateTimeExpr.parts & DateTimeParts::Time) {
759 time << std::setfill('0') << std::setw(2) << asDateTime.hour() << std::setfill('0') << std::setw(2) << asDateTime.minute();
760 setValue(Id3v2FrameIds::lTime, TagValue(time.str()));
761 }
762 if (dateTimeExpr.parts & (DateTimeParts::Second | DateTimeParts::SubSecond)) {
763 diag.emplace_back(DiagLevel::Warning,
764 "The recording time field (TDRC) has been truncated to full minutes when converting to corresponding fields for older ID3v2 "
765 "versions.",
766 diagContext);
767 }
768 } catch (const ConversionException &e) {
769 try {
770 diag.emplace_back(DiagLevel::Critical,
771 argsToString("Unable to convert recording time field (TDRC) with the value \"", recordingTime.toString(),
772 "\" to corresponding fields for older ID3v2 versions: ", e.what()),
773 diagContext);
774 } catch (const ConversionException &) {
775 diag.emplace_back(DiagLevel::Critical,
776 argsToString("Unable to convert recording time field (TRDA) to corresponding fields for older ID3v2 versions: ", e.what()),
777 diagContext);
778 }
779 }
780 // -> get rid of lRecordingTime
782}
783
788Id3v2TagMaker::Id3v2TagMaker(Id3v2Tag &tag, Diagnostics &diag)
789 : m_tag(tag)
790 , m_framesSize(0)
791{
792 static const string context("making ID3v2 tag");
793
794 // check if version is supported
795 // (the version could have been changed using setVersion())
796 if (!tag.isVersionSupported()) {
797 diag.emplace_back(DiagLevel::Critical, "The ID3v2 tag version isn't supported.", context);
798 throw VersionNotSupportedException();
799 }
800
801 if (m_tag.m_handlingFlags & Id3v2HandlingFlags::ConvertRecordDateFields) {
802 tag.prepareRecordDataForMaking(context, diag);
803 }
804
805 // prepare frames
806 m_maker.reserve(tag.fields().size());
807 for (auto &pair : tag.fields()) {
808 try {
809 m_maker.emplace_back(pair.second.prepareMaking(tag.majorVersion(), diag));
810 m_framesSize += m_maker.back().requiredSize();
811 } catch (const Failure &) {
812 }
813 }
814
815 // calculate required size
816 // -> header + size of frames
817 m_requiredSize = 10 + m_framesSize;
818}
819
827void Id3v2TagMaker::make(std::ostream &stream, std::uint32_t padding, Diagnostics &diag)
828{
829 CPP_UTILITIES_UNUSED(diag)
830
831 BinaryWriter writer(&stream);
832
833 // write header
834 // -> signature
835 writer.writeUInt24BE(0x494433u);
836 // -> version
837 writer.writeByte(m_tag.majorVersion());
838 writer.writeByte(m_tag.revisionVersion());
839 // -> flags, but without extended header or compression bit set
840 writer.writeByte(m_tag.flags() & 0xBF);
841 // -> size (excluding header)
842 writer.writeSynchsafeUInt32BE(m_framesSize + padding);
843
844 // write frames
845 for (auto &maker : m_maker) {
846 maker.make(writer);
847 }
848
849 // write padding
850 for (; padding; --padding) {
851 stream.put(0);
852 }
853}
854
855} // 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...
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
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
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.
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:827
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:618
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:627
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:606
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:486
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:147
void convertDataEncoding(TagTextEncoding encoding)
Converts the currently assigned text value to the specified encoding.
Definition tagvalue.cpp:921
bool isEmpty() const
Returns whether no or an empty value is assigned.
Definition tagvalue.h:490
void convertDescriptionEncoding(TagTextEncoding encoding)
Converts the assigned description to use the specified encoding.
Definition tagvalue.cpp:973
std::string m_version
Definition tag.h:203
std::uint64_t m_size
Definition tag.h:204
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.
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:29
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:646