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