Tag Parser  10.0.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 
12 using namespace std;
13 using namespace CppUtilities;
14 
15 namespace TagParser {
16 
26 bool Id3v2Tag::supportsMultipleValues(KnownField field) const
27 {
28  switch (field) {
29  case KnownField::Album:
30  case KnownField::Artist:
31  case KnownField::RecordDate:
32  case KnownField::ReleaseDate:
33  case KnownField::Title:
34  case KnownField::Genre:
38  case KnownField::Bpm:
40  case KnownField::Length:
41  case KnownField::Language:
42  case KnownField::EncoderSettings:
47  return m_majorVersion > 3;
48  case KnownField::Rating:
50  case KnownField::Cover:
51  case KnownField::Lyrics:
52  case KnownField::SynchronizedLyrics:
53  return true;
54  default:
55  return false;
56  }
57 }
58 
59 void Id3v2Tag::ensureTextValuesAreProperlyEncoded()
60 {
61  const auto encoding = proposedTextEncoding();
62  for (auto &field : fields()) {
63  auto &value = field.second.value();
64  value.convertDataEncoding(encoding);
65  value.convertDescriptionEncoding(encoding);
66  }
67 }
68 
72 void 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 
90 bool Id3v2Tag::internallySetValues(const IdentifierType &id, const std::vector<TagValue> &values)
91 {
92  // use default implementation for non-text frames
93  if (!Id3v2FrameIds::isTextFrame(id)) {
94  return CRTPBase::internallySetValues(id, values);
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 
132 Id3v2Tag::IdentifierType Id3v2Tag::internallyGetFieldId(KnownField field) const
133 {
134  using namespace Id3v2FrameIds;
135  if (m_majorVersion >= 3) {
136  switch (field) {
137  case KnownField::Album:
138  return lAlbum;
139  case KnownField::Artist:
140  return lArtist;
141  case KnownField::Comment:
142  return lComment;
143  case KnownField::RecordDate:
144  return lRecordingTime; // (de)serializer takes to convert to/from lYear/lRecordingDates/lDate/lTime
145  case KnownField::ReleaseDate:
146  return lReleaseTime;
147  case KnownField::Title:
148  return lTitle;
149  case KnownField::Genre:
150  return lGenre;
152  return lTrackPosition;
154  return lDiskPosition;
155  case KnownField::Encoder:
156  return lEncoder;
157  case KnownField::Bpm:
158  return lBpm;
159  case KnownField::Cover:
160  return lCover;
162  return lWriter;
163  case KnownField::Length:
164  return lLength;
165  case KnownField::Language:
166  return lLanguage;
167  case KnownField::EncoderSettings:
168  return lEncoderSettings;
169  case KnownField::Lyrics:
170  return lUnsynchronizedLyrics;
171  case KnownField::SynchronizedLyrics:
172  return lSynchronizedLyrics;
176  return lRecordLabel;
178  return lComposer;
179  case KnownField::Rating:
180  return lRating;
182  return lAlbumArtist;
183  default:;
184  }
185  } else {
186  switch (field) {
187  case KnownField::Album:
188  return sAlbum;
189  case KnownField::Artist:
190  return sArtist;
191  case KnownField::Comment:
192  return sComment;
193  case KnownField::RecordDate:
194  return lRecordingTime; // (de)serializer takes to convert to/from sYear/sRecordingDates/sDate/sTime
195  case KnownField::Title:
196  return sTitle;
197  case KnownField::Genre:
198  return sGenre;
200  return sTrackPosition;
202  return sDiskPosition;
203  case KnownField::Encoder:
204  return sEncoder;
205  case KnownField::Bpm:
206  return sBpm;
207  case KnownField::Cover:
208  return sCover;
210  return sWriter;
211  case KnownField::Length:
212  return sLength;
213  case KnownField::Language:
214  return sLanguage;
215  case KnownField::EncoderSettings:
216  return sEncoderSettings;
217  case KnownField::Lyrics:
218  return sUnsynchronizedLyrics;
219  case KnownField::SynchronizedLyrics:
220  return sSynchronizedLyrics;
224  return sRecordLabel;
226  return sComposer;
227  case KnownField::Rating:
228  return sRating;
230  return sAlbumArtist;
231  default:;
232  }
233  }
234  return 0;
235 }
236 
237 KnownField Id3v2Tag::internallyGetKnownField(const IdentifierType &id) const
238 {
239  using namespace Id3v2FrameIds;
240  switch (id) {
241  case lAlbum:
242  return KnownField::Album;
243  case lArtist:
244  return KnownField::Artist;
245  case lComment:
246  return KnownField::Comment;
247  case lRecordingTime:
248  case lYear:
249  return KnownField::RecordDate;
250  case lTitle:
251  return KnownField::Title;
252  case lGenre:
253  return KnownField::Genre;
254  case lTrackPosition:
256  case lDiskPosition:
258  case lEncoder:
259  return KnownField::Encoder;
260  case lBpm:
261  return KnownField::Bpm;
262  case lCover:
263  return KnownField::Cover;
264  case lWriter:
265  return KnownField::Lyricist;
266  case lLanguage:
267  return KnownField::Language;
268  case lLength:
269  return KnownField::Length;
270  case lEncoderSettings:
271  return KnownField::EncoderSettings;
273  return KnownField::Lyrics;
274  case lSynchronizedLyrics:
275  return KnownField::SynchronizedLyrics;
276  case lAlbumArtist:
279  return KnownField::Grouping;
280  case lRecordLabel:
282  case sAlbum:
283  return KnownField::Album;
284  case sArtist:
285  return KnownField::Artist;
286  case sComment:
287  return KnownField::Comment;
288  case sYear:
289  return KnownField::RecordDate;
290  case sTitle:
291  return KnownField::Title;
292  case sGenre:
293  return KnownField::Genre;
294  case sTrackPosition:
296  case sEncoder:
297  return KnownField::Encoder;
298  case sBpm:
299  return KnownField::Bpm;
300  case sCover:
301  return KnownField::Cover;
302  case sWriter:
303  return KnownField::Lyricist;
304  case sLanguage:
305  return KnownField::Language;
306  case sLength:
307  return KnownField::Length;
308  case sEncoderSettings:
309  return KnownField::EncoderSettings;
311  return KnownField::Lyrics;
312  case sSynchronizedLyrics:
313  return KnownField::SynchronizedLyrics;
314  case sAlbumArtist:
315  return KnownField::Grouping;
316  case sRecordLabel:
318  default:
319  return KnownField::Invalid;
320  }
321 }
322 
323 TagDataType Id3v2Tag::internallyGetProposedDataType(const std::uint32_t &id) const
324 {
325  using namespace Id3v2FrameIds;
326  switch (id) {
327  case lLength:
328  case sLength:
329  return TagDataType::TimeSpan;
330  case lBpm:
331  case sBpm:
332  case lYear:
333  case sYear:
334  return TagDataType::Integer;
335  case lTrackPosition:
336  case sTrackPosition:
337  case lDiskPosition:
338  return TagDataType::PositionInSet;
339  case lCover:
340  case sCover:
341  return TagDataType::Picture;
342  default:
343  if (Id3v2FrameIds::isTextFrame(id)) {
344  return TagDataType::Text;
345  } else {
346  return TagDataType::Undefined;
347  }
348  }
349 }
350 
357 void Id3v2Tag::convertOldRecordDateFields(const std::string &diagContext, Diagnostics &diag)
358 {
359  // skip if it is a v2.4.0 tag and lRecordingTime is present
360  if (majorVersion() >= 4 && fields().find(Id3v2FrameIds::lRecordingTime) != fields().cend()) {
361  return;
362  }
363 
364  // parse values of lYear/lRecordingDates/lDate/lTime/sYear/sRecordingDates/sDate/sTime fields
365  bool hasAnyValue = false;
366  int year = 1, month = 1, day = 1, hour = 0, minute = 0;
367  if (const auto &v = value(Id3v2FrameIds::lYear)) {
368  hasAnyValue = true;
369  try {
370  year = v.toInteger();
371  } catch (const ConversionException &e) {
372  diag.emplace_back(DiagLevel::Critical, argsToString("Unable to parse year from \"TYER\" frame: ", e.what()), diagContext);
373  }
374  }
375  if (const auto &v = value(Id3v2FrameIds::lDate)) {
376  hasAnyValue = true;
377  try {
378  auto str = v.toString();
379  if (str.size() != 4) {
380  throw ConversionException("format is not DDMM");
381  }
382  day = stringToNumber<unsigned short>(std::string_view(str.data() + 0, 2));
383  month = stringToNumber<unsigned short>(std::string_view(str.data() + 2, 2));
384  } catch (const ConversionException &e) {
385  diag.emplace_back(DiagLevel::Critical, argsToString("Unable to parse month and day from \"TDAT\" frame: ", e.what()), diagContext);
386  }
387  }
388  if (const auto &v = value(Id3v2FrameIds::lTime)) {
389  hasAnyValue = true;
390  try {
391  auto str = v.toString();
392  if (str.size() != 4) {
393  throw ConversionException("format is not HHMM");
394  }
395  hour = stringToNumber<unsigned short>(std::string_view(str.data() + 0, 2));
396  minute = stringToNumber<unsigned short>(std::string_view(str.data() + 2, 2));
397  } catch (const ConversionException &e) {
398  diag.emplace_back(DiagLevel::Critical, argsToString("Unable to parse hour and minute from \"TIME\" frame: ", +e.what()), diagContext);
399  }
400  }
401 
402  // set the field values as DateTime
403  if (!hasAnyValue) {
404  return;
405  }
406  try {
407  setValue(Id3v2FrameIds::lRecordingTime, TagValue(DateTime::fromDateAndTime(year, month, day, hour, minute)));
408  } catch (const ConversionException &e) {
409  try {
410  // try to set at least the year
411  setValue(Id3v2FrameIds::lRecordingTime, TagValue(DateTime::fromDate(year)));
412  diag.emplace_back(DiagLevel::Critical,
413  argsToString(
414  "Unable to parse the full date of the recording. Only the 'Year' frame could be parsed; related frames failed: ", e.what()),
415  diagContext);
416  } catch (const ConversionException &) {
417  }
418  diag.emplace_back(
419  DiagLevel::Critical, argsToString("Unable to parse a valid date from the 'Year' frame and related frames: ", e.what()), diagContext);
420  }
421 }
422 
430 void Id3v2Tag::parse(istream &stream, const std::uint64_t maximalSize, Diagnostics &diag)
431 {
432  // prepare parsing
433  static const string context("parsing ID3v2 tag");
434  BinaryReader reader(&stream);
435  const auto startOffset = static_cast<std::uint64_t>(stream.tellg());
436 
437  // check whether the header is truncated
438  if (maximalSize && maximalSize < 10) {
439  diag.emplace_back(DiagLevel::Critical, "ID3v2 header is truncated (at least 10 bytes expected).", context);
440  throw TruncatedDataException();
441  }
442 
443  // read signature: ID3
444  if (reader.readUInt24BE() != 0x494433u) {
445  diag.emplace_back(DiagLevel::Critical, "Signature is invalid.", context);
446  throw InvalidDataException();
447  }
448  // read header data
449  const std::uint8_t majorVersion = reader.readByte();
450  const std::uint8_t revisionVersion = reader.readByte();
451  setVersion(majorVersion, revisionVersion);
452  m_flags = reader.readByte();
453  m_sizeExcludingHeader = reader.readSynchsafeUInt32BE();
454  m_size = 10 + m_sizeExcludingHeader;
455  if (m_sizeExcludingHeader == 0) {
456  diag.emplace_back(DiagLevel::Warning, "ID3v2 tag seems to be empty.", context);
457  return;
458  }
459 
460  // check if the version
461  if (!isVersionSupported()) {
462  diag.emplace_back(DiagLevel::Critical, "The ID3v2 tag couldn't be parsed, because its version is not supported.", context);
464  }
465 
466  // read extended header (if present)
467  if (hasExtendedHeader()) {
468  if (maximalSize && maximalSize < 14) {
469  diag.emplace_back(DiagLevel::Critical, "Extended header denoted but not present.", context);
470  throw TruncatedDataException();
471  }
472  m_extendedHeaderSize = reader.readSynchsafeUInt32BE();
473  if (m_extendedHeaderSize < 6 || m_extendedHeaderSize > m_sizeExcludingHeader || (maximalSize && maximalSize < (10 + m_extendedHeaderSize))) {
474  diag.emplace_back(DiagLevel::Critical, "Extended header is invalid/truncated.", context);
475  throw TruncatedDataException();
476  }
477  stream.seekg(m_extendedHeaderSize - 4, ios_base::cur);
478  }
479 
480  // how many bytes remain for frames and padding?
481  std::uint32_t bytesRemaining = m_sizeExcludingHeader - m_extendedHeaderSize;
482  if (maximalSize && bytesRemaining > maximalSize) {
483  bytesRemaining = static_cast<std::uint32_t>(maximalSize);
484  diag.emplace_back(DiagLevel::Critical, "Frames are truncated.", context);
485  }
486 
487  // read frames
488  auto pos = static_cast<std::uint64_t>(stream.tellg());
489  while (bytesRemaining) {
490  // seek to next frame
491  stream.seekg(static_cast<streamoff>(pos));
492  // parse frame
493  Id3v2Frame frame;
494  try {
495  frame.parse(reader, majorVersion, bytesRemaining, diag);
496  if (Id3v2FrameIds::isTextFrame(frame.id()) && fields().count(frame.id()) == 1) {
497  diag.emplace_back(DiagLevel::Warning, "The text frame " % frame.idToString() + " exists more than once.", context);
498  }
499  fields().emplace(frame.id(), move(frame));
500  } catch (const NoDataFoundException &) {
501  if (frame.hasPaddingReached()) {
502  m_paddingSize = startOffset + m_size - pos;
503  break;
504  }
505  } catch (const Failure &) {
506  }
507 
508  // calculate next frame offset
509  if (frame.totalSize() <= bytesRemaining) {
510  pos += frame.totalSize();
511  bytesRemaining -= frame.totalSize();
512  } else {
513  pos += bytesRemaining;
514  bytesRemaining = 0;
515  }
516  }
517 
518  if (m_handlingFlags & Id3v2HandlingFlags::ConvertRecordDateFields) {
519  convertOldRecordDateFields(context, diag);
520  }
521 
522  // check for extended header
523  if (!hasFooter()) {
524  return;
525  }
526  if (maximalSize && m_size + 10 < maximalSize) {
527  // the footer does not provide additional information, just check the signature
528  stream.seekg(static_cast<streamoff>(startOffset + (m_size += 10)));
529  if (reader.readUInt24LE() != 0x494433u) {
530  diag.emplace_back(DiagLevel::Critical, "Footer signature is invalid.", context);
531  }
532  // skip remaining footer
533  stream.seekg(7, ios_base::cur);
534  } else {
535  diag.emplace_back(DiagLevel::Critical, "Footer denoted but not present.", context);
536  throw TruncatedDataException();
537  }
538 }
539 
550 Id3v2TagMaker Id3v2Tag::prepareMaking(Diagnostics &diag)
551 {
552  return Id3v2TagMaker(*this, diag);
553 }
554 
562 void Id3v2Tag::make(ostream &stream, std::uint32_t padding, Diagnostics &diag)
563 {
564  prepareMaking(diag).make(stream, padding, diag);
565 }
566 
571 void Id3v2Tag::setVersion(std::uint8_t majorVersion, std::uint8_t revisionVersion)
572 {
573  m_majorVersion = majorVersion;
574  m_revisionVersion = revisionVersion;
575  m_version = argsToString('2', '.', majorVersion, '.', revisionVersion);
576 }
577 
590 bool FrameComparer::operator()(std::uint32_t lhs, std::uint32_t rhs) const
591 {
592  if (lhs == rhs) {
593  return false;
594  }
595 
596  const bool lhsLong = Id3v2FrameIds::isLongId(lhs);
597  const bool rhsLong = Id3v2FrameIds::isLongId(rhs);
598  if (lhsLong != rhsLong) {
599  if (!lhsLong) {
601  if (!lhs) {
602  return true;
603  }
604  } else if (!rhsLong) {
606  if (!rhs) {
607  return true;
608  }
609  }
610  }
611 
613  return true;
614  }
616  return false;
617  }
618  if (lhs == Id3v2FrameIds::lTitle || lhs == Id3v2FrameIds::sTitle) {
619  return true;
620  }
621  if (rhs == Id3v2FrameIds::lTitle || rhs == Id3v2FrameIds::sTitle) {
622  return false;
623  }
624 
625  const bool lhstextfield = Id3v2FrameIds::isTextFrame(lhs);
626  const bool rhstextfield = Id3v2FrameIds::isTextFrame(rhs);
627  if (lhstextfield && !rhstextfield) {
628  return true;
629  }
630  if (!lhstextfield && rhstextfield) {
631  return false;
632  }
633 
634  if (lhs == Id3v2FrameIds::lCover || lhs == Id3v2FrameIds::sCover) {
635  return false;
636  }
637  if (rhs == Id3v2FrameIds::lCover || rhs == Id3v2FrameIds::sCover) {
638  return true;
639  }
640  return lhs < rhs;
641 }
642 
653 void Id3v2Tag::removeOldRecordDateRelatedFields()
654 {
656  fields().erase(field);
657  }
658 }
659 
663 void Id3v2Tag::prepareRecordDataForMaking(const std::string &diagContext, Diagnostics &diag)
664 {
665  // get rid of lYear/lRecordingDates/lDate/lTime/sYear/sRecordingDates/sDate/sTime if writing v2.4.0 or newer
666  // note: If the tag was initially v2.3.0 or older the "old" fields have already been converted to lRecordingTime when
667  // parsing and the generic accessors propose using lRecordingTime in any case.
668  if (majorVersion() >= 4) {
669  removeOldRecordDateRelatedFields();
670  return;
671  }
672 
673  // convert lRecordingTime to old fields for v2.3.0 and older
674  const auto recordingTimeFieldIterator = fields().find(Id3v2FrameIds::lRecordingTime);
675  // -> If the auto-created lRecordingTime field (see note above) has been completely removed write the old fields as-is.
676  // This allows one to bypass this handling and set the old fields explicitely.
677  if (recordingTimeFieldIterator == fields().cend()) {
678  return;
679  }
680  // -> simply remove all old fields if lRecordingTime is set to an empty value
681  const auto &recordingTime = recordingTimeFieldIterator->second.value();
682  if (recordingTime.isEmpty()) {
683  removeOldRecordDateRelatedFields();
684  return;
685  }
686  // -> convert lRecordingTime (which is supposed to be an ISO string) to a DateTime
687  try {
688  const auto asDateTime = recordingTime.toDateTime();
689  // -> remove any existing old fields to avoid any leftovers
690  removeOldRecordDateRelatedFields();
691  // -> assign old fields from parsed DateTime
692  std::stringstream year, date, time;
693  year << std::setfill('0') << std::setw(4) << asDateTime.year();
694  setValue(Id3v2FrameIds::lYear, TagValue(year.str()));
695  date << std::setfill('0') << std::setw(2) << asDateTime.day() << std::setfill('0') << std::setw(2) << asDateTime.month();
696  setValue(Id3v2FrameIds::lDate, TagValue(date.str()));
697  time << std::setfill('0') << std::setw(2) << asDateTime.hour() << std::setfill('0') << std::setw(2) << asDateTime.minute();
698  setValue(Id3v2FrameIds::lTime, TagValue(time.str()));
699  if (asDateTime.second() || asDateTime.millisecond()) {
700  diag.emplace_back(DiagLevel::Warning,
701  "The recording time field (TRDA) has been truncated to full minutes when converting to corresponding fields for older ID3v2 "
702  "versions.",
703  diagContext);
704  }
705  } catch (const ConversionException &e) {
706  try {
707  diag.emplace_back(DiagLevel::Critical,
708  argsToString("Unable to convert recording time field (TRDA) with the value \"", recordingTime.toString(),
709  "\" to corresponding fields for older ID3v2 versions: ", e.what()),
710  diagContext);
711  } catch (const ConversionException &) {
712  diag.emplace_back(DiagLevel::Critical,
713  argsToString("Unable to convert recording time field (TRDA) to corresponding fields for older ID3v2 versions: ", e.what()),
714  diagContext);
715  }
716  }
717  // -> get rid of lRecordingTime
718  fields().erase(Id3v2FrameIds::lRecordingTime);
719 }
720 
725 Id3v2TagMaker::Id3v2TagMaker(Id3v2Tag &tag, Diagnostics &diag)
726  : m_tag(tag)
727  , m_framesSize(0)
728 {
729  static const string context("making ID3v2 tag");
730 
731  // check if version is supported
732  // (the version could have been changed using setVersion())
733  if (!tag.isVersionSupported()) {
734  diag.emplace_back(DiagLevel::Critical, "The ID3v2 tag version isn't supported.", context);
735  throw VersionNotSupportedException();
736  }
737 
738  if (m_tag.m_handlingFlags & Id3v2HandlingFlags::ConvertRecordDateFields) {
739  tag.prepareRecordDataForMaking(context, diag);
740  }
741 
742  // prepare frames
743  m_maker.reserve(tag.fields().size());
744  for (auto &pair : tag.fields()) {
745  try {
746  m_maker.emplace_back(pair.second.prepareMaking(tag.majorVersion(), diag));
747  m_framesSize += m_maker.back().requiredSize();
748  } catch (const Failure &) {
749  }
750  }
751 
752  // calculate required size
753  // -> header + size of frames
754  m_requiredSize = 10 + m_framesSize;
755 }
756 
764 void Id3v2TagMaker::make(std::ostream &stream, std::uint32_t padding, Diagnostics &diag)
765 {
766  CPP_UTILITIES_UNUSED(diag)
767 
768  BinaryWriter writer(&stream);
769 
770  // write header
771  // -> signature
772  writer.writeUInt24BE(0x494433u);
773  // -> version
774  writer.writeByte(m_tag.majorVersion());
775  writer.writeByte(m_tag.revisionVersion());
776  // -> flags, but without extended header or compression bit set
777  writer.writeByte(m_tag.flags() & 0xBF);
778  // -> size (excluding header)
779  writer.writeSynchsafeUInt32BE(m_framesSize + padding);
780 
781  // write frames
782  for (auto &maker : m_maker) {
783  maker.make(writer);
784  }
785 
786  // write padding
787  for (; padding; --padding) {
788  stream.put(0);
789  }
790 }
791 
792 } // 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
typename FieldMapBasedTagTraits< Id3v2Tag >::FieldType::IdentifierType IdentifierType
Definition: fieldbasedtag.h:36
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:134
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:764
std::uint8_t revisionVersion() const
Returns the revision version if known; otherwise returns 0.
Definition: id3v2tag.h:204
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
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
const IdentifierType & id() const
Returns the id of the current TagField.
std::string idToString() const
The TagValue class wraps values of different types.
Definition: tagvalue.h:95
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:85
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:42
TagDataType
Specifies the data type.
Definition: tagvalue.h:74