Tag Parser  8.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 IoUtilities;
14 using namespace ConversionUtilities;
15 
16 namespace TagParser {
17 
27 bool Id3v2Tag::supportsMultipleValues(KnownField field) const
28 {
29  switch (field) {
30  case KnownField::Album:
31  case KnownField::Artist:
32  case KnownField::Year:
33  case KnownField::RecordDate:
34  case KnownField::Title:
35  case KnownField::Genre:
39  case KnownField::Bpm:
41  case KnownField::Length:
42  case KnownField::Language:
43  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 
63 std::vector<const TagValue *> Id3v2Tag::internallyGetValues(const IdentifierType &id) const
64 {
65  auto range = fields().equal_range(id);
66  std::vector<const TagValue *> values;
67  for (auto i = range.first; i != range.second; ++i) {
68  const auto &frame(i->second);
69  if (!frame.value().isEmpty()) {
70  values.push_back(&frame.value());
71  }
72  for (const auto &value : frame.additionalValues()) {
73  values.push_back(&value);
74  }
75  }
76  return values;
77 }
78 
85 bool Id3v2Tag::internallySetValues(const IdentifierType &id, const std::vector<TagValue> &values)
86 {
87  // use default implementation for non-text frames
88  if (!Id3v2FrameIds::isTextFrame(id)) {
89  return CRTPBase::internallySetValues(id, values);
90  }
91 
92  // find existing text frame
93  auto range = fields().equal_range(id);
94  auto frameIterator = range.first;
95 
96  // use existing frame or insert new text frame
97  auto valuesIterator = values.cbegin();
98  if (frameIterator != range.second) {
99  ++range.first;
100  // add primary value to existing frame
101  if (valuesIterator != values.cend()) {
102  frameIterator->second.setValue(*valuesIterator);
103  ++valuesIterator;
104  } else {
105  frameIterator->second.value().clearDataAndMetadata();
106  }
107  } else {
108  // skip if there is no existing frame but also no values to be assigned
109  if (valuesIterator == values.cend()) {
110  return true;
111  }
112  // add primary value to new frame
113  frameIterator = fields().insert(make_pair(id, Id3v2Frame(id, *valuesIterator)));
114  ++valuesIterator;
115  }
116 
117  // add additional values to frame
118  frameIterator->second.additionalValues() = vector<TagValue>(valuesIterator, values.cend());
119 
120  // remove remaining existing values (there are more existing values than specified ones)
121  for (; range.first != range.second; ++range.first) {
122  range.first->second.setValue(TagValue());
123  }
124  return true;
125 }
126 
127 Id3v2Tag::IdentifierType Id3v2Tag::internallyGetFieldId(KnownField field) const
128 {
129  using namespace Id3v2FrameIds;
130  if (m_majorVersion >= 3) {
131  switch (field) {
132  case KnownField::Album:
133  return lAlbum;
134  case KnownField::Artist:
135  return lArtist;
136  case KnownField::Comment:
137  return lComment;
138  case KnownField::Year:
139  return lYear;
140  case KnownField::RecordDate:
141  return lRecordDate;
142  case KnownField::Title:
143  return lTitle;
144  case KnownField::Genre:
145  return lGenre;
147  return lTrackPosition;
149  return lDiskPosition;
150  case KnownField::Encoder:
151  return lEncoder;
152  case KnownField::Bpm:
153  return lBpm;
154  case KnownField::Cover:
155  return lCover;
157  return lWriter;
158  case KnownField::Length:
159  return lLength;
160  case KnownField::Language:
161  return lLanguage;
162  case KnownField::EncoderSettings:
163  return lEncoderSettings;
164  case KnownField::Lyrics:
165  return lUnsynchronizedLyrics;
166  case KnownField::SynchronizedLyrics:
167  return lSynchronizedLyrics;
169  return lGrouping;
171  return lRecordLabel;
173  return lComposer;
174  case KnownField::Rating:
175  return lRating;
176  default:;
177  }
178  } else {
179  switch (field) {
180  case KnownField::Album:
181  return sAlbum;
182  case KnownField::Artist:
183  return sArtist;
184  case KnownField::Comment:
185  return sComment;
186  case KnownField::Year:
187  return sYear;
188  case KnownField::RecordDate:
189  return sRecordDate;
190  case KnownField::Title:
191  return sTitle;
192  case KnownField::Genre:
193  return sGenre;
195  return sTrackPosition;
197  return sDiskPosition;
198  case KnownField::Encoder:
199  return sEncoder;
200  case KnownField::Bpm:
201  return sBpm;
202  case KnownField::Cover:
203  return sCover;
205  return sWriter;
206  case KnownField::Length:
207  return sLength;
208  case KnownField::Language:
209  return sLanguage;
210  case KnownField::EncoderSettings:
211  return sEncoderSettings;
212  case KnownField::Lyrics:
213  return sUnsynchronizedLyrics;
214  case KnownField::SynchronizedLyrics:
215  return sSynchronizedLyrics;
217  return sGrouping;
219  return sRecordLabel;
221  return sComposer;
222  case KnownField::Rating:
223  return sRating;
224  default:;
225  }
226  }
227  return 0;
228 }
229 
230 KnownField Id3v2Tag::internallyGetKnownField(const IdentifierType &id) const
231 {
232  using namespace Id3v2FrameIds;
233  switch (id) {
234  case lAlbum:
235  return KnownField::Album;
236  case lArtist:
237  return KnownField::Artist;
238  case lComment:
239  return KnownField::Comment;
240  case lYear:
241  return KnownField::Year;
242  case lRecordDate:
243  return KnownField::RecordDate;
244  case lTitle:
245  return KnownField::Title;
246  case lGenre:
247  return KnownField::Genre;
248  case lTrackPosition:
250  case lDiskPosition:
252  case lEncoder:
253  return KnownField::Encoder;
254  case lBpm:
255  return KnownField::Bpm;
256  case lCover:
257  return KnownField::Cover;
258  case lWriter:
259  return KnownField::Lyricist;
260  case lLanguage:
261  return KnownField::Language;
262  case lLength:
263  return KnownField::Length;
264  case lEncoderSettings:
265  return KnownField::EncoderSettings;
267  return KnownField::Lyrics;
268  case lSynchronizedLyrics:
269  return KnownField::SynchronizedLyrics;
270  case lGrouping:
271  return KnownField::Grouping;
272  case lRecordLabel:
274  case sAlbum:
275  return KnownField::Album;
276  case sArtist:
277  return KnownField::Artist;
278  case sComment:
279  return KnownField::Comment;
280  case sYear:
281  return KnownField::Year;
282  case sRecordDate:
283  return KnownField::RecordDate;
284  case sTitle:
285  return KnownField::Title;
286  case sGenre:
287  return KnownField::Genre;
288  case sTrackPosition:
290  case sEncoder:
291  return KnownField::Encoder;
292  case sBpm:
293  return KnownField::Bpm;
294  case sCover:
295  return KnownField::Cover;
296  case sWriter:
297  return KnownField::Lyricist;
298  case sLanguage:
299  return KnownField::Language;
300  case sLength:
301  return KnownField::Length;
302  case sEncoderSettings:
303  return KnownField::EncoderSettings;
305  return KnownField::Lyrics;
306  case sSynchronizedLyrics:
307  return KnownField::SynchronizedLyrics;
308  case sGrouping:
309  return KnownField::Grouping;
310  case sRecordLabel:
312  default:
313  return KnownField::Invalid;
314  }
315 }
316 
317 TagDataType Id3v2Tag::internallyGetProposedDataType(const uint32 &id) const
318 {
319  using namespace Id3v2FrameIds;
320  switch (id) {
321  case lLength:
322  case sLength:
323  return TagDataType::TimeSpan;
324  case lBpm:
325  case sBpm:
326  return TagDataType::Integer;
327  case lTrackPosition:
328  case sTrackPosition:
329  case lDiskPosition:
330  return TagDataType::PositionInSet;
331  case lCover:
332  case sCover:
333  return TagDataType::Picture;
334  default:
335  if (Id3v2FrameIds::isTextFrame(id)) {
336  return TagDataType::Text;
337  } else {
338  return TagDataType::Undefined;
339  }
340  }
341 }
342 
350 void Id3v2Tag::parse(istream &stream, const uint64 maximalSize, Diagnostics &diag)
351 {
352  // prepare parsing
353  static const string context("parsing ID3v2 tag");
354  BinaryReader reader(&stream);
355  const auto startOffset = static_cast<uint64>(stream.tellg());
356 
357  // check whether the header is truncated
358  if (maximalSize && maximalSize < 10) {
359  diag.emplace_back(DiagLevel::Critical, "ID3v2 header is truncated (at least 10 bytes expected).", context);
360  throw TruncatedDataException();
361  }
362 
363  // read signature: ID3
364  if (reader.readUInt24BE() != 0x494433u) {
365  diag.emplace_back(DiagLevel::Critical, "Signature is invalid.", context);
366  throw InvalidDataException();
367  }
368  // read header data
369  byte majorVersion = reader.readByte();
370  byte revisionVersion = reader.readByte();
371  setVersion(majorVersion, revisionVersion);
372  m_flags = reader.readByte();
373  m_sizeExcludingHeader = reader.readSynchsafeUInt32BE();
374  m_size = 10 + m_sizeExcludingHeader;
375  if (m_sizeExcludingHeader == 0) {
376  diag.emplace_back(DiagLevel::Warning, "ID3v2 tag seems to be empty.", context);
377  return;
378  }
379 
380  // check if the version
381  if (!isVersionSupported()) {
382  diag.emplace_back(DiagLevel::Critical, "The ID3v2 tag couldn't be parsed, because its version is not supported.", context);
384  }
385 
386  // read extended header (if present)
387  if (hasExtendedHeader()) {
388  if (maximalSize && maximalSize < 14) {
389  diag.emplace_back(DiagLevel::Critical, "Extended header denoted but not present.", context);
390  throw TruncatedDataException();
391  }
392  m_extendedHeaderSize = reader.readSynchsafeUInt32BE();
393  if (m_extendedHeaderSize < 6 || m_extendedHeaderSize > m_sizeExcludingHeader || (maximalSize && maximalSize < (10 + m_extendedHeaderSize))) {
394  diag.emplace_back(DiagLevel::Critical, "Extended header is invalid/truncated.", context);
395  throw TruncatedDataException();
396  }
397  stream.seekg(m_extendedHeaderSize - 4, ios_base::cur);
398  }
399 
400  // how many bytes remain for frames and padding?
401  uint32 bytesRemaining = m_sizeExcludingHeader - m_extendedHeaderSize;
402  if (maximalSize && bytesRemaining > maximalSize) {
403  bytesRemaining = static_cast<uint32>(maximalSize);
404  diag.emplace_back(DiagLevel::Critical, "Frames are truncated.", context);
405  }
406 
407  // read frames
408  auto pos = static_cast<uint64>(stream.tellg());
409  while (bytesRemaining) {
410  // seek to next frame
411  stream.seekg(static_cast<streamoff>(pos));
412  // parse frame
413  Id3v2Frame frame;
414  try {
415  frame.parse(reader, majorVersion, bytesRemaining, diag);
416  if (Id3v2FrameIds::isTextFrame(frame.id()) && fields().count(frame.id()) == 1) {
417  diag.emplace_back(DiagLevel::Warning, "The text frame " % frame.idToString() + " exists more than once.", context);
418  }
419  fields().emplace(frame.id(), move(frame));
420  } catch (const NoDataFoundException &) {
421  if (frame.hasPaddingReached()) {
422  m_paddingSize = startOffset + m_size - pos;
423  break;
424  }
425  } catch (const Failure &) {
426  }
427 
428  // calculate next frame offset
429  if (frame.totalSize() <= bytesRemaining) {
430  pos += frame.totalSize();
431  bytesRemaining -= frame.totalSize();
432  } else {
433  pos += bytesRemaining;
434  bytesRemaining = 0;
435  }
436  }
437 
438  // check for extended header
439  if (!hasFooter()) {
440  return;
441  }
442  if (maximalSize && m_size + 10 < maximalSize) {
443  // the footer does not provide additional information, just check the signature
444  stream.seekg(static_cast<streamoff>(startOffset + (m_size += 10)));
445  if (reader.readUInt24LE() != 0x494433u) {
446  diag.emplace_back(DiagLevel::Critical, "Footer signature is invalid.", context);
447  }
448  // skip remaining footer
449  stream.seekg(7, ios_base::cur);
450  } else {
451  diag.emplace_back(DiagLevel::Critical, "Footer denoted but not present.", context);
452  throw TruncatedDataException();
453  }
454 }
455 
466 Id3v2TagMaker Id3v2Tag::prepareMaking(Diagnostics &diag)
467 {
468  return Id3v2TagMaker(*this, diag);
469 }
470 
478 void Id3v2Tag::make(ostream &stream, uint32 padding, Diagnostics &diag)
479 {
480  prepareMaking(diag).make(stream, padding, diag);
481 }
482 
487 void Id3v2Tag::setVersion(byte majorVersion, byte revisionVersion)
488 {
489  m_majorVersion = majorVersion;
490  m_revisionVersion = revisionVersion;
491  m_version = argsToString('2', '.', majorVersion, '.', revisionVersion);
492 }
493 
506 bool FrameComparer::operator()(uint32 lhs, uint32 rhs) const
507 {
508  if (lhs == rhs) {
509  return false;
510  }
511 
512  const bool lhsLong = Id3v2FrameIds::isLongId(lhs);
513  const bool rhsLong = Id3v2FrameIds::isLongId(rhs);
514  if (lhsLong != rhsLong) {
515  if (!lhsLong) {
517  if (!lhs) {
518  return true;
519  }
520  } else if (!rhsLong) {
522  if (!rhs) {
523  return true;
524  }
525  }
526  }
527 
529  return true;
530  }
532  return false;
533  }
534  if (lhs == Id3v2FrameIds::lTitle || lhs == Id3v2FrameIds::sTitle) {
535  return true;
536  }
537  if (rhs == Id3v2FrameIds::lTitle || rhs == Id3v2FrameIds::sTitle) {
538  return false;
539  }
540 
541  const bool lhstextfield = Id3v2FrameIds::isTextFrame(lhs);
542  const bool rhstextfield = Id3v2FrameIds::isTextFrame(rhs);
543  if (lhstextfield && !rhstextfield) {
544  return true;
545  }
546  if (!lhstextfield && rhstextfield) {
547  return false;
548  }
549 
550  if (lhs == Id3v2FrameIds::lCover || lhs == Id3v2FrameIds::sCover) {
551  return false;
552  }
553  if (rhs == Id3v2FrameIds::lCover || rhs == Id3v2FrameIds::sCover) {
554  return true;
555  }
556  return lhs < rhs;
557 }
558 
570 Id3v2TagMaker::Id3v2TagMaker(Id3v2Tag &tag, Diagnostics &diag)
571  : m_tag(tag)
572  , m_framesSize(0)
573 {
574  static const string context("making ID3v2 tag");
575 
576  // check if version is supported
577  // (the version could have been changed using setVersion())
578  if (!tag.isVersionSupported()) {
579  diag.emplace_back(DiagLevel::Critical, "The ID3v2 tag version isn't supported.", context);
581  }
582 
583  // prepare frames
584  m_maker.reserve(tag.fields().size());
585  for (auto &pair : tag.fields()) {
586  try {
587  m_maker.emplace_back(pair.second.prepareMaking(tag.majorVersion(), diag));
588  m_framesSize += m_maker.back().requiredSize();
589  } catch (const Failure &) {
590  }
591  }
592 
593  // calculate required size
594  // -> header + size of frames
595  m_requiredSize = 10 + m_framesSize;
596 }
597 
605 void Id3v2TagMaker::make(std::ostream &stream, uint32 padding, Diagnostics &diag)
606 {
607  VAR_UNUSED(diag)
608 
609  BinaryWriter writer(&stream);
610 
611  // write header
612  // -> signature
613  writer.writeUInt24BE(0x494433u);
614  // -> version
615  writer.writeByte(m_tag.majorVersion());
616  writer.writeByte(m_tag.revisionVersion());
617  // -> flags, but without extended header or compression bit set
618  writer.writeByte(m_tag.flags() & 0xBF);
619  // -> size (excluding header)
620  writer.writeSynchsafeUInt32BE(m_framesSize + padding);
621 
622  // write frames
623  for (auto &maker : m_maker) {
624  maker.make(writer);
625  }
626 
627  // write padding
628  for (; padding; --padding) {
629  stream.put(0);
630  }
631 }
632 
633 } // namespace TagParser
The exception that is thrown when the data to be parsed is truncated and therefore can not be parsed ...
Definition: exceptions.h:32
bool hasPaddingReached() const
Returns whether the padding has reached.
Definition: id3v2frame.h:196
typename FieldMapBasedTagTraits< Id3v2Tag >::FieldType::IdentifierType IdentifierType
Definition: fieldbasedtag.h:36
The exception that is thrown when an operation fails because the detected or specified version is not...
Definition: exceptions.h:46
constexpr bool isLongId(uint32 id)
Returns an indication whether the specified id is a long frame id.
Definition: id3v2frameids.h:69
STL namespace.
TagDataType
Specifies the data type.
Definition: tagvalue.h:53
byte flags() const
Returns the flags read from the ID3v2 header.
Definition: id3v2tag.h:179
uint32 convertToLongId(uint32 id)
Converts the specified short frame ID to the equivalent long frame ID.
KnownField
Specifies the field.
Definition: tag.h:40
byte revisionVersion() const
Returns the revision version if known; otherwise returns 0.
Definition: id3v2tag.h:160
The Id3v2Frame class is used by Id3v2Tag to store the fields.
Definition: id3v2frame.h:86
The Id3v2TagMaker class helps writing ID3v2 tags.
Definition: id3v2tag.h:18
std::string idToString() const
constexpr bool isTextFrame(uint32 id)
Returns an indication whether the specified id is a text frame id.
Definition: id3v2frameids.h:85
Contains utility classes helping to read and write streams.
bool isVersionSupported() const
Returns an indication whether the version is supported by the Id3v2Tag class.
Definition: id3v2tag.h:171
The exception that is thrown when the data to be parsed holds no parsable information.
Definition: exceptions.h:18
byte majorVersion() const
Returns the major version if known; otherwise returns 0.
Definition: id3v2tag.h:152
const IdentifierType & id() const
Returns the id of the current TagField.
void make(std::ostream &stream, uint32 padding, Diagnostics &diag)
Saves the tag (specified when constructing the object) to the specified stream.
Definition: id3v2tag.cpp:605
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.
Implementation of TagParser::Tag for ID3v2 tags.
Definition: id3v2tag.h:61
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 TagValue class wraps values of different types.
Definition: tagvalue.h:65
The class inherits from std::exception and serves as base class for exceptions thrown by the elements...
Definition: exceptions.h:11
void parse(IoUtilities::BinaryReader &reader, uint32 version, uint32 maximalSize, Diagnostics &diag)
Parses a frame from the stream read using the specified reader.
Definition: id3v2frame.cpp:133
Contains all classes and functions of the TagInfo library.
Definition: aaccodebook.h:9
The Diagnostics class is a container for DiagMessage.
Definition: diagnostics.h:156
uint32 totalSize() const
Returns the total size of the frame in bytes.
Definition: id3v2frame.h:220