Tag Parser  7.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 using namespace std;
11 using namespace IoUtilities;
12 using namespace ConversionUtilities;
13 
14 namespace TagParser {
15 
21 Id3v2Tag::IdentifierType Id3v2Tag::internallyGetFieldId(KnownField field) const
22 {
23  using namespace Id3v2FrameIds;
24  if (m_majorVersion >= 3) {
25  switch (field) {
26  case KnownField::Album:
27  return lAlbum;
28  case KnownField::Artist:
29  return lArtist;
31  return lComment;
32  case KnownField::Year:
33  return lYear;
34  case KnownField::RecordDate:
35  return lRecordDate;
36  case KnownField::Title:
37  return lTitle;
38  case KnownField::Genre:
39  return lGenre;
41  return lTrackPosition;
43  return lDiskPosition;
45  return lEncoder;
46  case KnownField::Bpm:
47  return lBpm;
48  case KnownField::Cover:
49  return lCover;
51  return lWriter;
52  case KnownField::Length:
53  return lLength;
54  case KnownField::Language:
55  return lLanguage;
56  case KnownField::EncoderSettings:
57  return lEncoderSettings;
58  case KnownField::Lyrics:
59  return lUnsynchronizedLyrics;
60  case KnownField::SynchronizedLyrics:
61  return lSynchronizedLyrics;
63  return lGrouping;
65  return lRecordLabel;
67  return lComposer;
68  case KnownField::Rating:
69  return lRating;
70  default:;
71  }
72  } else {
73  switch (field) {
74  case KnownField::Album:
75  return sAlbum;
76  case KnownField::Artist:
77  return sArtist;
79  return sComment;
80  case KnownField::Year:
81  return sYear;
82  case KnownField::RecordDate:
83  return sRecordDate;
84  case KnownField::Title:
85  return sTitle;
86  case KnownField::Genre:
87  return sGenre;
89  return sTrackPosition;
91  return lDiskPosition;
93  return sEncoder;
94  case KnownField::Bpm:
95  return sBpm;
96  case KnownField::Cover:
97  return sCover;
99  return sWriter;
100  case KnownField::Length:
101  return sLength;
102  case KnownField::Language:
103  return sLanguage;
104  case KnownField::EncoderSettings:
105  return sEncoderSettings;
106  case KnownField::Lyrics:
107  return sUnsynchronizedLyrics;
108  case KnownField::SynchronizedLyrics:
109  return sSynchronizedLyrics;
111  return sGrouping;
113  return sRecordLabel;
115  return sComposer;
116  case KnownField::Rating:
117  return sRating;
118  default:;
119  }
120  }
121  return 0;
122 }
123 
124 KnownField Id3v2Tag::internallyGetKnownField(const IdentifierType &id) const
125 {
126  using namespace Id3v2FrameIds;
127  switch (id) {
128  case lAlbum:
129  return KnownField::Album;
130  case lArtist:
131  return KnownField::Artist;
132  case lComment:
133  return KnownField::Comment;
134  case lYear:
135  return KnownField::Year;
136  case lRecordDate:
137  return KnownField::RecordDate;
138  case lTitle:
139  return KnownField::Title;
140  case lGenre:
141  return KnownField::Genre;
142  case lTrackPosition:
144  case lDiskPosition:
146  case lEncoder:
147  return KnownField::Encoder;
148  case lBpm:
149  return KnownField::Bpm;
150  case lCover:
151  return KnownField::Cover;
152  case lWriter:
153  return KnownField::Lyricist;
154  case lLanguage:
155  return KnownField::Language;
156  case lLength:
157  return KnownField::Length;
158  case lEncoderSettings:
159  return KnownField::EncoderSettings;
161  return KnownField::Lyrics;
162  case lSynchronizedLyrics:
163  return KnownField::SynchronizedLyrics;
164  case lGrouping:
165  return KnownField::Grouping;
166  case lRecordLabel:
168  case sAlbum:
169  return KnownField::Album;
170  case sArtist:
171  return KnownField::Artist;
172  case sComment:
173  return KnownField::Comment;
174  case sYear:
175  return KnownField::Year;
176  case sRecordDate:
177  return KnownField::RecordDate;
178  case sTitle:
179  return KnownField::Title;
180  case sGenre:
181  return KnownField::Genre;
182  case sTrackPosition:
184  case sEncoder:
185  return KnownField::Encoder;
186  case sBpm:
187  return KnownField::Bpm;
188  case sCover:
189  return KnownField::Cover;
190  case sWriter:
191  return KnownField::Lyricist;
192  case sLanguage:
193  return KnownField::Language;
194  case sLength:
195  return KnownField::Length;
196  case sEncoderSettings:
197  return KnownField::EncoderSettings;
199  return KnownField::Lyrics;
200  case sSynchronizedLyrics:
201  return KnownField::SynchronizedLyrics;
202  case sGrouping:
203  return KnownField::Grouping;
204  case sRecordLabel:
206  default:
207  return KnownField::Invalid;
208  }
209 }
210 
211 TagDataType Id3v2Tag::internallyGetProposedDataType(const uint32 &id) const
212 {
213  using namespace Id3v2FrameIds;
214  switch (id) {
215  case lLength:
216  case sLength:
217  return TagDataType::TimeSpan;
218  case lBpm:
219  case sBpm:
220  return TagDataType::Integer;
221  case lTrackPosition:
222  case sTrackPosition:
223  case lDiskPosition:
224  return TagDataType::PositionInSet;
225  case lCover:
226  case sCover:
227  return TagDataType::Picture;
228  default:
229  if (Id3v2FrameIds::isTextFrame(id)) {
230  return TagDataType::Text;
231  } else {
232  return TagDataType::Undefined;
233  }
234  }
235 }
236 
244 void Id3v2Tag::parse(istream &stream, const uint64 maximalSize, Diagnostics &diag)
245 {
246  // prepare parsing
247  static const string context("parsing ID3v2 tag");
248  BinaryReader reader(&stream);
249  uint64 startOffset = stream.tellg();
250 
251  // check whether the header is truncated
252  if (maximalSize && maximalSize < 10) {
253  diag.emplace_back(DiagLevel::Critical, "ID3v2 header is truncated (at least 10 bytes expected).", context);
254  throw TruncatedDataException();
255  }
256 
257  // read signature: ID3
258  if (reader.readUInt24BE() != 0x494433u) {
259  diag.emplace_back(DiagLevel::Critical, "Signature is invalid.", context);
260  throw InvalidDataException();
261  }
262  // read header data
263  byte majorVersion = reader.readByte();
264  byte revisionVersion = reader.readByte();
265  setVersion(majorVersion, revisionVersion);
266  m_flags = reader.readByte();
267  m_sizeExcludingHeader = reader.readSynchsafeUInt32BE();
268  m_size = 10 + m_sizeExcludingHeader;
269  if (m_sizeExcludingHeader == 0) {
270  diag.emplace_back(DiagLevel::Warning, "ID3v2 tag seems to be empty.", context);
271  return;
272  }
273 
274  // check if the version
275  if (!isVersionSupported()) {
276  diag.emplace_back(DiagLevel::Critical, "The ID3v2 tag couldn't be parsed, because its version is not supported.", context);
278  }
279 
280  // read extended header (if present)
281  if (hasExtendedHeader()) {
282  if (maximalSize && maximalSize < 14) {
283  diag.emplace_back(DiagLevel::Critical, "Extended header denoted but not present.", context);
284  throw TruncatedDataException();
285  }
286  m_extendedHeaderSize = reader.readSynchsafeUInt32BE();
287  if (m_extendedHeaderSize < 6 || m_extendedHeaderSize > m_sizeExcludingHeader || (maximalSize && maximalSize < (10 + m_extendedHeaderSize))) {
288  diag.emplace_back(DiagLevel::Critical, "Extended header is invalid/truncated.", context);
289  throw TruncatedDataException();
290  }
291  stream.seekg(m_extendedHeaderSize - 4, ios_base::cur);
292  }
293 
294  // how many bytes remain for frames and padding?
295  uint32 bytesRemaining = m_sizeExcludingHeader - m_extendedHeaderSize;
296  if (maximalSize && bytesRemaining > maximalSize) {
297  bytesRemaining = maximalSize;
298  diag.emplace_back(DiagLevel::Critical, "Frames are truncated.", context);
299  }
300 
301  // read frames
302  auto pos = stream.tellg();
303  while (bytesRemaining) {
304  // seek to next frame
305  stream.seekg(pos);
306  // parse frame
307  Id3v2Frame frame;
308  try {
309  frame.parse(reader, majorVersion, bytesRemaining, diag);
310  if (Id3v2FrameIds::isTextFrame(frame.id()) && fields().count(frame.id()) == 1) {
311  diag.emplace_back(DiagLevel::Warning, "The text frame " % frame.frameIdString() + " exists more than once.", context);
312  }
313  fields().emplace(frame.id(), move(frame));
314  } catch (const NoDataFoundException &) {
315  if (frame.hasPaddingReached()) {
316  m_paddingSize = startOffset + m_size - pos;
317  break;
318  }
319  } catch (const Failure &) {
320  }
321 
322  // calculate next frame offset
323  if (frame.totalSize() <= bytesRemaining) {
324  pos += frame.totalSize();
325  bytesRemaining -= frame.totalSize();
326  } else {
327  pos += bytesRemaining;
328  bytesRemaining = 0;
329  }
330  }
331 
332  // check for extended header
333  if (!hasFooter()) {
334  return;
335  }
336  if (maximalSize && m_size + 10 < maximalSize) {
337  // the footer does not provide additional information, just check the signature
338  stream.seekg(startOffset + (m_size += 10));
339  if (reader.readUInt24LE() != 0x494433u) {
340  diag.emplace_back(DiagLevel::Critical, "Footer signature is invalid.", context);
341  }
342  // skip remaining footer
343  stream.seekg(7, ios_base::cur);
344  } else {
345  diag.emplace_back(DiagLevel::Critical, "Footer denoted but not present.", context);
346  throw TruncatedDataException();
347  }
348 }
349 
360 Id3v2TagMaker Id3v2Tag::prepareMaking(Diagnostics &diag)
361 {
362  return Id3v2TagMaker(*this, diag);
363 }
364 
372 void Id3v2Tag::make(ostream &stream, uint32 padding, Diagnostics &diag)
373 {
374  prepareMaking(diag).make(stream, padding, diag);
375 }
376 
381 void Id3v2Tag::setVersion(byte majorVersion, byte revisionVersion)
382 {
383  m_majorVersion = majorVersion;
384  m_revisionVersion = revisionVersion;
385  m_version = argsToString('2', '.', majorVersion, '.', revisionVersion);
386 }
387 
398 bool FrameComparer::operator()(const uint32 &lhs, const uint32 &rhs) const
399 {
400  if (lhs == rhs) {
401  return false;
402  }
403  const bool lhsLong = Id3v2FrameIds::isLongId(lhs);
404  const bool rhsLong = Id3v2FrameIds::isLongId(rhs);
405  if (((lhsLong && !rhsLong) && (lhs == Id3v2FrameIds::convertToLongId(rhs)))
406  || ((!lhsLong && rhsLong) && (Id3v2FrameIds::convertToLongId(lhs) == rhs))) {
407  return false;
408  }
409 
411  return true;
412  }
414  return false;
415  }
416  if (lhs == Id3v2FrameIds::lTitle || lhs == Id3v2FrameIds::sTitle) {
417  return true;
418  }
419  if (rhs == Id3v2FrameIds::lTitle || rhs == Id3v2FrameIds::sTitle) {
420  return false;
421  }
422  bool lhstextfield = Id3v2FrameIds::isTextFrame(lhs);
423  bool rhstextfield = Id3v2FrameIds::isTextFrame(rhs);
424  if (lhstextfield && !rhstextfield) {
425  return true;
426  }
427  if (!lhstextfield && rhstextfield) {
428  return false;
429  }
430  if (lhs == Id3v2FrameIds::lCover || lhs == Id3v2FrameIds::sCover) {
431  return false;
432  }
433  if (rhs == Id3v2FrameIds::lCover || rhs == Id3v2FrameIds::sCover) {
434  return true;
435  }
436  return lhs < rhs;
437 }
438 
450 Id3v2TagMaker::Id3v2TagMaker(Id3v2Tag &tag, Diagnostics &diag)
451  : m_tag(tag)
452  , m_framesSize(0)
453 {
454  static const string context("making ID3v2 tag");
455 
456  // check if version is supported
457  // (the version could have been changed using setVersion())
458  if (!tag.isVersionSupported()) {
459  diag.emplace_back(DiagLevel::Critical, "The ID3v2 tag version isn't supported.", context);
461  }
462 
463  // prepare frames
464  m_maker.reserve(tag.fields().size());
465  for (auto &pair : tag.fields()) {
466  try {
467  m_maker.emplace_back(pair.second.prepareMaking(tag.majorVersion(), diag));
468  m_framesSize += m_maker.back().requiredSize();
469  } catch (const Failure &) {
470  }
471  }
472 
473  // calculate required size
474  // -> header + size of frames
475  m_requiredSize = 10 + m_framesSize;
476 }
477 
485 void Id3v2TagMaker::make(std::ostream &stream, uint32 padding, Diagnostics &diag)
486 {
487  BinaryWriter writer(&stream);
488 
489  // write header
490  // -> signature
491  writer.writeUInt24BE(0x494433u);
492  // -> version
493  writer.writeByte(m_tag.majorVersion());
494  writer.writeByte(m_tag.revisionVersion());
495  // -> flags, but without extended header or compression bit set
496  writer.writeByte(m_tag.flags() & 0xBF);
497  // -> size (excluding header)
498  writer.writeSynchsafeUInt32BE(m_framesSize + padding);
499 
500  // write frames
501  for (auto &maker : m_maker) {
502  maker.make(writer);
503  }
504 
505  // write padding
506  for (; padding; --padding) {
507  stream.put(0);
508  }
509 }
510 
511 } // namespace TagParser
FieldMapBasedTagTraits< Id3v2Tag >::FieldType::IdentifierType IdentifierType
Definition: fieldbasedtag.h:36
bool hasPaddingReached() const
Returns whether the padding has reached.
Definition: id3v2frame.h:170
STL namespace.
TagDataType
Specifies the data type.
Definition: tagvalue.h:51
byte flags() const
Returns the flags read from the ID3v2 header.
Definition: id3v2tag.h:169
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:150
bool isLongId(uint32 id)
Returns an indication whether the specified id is a long frame id.
Definition: id3v2frameids.h:68
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:161
std::string frameIdString() const
Returns the frame ID as string.
Definition: id3v2frame.h:179
byte majorVersion() const
Returns the major version if known; otherwise returns 0.
Definition: id3v2tag.h:142
const IdentifierType & id() const
Returns the id of the current TagField.
bool isTextFrame(uint32 id)
Returns an indication whether the specified id is a text frame id.
Definition: id3v2frameids.h:84
void make(std::ostream &stream, uint32 padding, Diagnostics &diag)
Saves the tag (specified when constructing the object) to the specified stream.
Definition: id3v2tag.cpp:485
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.
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:112
uint32 totalSize() const
Returns the total size of the frame in bytes.
Definition: id3v2frame.h:203