Tag Parser 12.4.0
C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
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#include "../mediafileinfo.h"
7
8#include <c++utilities/conversion/stringbuilder.h>
9#include <c++utilities/conversion/stringconversion.h>
10
11#include <iostream>
12
13using namespace std;
14using namespace CppUtilities;
15
16namespace TagParser {
17
30
37bool Id3v2Tag::supportsMultipleValues(IdentifierType id) const
38{
40 return m_majorVersion > 3;
41 }
42 using namespace Id3v2FrameIds;
43 switch (id) {
44 case sUserDefinedText:
45 case lUserDefinedText:
46 case sUserDefinedURL:
47 case lUserDefinedURL:
48 case sRating:
49 case lRating:
50 case sComment:
51 case lComment:
52 case sCover:
53 case lCover:
54 case sUnsynchronizedLyrics:
55 case lUnsynchronizedLyrics:
56 case sSynchronizedLyrics:
57 case lSynchronizedLyrics:
58 case lPublisherWebpage:
59 return true;
60 default:
61 return false;
62 }
63}
64
66{
67 const auto encoding = proposedTextEncoding();
68 for (auto &field : fields()) {
69 auto &value = field.second.value();
70 value.convertDataEncoding(encoding);
72 }
73}
74
78void Id3v2Tag::internallyGetValuesFromField(const Id3v2Tag::FieldType &field, std::vector<const TagValue *> &values) const
79{
80 if (!field.value().isEmpty()) {
81 values.emplace_back(&field.value());
82 }
83 for (const auto &value : field.additionalValues()) {
84 if (!value.isEmpty()) {
85 values.emplace_back(&value);
86 }
87 }
88}
89
96bool Id3v2Tag::internallySetValues(const IdentifierType &id, const std::vector<TagValue> &values)
97{
98 // use default implementation for non-text frames
101 }
102
103 // find existing text frame
104 auto range = fields().equal_range(id);
105 auto frameIterator = range.first;
106
107 // use existing frame or insert new text frame
108 auto valuesIterator = values.cbegin();
109 if (frameIterator != range.second) {
110 ++range.first;
111 // add primary value to existing frame
112 if (valuesIterator != values.cend()) {
113 frameIterator->second.setValue(*valuesIterator);
114 ++valuesIterator;
115 } else {
116 frameIterator->second.value().clearDataAndMetadata();
117 }
118 } else {
119 // skip if there is no existing frame but also no values to be assigned
120 if (valuesIterator == values.cend()) {
121 return true;
122 }
123 // add primary value to new frame
124 frameIterator = fields().insert(make_pair(id, Id3v2Frame(id, *valuesIterator)));
125 ++valuesIterator;
126 }
127
128 // add additional values to frame
129 frameIterator->second.additionalValues() = vector<TagValue>(valuesIterator, values.cend());
130
131 // remove remaining existing values (there are more existing values than specified ones)
132 for (; range.first != range.second; ++range.first) {
133 range.first->second.setValue(TagValue());
134 }
135 return true;
136}
137
139{
140 using namespace Id3v2FrameIds;
141 if (m_majorVersion >= 3) {
142 switch (field) {
144 return lAlbum;
146 return lArtist;
148 return lComment;
150 return lRecordingTime; // (de)serializer converts to/from lYear/lRecordingDates/lDate/lTime
152 return lReleaseTime;
154 return lTitle;
156 return lGenre;
158 return lTrackPosition;
160 return lDiskPosition;
162 return lEncoder;
163 case KnownField::Bpm:
164 return lBpm;
166 return lCover;
168 return lWriter;
170 return lLength;
172 return lLanguage;
174 return lEncoderSettings;
176 return lUnsynchronizedLyrics;
178 return lSynchronizedLyrics;
180 return lContentGroupDescription;
182 return lRecordLabel;
184 return lComposer;
186 return lPlayCounter;
188 return lRating;
190 return lAlbumArtist;
192 return lRemixedBy;
194 return lCopyright;
196 return lTaggingTime;
198 return lEncodingTime;
200 return lOriginalReleaseTime;
202 return lOriginalMediaType;
203 case KnownField::Mood:
204 return lMood;
206 return lKey;
208 return lPublisherWebpage;
210 return lPerformerWebpage;
212 return lPaymentWebpage;
213 default:;
214 }
215 } else {
216 switch (field) {
218 return sAlbum;
220 return sArtist;
222 return sComment;
224 return lRecordingTime; // (de)serializer converts to/from sYear/sRecordingDates/sDate/sTime
226 return sTitle;
228 return sGenre;
230 return sTrackPosition;
232 return sDiskPosition;
234 return sEncoder;
235 case KnownField::Bpm:
236 return sBpm;
238 return sCover;
240 return sWriter;
242 return sLength;
244 return sLanguage;
246 return sEncoderSettings;
248 return sUnsynchronizedLyrics;
250 return sSynchronizedLyrics;
252 return sContentGroupDescription;
254 return sRecordLabel;
256 return sComposer;
258 return sPlayCounter;
260 return sRating;
262 return sAlbumArtist;
264 return sRemixedBy;
266 return sCopyright;
268 return sOriginalMediaType;
270 return sKey;
272 return sPublisherWebpage;
274 return sPerformerWebpage;
275 default:;
276 }
277 }
278 return 0;
279}
280
281KnownField Id3v2Tag::internallyGetKnownField(const IdentifierType &id) const
282{
283 using namespace Id3v2FrameIds;
284 switch (id) {
285 case lAlbum:
286 return KnownField::Album;
287 case lArtist:
288 return KnownField::Artist;
289 case lComment:
290 return KnownField::Comment;
291 case lRecordingTime:
292 case lYear:
294 case lTitle:
295 return KnownField::Title;
296 case lGenre:
297 return KnownField::Genre;
298 case lTrackPosition:
300 case lDiskPosition:
302 case lEncoder:
303 return KnownField::Encoder;
304 case lBpm:
305 return KnownField::Bpm;
306 case lCover:
307 return KnownField::Cover;
308 case lWriter:
310 case lLanguage:
312 case lLength:
313 return KnownField::Length;
314 case lEncoderSettings:
316 case lUnsynchronizedLyrics:
317 return KnownField::Lyrics;
318 case lSynchronizedLyrics:
320 case lAlbumArtist:
322 case lRemixedBy:
324 case lCopyright:
326 case lContentGroupDescription:
328 case lRecordLabel:
330 case lTaggingTime:
332 case lEncodingTime:
334 case lReleaseTime:
336 case lOriginalReleaseTime:
338 case lOriginalMediaType:
340 case lMood:
341 return KnownField::Mood;
342 case lPlayCounter:
344 case lRating:
345 return KnownField::Rating;
346 case lISRC:
347 return KnownField::ISRC;
348 case lKey:
350 case lComposer:
352 case lPublisherWebpage:
354 case lPerformerWebpage:
356 case lPaymentWebpage:
358 case sAlbum:
359 return KnownField::Album;
360 case sArtist:
361 return KnownField::Artist;
362 case sComment:
363 return KnownField::Comment;
364 case sYear:
366 case sTitle:
367 return KnownField::Title;
368 case sGenre:
369 return KnownField::Genre;
370 case sTrackPosition:
372 case sDiskPosition:
374 case sEncoder:
375 return KnownField::Encoder;
376 case sBpm:
377 return KnownField::Bpm;
378 case sCover:
379 return KnownField::Cover;
380 case sWriter:
382 case sLanguage:
384 case sLength:
385 return KnownField::Length;
386 case sEncoderSettings:
388 case sUnsynchronizedLyrics:
389 return KnownField::Lyrics;
390 case sSynchronizedLyrics:
392 case sAlbumArtist:
394 case sRecordLabel:
396 case sRemixedBy:
398 case sCopyright:
400 case sPlayCounter:
402 case sRating:
403 return KnownField::Rating;
404 case sISRC:
405 return KnownField::ISRC;
406 case sOriginalMediaType:
408 case sKey:
410 case sComposer:
412 case sContentGroupDescription:
414 case sPublisherWebpage:
416 case sPerformerWebpage:
418 default:
419 return KnownField::Invalid;
420 }
421}
422
424{
425 using namespace Id3v2FrameIds;
426 switch (id) {
427 case lLength:
428 case sLength:
430 case lBpm:
431 case sBpm:
432 case lYear:
433 case sYear:
434 case lPlayCounter:
435 case sPlayCounter:
437 case lTrackPosition:
438 case sTrackPosition:
439 case lDiskPosition:
441 case lCover:
442 case sCover:
444 case lRating:
445 case sRating:
447 default:
449 return TagDataType::Text;
450 } else {
452 }
453 }
454}
455
462void Id3v2Tag::convertOldRecordDateFields(const std::string &diagContext, Diagnostics &diag)
463{
464 // skip if it is a v2.4.0 tag and lRecordingTime is present
465 if (majorVersion() >= 4 && fields().find(Id3v2FrameIds::lRecordingTime) != fields().cend()) {
466 return;
467 }
468
469 // parse values of lYear/lRecordingDates/lDate/lTime/sYear/sRecordingDates/sDate/sTime fields
470 auto expr = DateTimeExpression();
471 auto year = 1, month = 1, day = 1, hour = 0, minute = 0;
472 if (const auto &v = value(Id3v2FrameIds::lYear)) {
473 expr.parts |= DateTimeParts::Year;
474 try {
475 year = v.toInteger();
476 } catch (const ConversionException &e) {
477 diag.emplace_back(DiagLevel::Critical, argsToString("Unable to parse year from \"TYER\" frame: ", e.what()), diagContext);
478 }
479 }
480 if (const auto &v = value(Id3v2FrameIds::lDate)) {
481 expr.parts |= DateTimeParts::Day | DateTimeParts::Month;
482 try {
483 auto str = v.toString();
484 if (str.size() != 4) {
485 throw ConversionException("format is not DDMM");
486 }
487 day = stringToNumber<unsigned short>(std::string_view(str.data() + 0, 2));
488 month = stringToNumber<unsigned short>(std::string_view(str.data() + 2, 2));
489 } catch (const ConversionException &e) {
490 diag.emplace_back(DiagLevel::Critical, argsToString("Unable to parse month and day from \"TDAT\" frame: ", e.what()), diagContext);
491 }
492 }
493 if (const auto &v = value(Id3v2FrameIds::lTime)) {
494 expr.parts |= DateTimeParts::Hour | DateTimeParts::Minute;
495 try {
496 auto str = v.toString();
497 if (str.size() != 4) {
498 throw ConversionException("format is not HHMM");
499 }
500 hour = stringToNumber<unsigned short>(std::string_view(str.data() + 0, 2));
501 minute = stringToNumber<unsigned short>(std::string_view(str.data() + 2, 2));
502 } catch (const ConversionException &e) {
503 diag.emplace_back(DiagLevel::Critical, argsToString("Unable to parse hour and minute from \"TIME\" frame: ", +e.what()), diagContext);
504 }
505 }
506
507 // set the field values as DateTime
508 if (expr.parts == DateTimeParts::None) {
509 return;
510 }
511 try {
512 expr.value = DateTime::fromDateAndTime(year, month, day, hour, minute);
514 } catch (const ConversionException &e) {
515 try {
516 // try to set at least the year
517 expr.parts = DateTimeParts::Year;
518 expr.value = DateTime::fromDate(year);
520 diag.emplace_back(DiagLevel::Critical,
521 argsToString(
522 "Unable to parse the full date of the recording. Only the 'Year' frame could be parsed; related frames failed: ", e.what()),
523 diagContext);
524 } catch (const ConversionException &) {
525 }
526 diag.emplace_back(
527 DiagLevel::Critical, argsToString("Unable to parse a valid date from the 'Year' frame and related frames: ", e.what()), diagContext);
528 }
529}
530
538void Id3v2Tag::parse(istream &stream, const std::uint64_t maximalSize, Diagnostics &diag)
539{
540 // prepare parsing
541 static const string context("parsing ID3v2 tag");
542 BinaryReader reader(&stream);
543 const auto startOffset = static_cast<std::uint64_t>(stream.tellg());
544
545 // check whether the header is truncated
546 if (maximalSize && maximalSize < 10) {
547 diag.emplace_back(DiagLevel::Critical, "ID3v2 header is truncated (at least 10 bytes expected).", context);
549 }
550
551 // read signature: ID3
552 if (reader.readUInt24BE() != 0x494433u) {
553 diag.emplace_back(DiagLevel::Critical, "Signature is invalid.", context);
554 throw InvalidDataException();
555 }
556 // read header data
557 const std::uint8_t majorVersion = reader.readByte();
558 const std::uint8_t revisionVersion = reader.readByte();
560 m_flags = reader.readByte();
561 m_sizeExcludingHeader = reader.readSynchsafeUInt32BE();
562 m_size = 10 + m_sizeExcludingHeader;
563 if (m_sizeExcludingHeader == 0) {
564 diag.emplace_back(DiagLevel::Warning, "ID3v2 tag seems to be empty.", context);
565 return;
566 }
567
568 // check if the version
569 if (!isVersionSupported()) {
570 diag.emplace_back(DiagLevel::Critical, "The ID3v2 tag couldn't be parsed, because its version is not supported.", context);
572 }
573
574 // read extended header (if present)
575 if (hasExtendedHeader()) {
576 if (maximalSize && maximalSize < 14) {
577 diag.emplace_back(DiagLevel::Critical, "Extended header denoted but not present.", context);
579 }
580 m_extendedHeaderSize = reader.readSynchsafeUInt32BE();
581 if (m_extendedHeaderSize < 6 || m_extendedHeaderSize > m_sizeExcludingHeader || (maximalSize && maximalSize < (10 + m_extendedHeaderSize))) {
582 diag.emplace_back(DiagLevel::Critical, "Extended header is invalid/truncated.", context);
584 }
585 stream.seekg(m_extendedHeaderSize - 4, ios_base::cur);
586 }
587
588 // how many bytes remain for frames and padding?
589 std::uint32_t bytesRemaining = m_sizeExcludingHeader - m_extendedHeaderSize;
590 if (maximalSize && bytesRemaining > maximalSize) {
591 bytesRemaining = static_cast<std::uint32_t>(maximalSize);
592 diag.emplace_back(DiagLevel::Critical, "Frames are truncated.", context);
593 }
594
595 // read frames
596 auto pos = static_cast<std::uint64_t>(stream.tellg());
597 while (bytesRemaining) {
598 // seek to next frame
599 stream.seekg(static_cast<streamoff>(pos));
600 // parse frame
601 Id3v2Frame frame;
602 try {
603 frame.parse(reader, majorVersion, bytesRemaining, diag);
604 if (Id3v2FrameIds::isTextFrame(frame.id()) && fields().count(frame.id()) == 1) {
605 diag.emplace_back(DiagLevel::Warning, "The text frame " % frame.idToString() + " exists more than once.", context);
606 }
607 fields().emplace(frame.id(), std::move(frame));
608 } catch (const NoDataFoundException &) {
609 if (frame.hasPaddingReached()) {
610 m_paddingSize = startOffset + m_size - pos;
611 break;
612 }
613 } catch (const Failure &) {
614 }
615
616 // calculate next frame offset
617 if (frame.totalSize() <= bytesRemaining) {
618 pos += frame.totalSize();
619 bytesRemaining -= frame.totalSize();
620 } else {
621 pos += bytesRemaining;
622 bytesRemaining = 0;
623 }
624 }
625
626 if (m_handlingFlags & Id3v2HandlingFlags::ConvertRecordDateFields) {
627 convertOldRecordDateFields(context, diag);
628 }
629
630 // check for extended header
631 if (!hasFooter()) {
632 return;
633 }
634 if (maximalSize && m_size + 10 < maximalSize) {
635 // the footer does not provide additional information, just check the signature
636 stream.seekg(static_cast<streamoff>(startOffset + (m_size += 10)));
637 if (reader.readUInt24LE() != 0x494433u) {
638 diag.emplace_back(DiagLevel::Critical, "Footer signature is invalid.", context);
639 }
640 // skip remaining footer
641 stream.seekg(7, ios_base::cur);
642 } else {
643 diag.emplace_back(DiagLevel::Critical, "Footer denoted but not present.", context);
645 }
646}
647
659{
660 return Id3v2TagMaker(*this, diag);
661}
662
670void Id3v2Tag::make(ostream &stream, std::uint32_t padding, Diagnostics &diag)
671{
672 prepareMaking(diag).make(stream, padding, diag);
673}
674
679void Id3v2Tag::setVersion(std::uint8_t majorVersion, std::uint8_t revisionVersion)
680{
681 m_majorVersion = majorVersion;
682 m_revisionVersion = revisionVersion;
683 m_version = argsToString('2', '.', majorVersion, '.', revisionVersion);
684}
685
698bool FrameComparer::operator()(std::uint32_t lhs, std::uint32_t rhs) const
699{
700 if (lhs == rhs) {
701 return false;
702 }
703
704 const bool lhsLong = Id3v2FrameIds::isLongId(lhs);
705 const bool rhsLong = Id3v2FrameIds::isLongId(rhs);
706 if (lhsLong != rhsLong) {
707 if (!lhsLong) {
709 if (!lhs) {
710 return true;
711 }
712 } else if (!rhsLong) {
714 if (!rhs) {
715 return true;
716 }
717 }
718 }
719
721 return true;
722 }
724 return false;
725 }
726 if (lhs == Id3v2FrameIds::lTitle || lhs == Id3v2FrameIds::sTitle) {
727 return true;
728 }
729 if (rhs == Id3v2FrameIds::lTitle || rhs == Id3v2FrameIds::sTitle) {
730 return false;
731 }
732
733 const bool lhstextfield = Id3v2FrameIds::isTextFrame(lhs);
734 const bool rhstextfield = Id3v2FrameIds::isTextFrame(rhs);
735 if (lhstextfield && !rhstextfield) {
736 return true;
737 }
738 if (!lhstextfield && rhstextfield) {
739 return false;
740 }
741
742 if (lhs == Id3v2FrameIds::lCover || lhs == Id3v2FrameIds::sCover) {
743 return false;
744 }
745 if (rhs == Id3v2FrameIds::lCover || rhs == Id3v2FrameIds::sCover) {
746 return true;
747 }
748 return lhs < rhs;
749}
750
761void Id3v2Tag::removeOldRecordDateRelatedFields()
762{
764 fields().erase(field);
765 }
766}
767
771void Id3v2Tag::prepareRecordDataForMaking(const std::string &diagContext, Diagnostics &diag)
772{
773 // get rid of lYear/lRecordingDates/lDate/lTime/sYear/sRecordingDates/sDate/sTime if writing v2.4.0 or newer
774 // note: If the tag was initially v2.3.0 or older the "old" fields have already been converted to lRecordingTime when
775 // parsing and the generic accessors propose using lRecordingTime in any case.
776 if (majorVersion() >= 4) {
777 removeOldRecordDateRelatedFields();
778 return;
779 }
780
781 // convert lRecordingTime to old fields for v2.3.0 and older
782 const auto recordingTimeFieldIterator = fields().find(Id3v2FrameIds::lRecordingTime);
783 // -> If the auto-created lRecordingTime field (see note above) has been completely removed write the old fields as-is.
784 // This allows one to bypass this handling and set the old fields explicitly.
785 if (recordingTimeFieldIterator == fields().cend()) {
786 return;
787 }
788 // -> simply remove all old fields if lRecordingTime is set to an empty value
789 const auto &recordingTime = recordingTimeFieldIterator->second.value();
790 if (recordingTime.isEmpty()) {
791 removeOldRecordDateRelatedFields();
792 return;
793 }
794 // -> convert lRecordingTime (which is supposed to be an ISO string) to a DateTime
795 try {
796 const auto dateTimeExpr = recordingTime.toDateTimeExpression();
797 const auto &asDateTime = dateTimeExpr.value;
798 // -> remove any existing old fields to avoid any leftovers
799 removeOldRecordDateRelatedFields();
800 // -> assign old fields from parsed DateTime
801 std::stringstream year, date, time;
802 if (dateTimeExpr.parts & DateTimeParts::Year) {
803 year << std::setfill('0') << std::setw(4) << asDateTime.year();
804 setValue(Id3v2FrameIds::lYear, TagValue(year.str()));
805 }
806 if (dateTimeExpr.parts & (DateTimeParts::Day | DateTimeParts::Month)) {
807 date << std::setfill('0') << std::setw(2) << asDateTime.day() << std::setfill('0') << std::setw(2) << asDateTime.month();
808 setValue(Id3v2FrameIds::lDate, TagValue(date.str()));
809 }
810 if (dateTimeExpr.parts & DateTimeParts::Time) {
811 time << std::setfill('0') << std::setw(2) << asDateTime.hour() << std::setfill('0') << std::setw(2) << asDateTime.minute();
812 setValue(Id3v2FrameIds::lTime, TagValue(time.str()));
813 }
814 if (dateTimeExpr.parts & (DateTimeParts::Second | DateTimeParts::SubSecond)) {
815 diag.emplace_back(DiagLevel::Warning,
816 "The recording time field (TDRC) has been truncated to full minutes when converting to corresponding fields for older ID3v2 "
817 "versions.",
818 diagContext);
819 }
820 } catch (const ConversionException &e) {
821 try {
822 diag.emplace_back(DiagLevel::Critical,
823 argsToString("Unable to convert recording time field (TDRC) with the value \"", recordingTime.toString(),
824 "\" to corresponding fields for older ID3v2 versions: ", e.what()),
825 diagContext);
826 } catch (const ConversionException &) {
827 diag.emplace_back(DiagLevel::Critical,
828 argsToString("Unable to convert recording time field (TRDA) to corresponding fields for older ID3v2 versions: ", e.what()),
829 diagContext);
830 }
831 }
832 // -> get rid of lRecordingTime
834}
835
840Id3v2TagMaker::Id3v2TagMaker(Id3v2Tag &tag, Diagnostics &diag)
841 : m_tag(tag)
842 , m_framesSize(0)
843{
844 static const string context("making ID3v2 tag");
845
846 // check if version is supported
847 // (the version could have been changed using setVersion())
848 if (!tag.isVersionSupported()) {
849 diag.emplace_back(DiagLevel::Critical, "The ID3v2 tag version isn't supported.", context);
850 throw VersionNotSupportedException();
851 }
852
853 if (m_tag.m_handlingFlags & Id3v2HandlingFlags::ConvertRecordDateFields) {
854 tag.prepareRecordDataForMaking(context, diag);
855 }
856
857 // prepare frames
858 m_maker.reserve(tag.fields().size());
859 for (auto &pair : tag.fields()) {
860 try {
861 m_maker.emplace_back(pair.second.prepareMaking(tag.majorVersion(), diag));
862 m_framesSize += m_maker.back().requiredSize();
863 } catch (const Failure &) {
864 }
865 }
866
867 // calculate required size
868 // -> header + size of frames
869 m_requiredSize = 10 + m_framesSize;
870}
871
879void Id3v2TagMaker::make(std::ostream &stream, std::uint32_t padding, Diagnostics &diag)
880{
881 CPP_UTILITIES_UNUSED(diag)
882
883 BinaryWriter writer(&stream);
884
885 // write header
886 // -> signature
887 writer.writeUInt24BE(0x494433u);
888 // -> version
889 writer.writeByte(m_tag.majorVersion());
890 writer.writeByte(m_tag.revisionVersion());
891 // -> flags, but without extended header or compression bit set
892 writer.writeByte(m_tag.flags() & 0xBF);
893 // -> size (excluding header)
894 writer.writeSynchsafeUInt32BE(m_framesSize + padding);
895
896 // write frames
897 for (auto &maker : m_maker) {
898 maker.make(writer);
899 }
900
901 MediaFileInfo::writePadding(stream, padding);
902}
903
904} // 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)
typename FieldMapBasedTagTraits< Id3v2Tag >::FieldType::IdentifierType IdentifierType
const TagValue & value(const IdentifierType &id) const
const std::multimap< IdentifierType, FieldType, Compare > & fields() const
bool internallySetValues(const IdentifierType &id, const std::vector< TagValue > &values)
typename FieldMapBasedTagTraits< Id3v2Tag >::FieldType FieldType
std::vector< const TagValue * > values(const IdentifierType &id) const
IdentifierType fieldId(KnownField value) const
The Id3v2Frame class is used by Id3v2Tag to store the fields.
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.
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:879
std::uint8_t revisionVersion() const
Returns the revision version if known; otherwise returns 0.
Definition id3v2tag.h:205
void make(std::ostream &targetStream, std::uint32_t padding, Diagnostics &diag)
Writes tag information to the specified stream.
Definition id3v2tag.cpp:670
bool hasExtendedHeader() const
Returns an indication whether an extended header is used.
Definition id3v2tag.h:240
bool isVersionSupported() const
Returns an indication whether the version is supported by the Id3v2Tag class.
Definition id3v2tag.h:216
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:679
KnownField internallyGetKnownField(const IdentifierType &id) const
Definition id3v2tag.cpp:281
std::uint8_t flags() const
Returns the flags read from the ID3v2 header.
Definition id3v2tag.h:224
std::uint8_t majorVersion() const
Returns the major version if known; otherwise returns 0.
Definition id3v2tag.h:197
bool hasFooter() const
Returns an indication whether a footer is present.
Definition id3v2tag.h:256
void internallyGetValuesFromField(const FieldType &field, std::vector< const TagValue * > &values) const
Adds additional values as well.
Definition id3v2tag.cpp:78
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:96
TagTextEncoding proposedTextEncoding() const override
Returns the proposed text encoding.
Definition id3v2tag.h:150
TagDataType internallyGetProposedDataType(const std::uint32_t &id) const
Definition id3v2tag.cpp:423
Id3v2TagMaker prepareMaking(Diagnostics &diag)
Prepares making.
Definition id3v2tag.cpp:658
void ensureTextValuesAreProperlyEncoded() override
Ensures the encoding of all assigned text values is supported by the tag by converting the character ...
Definition id3v2tag.cpp:65
IdentifierType internallyGetFieldId(KnownField field) const
Definition id3v2tag.cpp:138
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:538
The exception that is thrown when the data to be parsed or to be made seems invalid and therefore can...
static void writePadding(std::ostream &outputStream, uint64_t size)
Writes the specified number of zeroes to outputStream.
The exception that is thrown when the data to be parsed holds no parsable information (e....
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.
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:216
std::uint64_t m_size
Definition tag.h:217
The exception that is thrown when the data to be parsed is truncated and therefore can not be parsed ...
The exception that is thrown when an operation fails because the detected or specified version is not...
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:37
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:698