Tag Parser  8.2.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:
48  return m_majorVersion > 3;
49  case KnownField::Rating:
51  case KnownField::Cover:
52  case KnownField::Lyrics:
53  case KnownField::SynchronizedLyrics:
54  return true;
55  default:
56  return false;
57  ;
58  }
59 }
60 
64 std::vector<const TagValue *> Id3v2Tag::internallyGetValues(const IdentifierType &id) const
65 {
66  auto range = fields().equal_range(id);
67  std::vector<const TagValue *> values;
68  for (auto i = range.first; i != range.second; ++i) {
69  const auto &frame(i->second);
70  if (!frame.value().isEmpty()) {
71  values.push_back(&frame.value());
72  }
73  for (const auto &value : frame.additionalValues()) {
74  values.push_back(&value);
75  }
76  }
77  return values;
78 }
79 
86 bool Id3v2Tag::internallySetValues(const IdentifierType &id, const std::vector<TagValue> &values)
87 {
88  // use default implementation for non-text frames
89  if (!Id3v2FrameIds::isTextFrame(id)) {
90  return CRTPBase::internallySetValues(id, values);
91  }
92 
93  // find existing text frame
94  auto range = fields().equal_range(id);
95  auto frameIterator = range.first;
96 
97  // use existing frame or insert new text frame
98  auto valuesIterator = values.cbegin();
99  if (frameIterator != range.second) {
100  ++range.first;
101  // add primary value to existing frame
102  if (valuesIterator != values.cend()) {
103  frameIterator->second.setValue(*valuesIterator);
104  ++valuesIterator;
105  } else {
106  frameIterator->second.value().clearDataAndMetadata();
107  }
108  } else {
109  // skip if there is no existing frame but also no values to be assigned
110  if (valuesIterator == values.cend()) {
111  return true;
112  }
113  // add primary value to new frame
114  frameIterator = fields().insert(make_pair(id, Id3v2Frame(id, *valuesIterator)));
115  ++valuesIterator;
116  }
117 
118  // add additional values to frame
119  frameIterator->second.additionalValues() = vector<TagValue>(valuesIterator, values.cend());
120 
121  // remove remaining existing values (there are more existing values than specified ones)
122  for (; range.first != range.second; ++range.first) {
123  range.first->second.setValue(TagValue());
124  }
125  return true;
126 }
127 
128 Id3v2Tag::IdentifierType Id3v2Tag::internallyGetFieldId(KnownField field) const
129 {
130  using namespace Id3v2FrameIds;
131  if (m_majorVersion >= 3) {
132  switch (field) {
133  case KnownField::Album:
134  return lAlbum;
135  case KnownField::Artist:
136  return lArtist;
137  case KnownField::Comment:
138  return lComment;
139  case KnownField::Year:
140  return lYear;
141  case KnownField::RecordDate:
142  return lRecordDate;
143  case KnownField::Title:
144  return lTitle;
145  case KnownField::Genre:
146  return lGenre;
148  return lTrackPosition;
150  return lDiskPosition;
151  case KnownField::Encoder:
152  return lEncoder;
153  case KnownField::Bpm:
154  return lBpm;
155  case KnownField::Cover:
156  return lCover;
158  return lWriter;
159  case KnownField::Length:
160  return lLength;
161  case KnownField::Language:
162  return lLanguage;
163  case KnownField::EncoderSettings:
164  return lEncoderSettings;
165  case KnownField::Lyrics:
166  return lUnsynchronizedLyrics;
167  case KnownField::SynchronizedLyrics:
168  return lSynchronizedLyrics;
172  return lRecordLabel;
174  return lComposer;
175  case KnownField::Rating:
176  return lRating;
178  return lGrouping;
179  default:;
180  }
181  } else {
182  switch (field) {
183  case KnownField::Album:
184  return sAlbum;
185  case KnownField::Artist:
186  return sArtist;
187  case KnownField::Comment:
188  return sComment;
189  case KnownField::Year:
190  return sYear;
191  case KnownField::RecordDate:
192  return sRecordDate;
193  case KnownField::Title:
194  return sTitle;
195  case KnownField::Genre:
196  return sGenre;
198  return sTrackPosition;
200  return sDiskPosition;
201  case KnownField::Encoder:
202  return sEncoder;
203  case KnownField::Bpm:
204  return sBpm;
205  case KnownField::Cover:
206  return sCover;
208  return sWriter;
209  case KnownField::Length:
210  return sLength;
211  case KnownField::Language:
212  return sLanguage;
213  case KnownField::EncoderSettings:
214  return sEncoderSettings;
215  case KnownField::Lyrics:
216  return sUnsynchronizedLyrics;
217  case KnownField::SynchronizedLyrics:
218  return sSynchronizedLyrics;
222  return sRecordLabel;
224  return sComposer;
225  case KnownField::Rating:
226  return sRating;
228  return sGrouping;
229  default:;
230  }
231  }
232  return 0;
233 }
234 
235 KnownField Id3v2Tag::internallyGetKnownField(const IdentifierType &id) const
236 {
237  using namespace Id3v2FrameIds;
238  switch (id) {
239  case lAlbum:
240  return KnownField::Album;
241  case lArtist:
242  return KnownField::Artist;
243  case lComment:
244  return KnownField::Comment;
245  case lYear:
246  return KnownField::Year;
247  case lRecordDate:
248  return KnownField::RecordDate;
249  case lTitle:
250  return KnownField::Title;
251  case lGenre:
252  return KnownField::Genre;
253  case lTrackPosition:
255  case lDiskPosition:
257  case lEncoder:
258  return KnownField::Encoder;
259  case lBpm:
260  return KnownField::Bpm;
261  case lCover:
262  return KnownField::Cover;
263  case lWriter:
264  return KnownField::Lyricist;
265  case lLanguage:
266  return KnownField::Language;
267  case lLength:
268  return KnownField::Length;
269  case lEncoderSettings:
270  return KnownField::EncoderSettings;
272  return KnownField::Lyrics;
273  case lSynchronizedLyrics:
274  return KnownField::SynchronizedLyrics;
275  case lGrouping:
278  return KnownField::Grouping;
279  case lRecordLabel:
281  case sAlbum:
282  return KnownField::Album;
283  case sArtist:
284  return KnownField::Artist;
285  case sComment:
286  return KnownField::Comment;
287  case sYear:
288  return KnownField::Year;
289  case sRecordDate:
290  return KnownField::RecordDate;
291  case sTitle:
292  return KnownField::Title;
293  case sGenre:
294  return KnownField::Genre;
295  case sTrackPosition:
297  case sEncoder:
298  return KnownField::Encoder;
299  case sBpm:
300  return KnownField::Bpm;
301  case sCover:
302  return KnownField::Cover;
303  case sWriter:
304  return KnownField::Lyricist;
305  case sLanguage:
306  return KnownField::Language;
307  case sLength:
308  return KnownField::Length;
309  case sEncoderSettings:
310  return KnownField::EncoderSettings;
312  return KnownField::Lyrics;
313  case sSynchronizedLyrics:
314  return KnownField::SynchronizedLyrics;
315  case sGrouping:
316  return KnownField::Grouping;
317  case sRecordLabel:
319  default:
320  return KnownField::Invalid;
321  }
322 }
323 
324 TagDataType Id3v2Tag::internallyGetProposedDataType(const uint32 &id) const
325 {
326  using namespace Id3v2FrameIds;
327  switch (id) {
328  case lLength:
329  case sLength:
330  return TagDataType::TimeSpan;
331  case lBpm:
332  case sBpm:
333  return TagDataType::Integer;
334  case lTrackPosition:
335  case sTrackPosition:
336  case lDiskPosition:
337  return TagDataType::PositionInSet;
338  case lCover:
339  case sCover:
340  return TagDataType::Picture;
341  default:
342  if (Id3v2FrameIds::isTextFrame(id)) {
343  return TagDataType::Text;
344  } else {
345  return TagDataType::Undefined;
346  }
347  }
348 }
349 
357 void Id3v2Tag::parse(istream &stream, const uint64 maximalSize, Diagnostics &diag)
358 {
359  // prepare parsing
360  static const string context("parsing ID3v2 tag");
361  BinaryReader reader(&stream);
362  const auto startOffset = static_cast<uint64>(stream.tellg());
363 
364  // check whether the header is truncated
365  if (maximalSize && maximalSize < 10) {
366  diag.emplace_back(DiagLevel::Critical, "ID3v2 header is truncated (at least 10 bytes expected).", context);
367  throw TruncatedDataException();
368  }
369 
370  // read signature: ID3
371  if (reader.readUInt24BE() != 0x494433u) {
372  diag.emplace_back(DiagLevel::Critical, "Signature is invalid.", context);
373  throw InvalidDataException();
374  }
375  // read header data
376  byte majorVersion = reader.readByte();
377  byte revisionVersion = reader.readByte();
378  setVersion(majorVersion, revisionVersion);
379  m_flags = reader.readByte();
380  m_sizeExcludingHeader = reader.readSynchsafeUInt32BE();
381  m_size = 10 + m_sizeExcludingHeader;
382  if (m_sizeExcludingHeader == 0) {
383  diag.emplace_back(DiagLevel::Warning, "ID3v2 tag seems to be empty.", context);
384  return;
385  }
386 
387  // check if the version
388  if (!isVersionSupported()) {
389  diag.emplace_back(DiagLevel::Critical, "The ID3v2 tag couldn't be parsed, because its version is not supported.", context);
391  }
392 
393  // read extended header (if present)
394  if (hasExtendedHeader()) {
395  if (maximalSize && maximalSize < 14) {
396  diag.emplace_back(DiagLevel::Critical, "Extended header denoted but not present.", context);
397  throw TruncatedDataException();
398  }
399  m_extendedHeaderSize = reader.readSynchsafeUInt32BE();
400  if (m_extendedHeaderSize < 6 || m_extendedHeaderSize > m_sizeExcludingHeader || (maximalSize && maximalSize < (10 + m_extendedHeaderSize))) {
401  diag.emplace_back(DiagLevel::Critical, "Extended header is invalid/truncated.", context);
402  throw TruncatedDataException();
403  }
404  stream.seekg(m_extendedHeaderSize - 4, ios_base::cur);
405  }
406 
407  // how many bytes remain for frames and padding?
408  uint32 bytesRemaining = m_sizeExcludingHeader - m_extendedHeaderSize;
409  if (maximalSize && bytesRemaining > maximalSize) {
410  bytesRemaining = static_cast<uint32>(maximalSize);
411  diag.emplace_back(DiagLevel::Critical, "Frames are truncated.", context);
412  }
413 
414  // read frames
415  auto pos = static_cast<uint64>(stream.tellg());
416  while (bytesRemaining) {
417  // seek to next frame
418  stream.seekg(static_cast<streamoff>(pos));
419  // parse frame
420  Id3v2Frame frame;
421  try {
422  frame.parse(reader, majorVersion, bytesRemaining, diag);
423  if (Id3v2FrameIds::isTextFrame(frame.id()) && fields().count(frame.id()) == 1) {
424  diag.emplace_back(DiagLevel::Warning, "The text frame " % frame.idToString() + " exists more than once.", context);
425  }
426  fields().emplace(frame.id(), move(frame));
427  } catch (const NoDataFoundException &) {
428  if (frame.hasPaddingReached()) {
429  m_paddingSize = startOffset + m_size - pos;
430  break;
431  }
432  } catch (const Failure &) {
433  }
434 
435  // calculate next frame offset
436  if (frame.totalSize() <= bytesRemaining) {
437  pos += frame.totalSize();
438  bytesRemaining -= frame.totalSize();
439  } else {
440  pos += bytesRemaining;
441  bytesRemaining = 0;
442  }
443  }
444 
445  // check for extended header
446  if (!hasFooter()) {
447  return;
448  }
449  if (maximalSize && m_size + 10 < maximalSize) {
450  // the footer does not provide additional information, just check the signature
451  stream.seekg(static_cast<streamoff>(startOffset + (m_size += 10)));
452  if (reader.readUInt24LE() != 0x494433u) {
453  diag.emplace_back(DiagLevel::Critical, "Footer signature is invalid.", context);
454  }
455  // skip remaining footer
456  stream.seekg(7, ios_base::cur);
457  } else {
458  diag.emplace_back(DiagLevel::Critical, "Footer denoted but not present.", context);
459  throw TruncatedDataException();
460  }
461 }
462 
473 Id3v2TagMaker Id3v2Tag::prepareMaking(Diagnostics &diag)
474 {
475  return Id3v2TagMaker(*this, diag);
476 }
477 
485 void Id3v2Tag::make(ostream &stream, uint32 padding, Diagnostics &diag)
486 {
487  prepareMaking(diag).make(stream, padding, diag);
488 }
489 
494 void Id3v2Tag::setVersion(byte majorVersion, byte revisionVersion)
495 {
496  m_majorVersion = majorVersion;
497  m_revisionVersion = revisionVersion;
498  m_version = argsToString('2', '.', majorVersion, '.', revisionVersion);
499 }
500 
513 bool FrameComparer::operator()(uint32 lhs, uint32 rhs) const
514 {
515  if (lhs == rhs) {
516  return false;
517  }
518 
519  const bool lhsLong = Id3v2FrameIds::isLongId(lhs);
520  const bool rhsLong = Id3v2FrameIds::isLongId(rhs);
521  if (lhsLong != rhsLong) {
522  if (!lhsLong) {
524  if (!lhs) {
525  return true;
526  }
527  } else if (!rhsLong) {
529  if (!rhs) {
530  return true;
531  }
532  }
533  }
534 
536  return true;
537  }
539  return false;
540  }
541  if (lhs == Id3v2FrameIds::lTitle || lhs == Id3v2FrameIds::sTitle) {
542  return true;
543  }
544  if (rhs == Id3v2FrameIds::lTitle || rhs == Id3v2FrameIds::sTitle) {
545  return false;
546  }
547 
548  const bool lhstextfield = Id3v2FrameIds::isTextFrame(lhs);
549  const bool rhstextfield = Id3v2FrameIds::isTextFrame(rhs);
550  if (lhstextfield && !rhstextfield) {
551  return true;
552  }
553  if (!lhstextfield && rhstextfield) {
554  return false;
555  }
556 
557  if (lhs == Id3v2FrameIds::lCover || lhs == Id3v2FrameIds::sCover) {
558  return false;
559  }
560  if (rhs == Id3v2FrameIds::lCover || rhs == Id3v2FrameIds::sCover) {
561  return true;
562  }
563  return lhs < rhs;
564 }
565 
577 Id3v2TagMaker::Id3v2TagMaker(Id3v2Tag &tag, Diagnostics &diag)
578  : m_tag(tag)
579  , m_framesSize(0)
580 {
581  static const string context("making ID3v2 tag");
582 
583  // check if version is supported
584  // (the version could have been changed using setVersion())
585  if (!tag.isVersionSupported()) {
586  diag.emplace_back(DiagLevel::Critical, "The ID3v2 tag version isn't supported.", context);
588  }
589 
590  // prepare frames
591  m_maker.reserve(tag.fields().size());
592  for (auto &pair : tag.fields()) {
593  try {
594  m_maker.emplace_back(pair.second.prepareMaking(tag.majorVersion(), diag));
595  m_framesSize += m_maker.back().requiredSize();
596  } catch (const Failure &) {
597  }
598  }
599 
600  // calculate required size
601  // -> header + size of frames
602  m_requiredSize = 10 + m_framesSize;
603 }
604 
612 void Id3v2TagMaker::make(std::ostream &stream, uint32 padding, Diagnostics &diag)
613 {
614  VAR_UNUSED(diag)
615 
616  BinaryWriter writer(&stream);
617 
618  // write header
619  // -> signature
620  writer.writeUInt24BE(0x494433u);
621  // -> version
622  writer.writeByte(m_tag.majorVersion());
623  writer.writeByte(m_tag.revisionVersion());
624  // -> flags, but without extended header or compression bit set
625  writer.writeByte(m_tag.flags() & 0xBF);
626  // -> size (excluding header)
627  writer.writeSynchsafeUInt32BE(m_framesSize + padding);
628 
629  // write frames
630  for (auto &maker : m_maker) {
631  maker.make(writer);
632  }
633 
634  // write padding
635  for (; padding; --padding) {
636  stream.put(0);
637  }
638 }
639 
640 } // namespace TagParser
The exception that is thrown when the data to be parsed is truncated and therefore can not be parsed ...
Definition: exceptions.h:39
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:53
constexpr bool isLongId(uint32 id)
Returns an indication whether the specified id is a long frame id.
Definition: id3v2frameids.h:71
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:42
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:87
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 (e....
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:612
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:134
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