Tag Parser  6.4.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 "../exceptions.h"
5 
6 #include <c++utilities/conversion/stringconversion.h>
7 #include <c++utilities/conversion/stringbuilder.h>
8 
9 using namespace std;
10 using namespace IoUtilities;
11 using namespace ConversionUtilities;
12 
13 namespace Media {
14 
20 uint32 Id3v2Tag::fieldId(KnownField field) const
21 {
22  using namespace Id3v2FrameIds;
23  if(m_majorVersion >= 3) {
24  switch(field) {
25  case KnownField::Album: return lAlbum;
26  case KnownField::Artist: return lArtist;
27  case KnownField::Comment: return lComment;
28  case KnownField::Year: return lYear;
29  case KnownField::RecordDate: return lRecordDate;
30  case KnownField::Title: return lTitle;
31  case KnownField::Genre: return lGenre;
34  case KnownField::Encoder: return lEncoder;
35  case KnownField::Bpm: return lBpm;
36  case KnownField::Cover: return lCover;
37  case KnownField::Lyricist: return lWriter;
38  case KnownField::Length: return lLength;
39  case KnownField::Language: return lLanguage;
40  case KnownField::EncoderSettings: return lEncoderSettings;
42  case KnownField::SynchronizedLyrics: return lSynchronizedLyrics;
43  case KnownField::Grouping: return lGrouping;
45  case KnownField::Composer: return lComposer;
46  case KnownField::Rating: return lRating;
47  default:
48  ;
49  }
50  } else {
51  switch(field) {
52  case KnownField::Album: return sAlbum;
53  case KnownField::Artist: return sArtist;
54  case KnownField::Comment: return sComment;
55  case KnownField::Year: return sYear;
56  case KnownField::RecordDate: return sRecordDate;
57  case KnownField::Title: return sTitle;
58  case KnownField::Genre: return sGenre;
61  case KnownField::Encoder: return sEncoder;
62  case KnownField::Bpm: return sBpm;
63  case KnownField::Cover: return sCover;
64  case KnownField::Lyricist: return sWriter;
65  case KnownField::Length: return sLength;
66  case KnownField::Language: return sLanguage;
67  case KnownField::EncoderSettings: return sEncoderSettings;
69  case KnownField::SynchronizedLyrics: return sSynchronizedLyrics;
70  case KnownField::Grouping: return sGrouping;
72  case KnownField::Composer: return sComposer;
73  case KnownField::Rating: return sRating;
74  default:
75  ;
76  }
77  }
78  return 0;
79 }
80 
81 KnownField Id3v2Tag::knownField(const uint32 &id) const
82 {
83  using namespace Id3v2FrameIds;
84  switch(id) {
85  case lAlbum: return KnownField::Album;
86  case lArtist: return KnownField::Artist;
87  case lComment: return KnownField::Comment;
88  case lYear: return KnownField::Year;
89  case lRecordDate: return KnownField::RecordDate;
90  case lTitle: return KnownField::Title;
91  case lGenre: return KnownField::Genre;
94  case lEncoder: return KnownField::Encoder;
95  case lBpm: return KnownField::Bpm;
96  case lCover: return KnownField::Cover;
97  case lWriter: return KnownField::Lyricist;
98  case lLanguage: return KnownField::Language;
99  case lLength: return KnownField::Length;
100  case lEncoderSettings: return KnownField::EncoderSettings;
102  case lSynchronizedLyrics: return KnownField::SynchronizedLyrics;
103  case lGrouping: return KnownField::Grouping;
105  case sAlbum: return KnownField::Album;
106  case sArtist: return KnownField::Artist;
107  case sComment: return KnownField::Comment;
108  case sYear: return KnownField::Year;
109  case sRecordDate: return KnownField::RecordDate;
110  case sTitle: return KnownField::Title;
111  case sGenre: return KnownField::Genre;
113  case sEncoder: return KnownField::Encoder;
114  case sBpm: return KnownField::Bpm;
115  case sCover: return KnownField::Cover;
116  case sWriter: return KnownField::Lyricist;
117  case sLanguage: return KnownField::Language;
118  case sLength: return KnownField::Length;
119  case sEncoderSettings: return KnownField::EncoderSettings;
121  case sSynchronizedLyrics: return KnownField::SynchronizedLyrics;
122  case sGrouping: return KnownField::Grouping;
124  default: return KnownField::Invalid;
125  }
126 }
127 
128 TagDataType Id3v2Tag::proposedDataType(const uint32 &id) const
129 {
130  using namespace Id3v2FrameIds;
131  switch(id) {
132  case lLength: case sLength:
133  return TagDataType::TimeSpan;
134  case lBpm: case sBpm:
135  return TagDataType::Integer;
136  case lTrackPosition: case sTrackPosition:
137  case lDiskPosition:
138  return TagDataType::PositionInSet;
139  case lCover: case sCover:
140  return TagDataType::Picture;
141  default:
143  return TagDataType::Text;
144  } else {
145  return TagDataType::Undefined;
146  }
147  }
148 }
149 
150 const TagValue &Id3v2Tag::value(const typename Id3v2Frame::identifierType &id) const
151 {
153 }
154 
155 bool Id3v2Tag::setValue(const typename Id3v2Frame::identifierType &id, const TagValue &value)
156 {
158 }
159 
167 void Id3v2Tag::parse(istream &stream, const uint64 maximalSize)
168 {
169  // prepare parsing
170  invalidateStatus();
171  static const string context("parsing ID3v2 tag");
172  BinaryReader reader(&stream);
173  uint64 startOffset = stream.tellg();
174 
175  // check whether the header is truncated
176  if(maximalSize && maximalSize < 10) {
177  addNotification(NotificationType::Critical, "ID3v2 header is truncated (at least 10 bytes expected).", context);
178  throw TruncatedDataException();
179  }
180 
181  // read signature: ID3
182  if(reader.readUInt24BE() == 0x494433u) {
183  // read header data
184  byte majorVersion = reader.readByte();
185  byte revisionVersion = reader.readByte();
186  setVersion(majorVersion, revisionVersion);
187  m_flags = reader.readByte();
188  m_sizeExcludingHeader = reader.readSynchsafeUInt32BE();
189  m_size = 10 + m_sizeExcludingHeader;
190  if(m_sizeExcludingHeader == 0) {
191  addNotification(NotificationType::Warning, "ID3v2 tag seems to be empty.", context);
192  } else {
193  // check if the version
194  if(!isVersionSupported()) {
195  addNotification(NotificationType::Critical, "The ID3v2 tag couldn't be parsed, because its version is not supported.", context);
197  }
198 
199  // read extended header (if present)
200  if(hasExtendedHeader()) {
201  if(maximalSize && maximalSize < 14) {
202  addNotification(NotificationType::Critical, "Extended header denoted but not present.", context);
203  throw TruncatedDataException();
204  }
205  m_extendedHeaderSize = reader.readSynchsafeUInt32BE();
206  if(m_extendedHeaderSize < 6 || m_extendedHeaderSize > m_sizeExcludingHeader || (maximalSize && maximalSize < (10 + m_extendedHeaderSize))) {
207  addNotification(NotificationType::Critical, "Extended header is invalid/truncated.", context);
208  throw TruncatedDataException();
209  }
210  stream.seekg(m_extendedHeaderSize - 4, ios_base::cur);
211  }
212 
213  // how many bytes remain for frames and padding?
214  uint32 bytesRemaining = m_sizeExcludingHeader - m_extendedHeaderSize;
215  if(maximalSize && bytesRemaining > maximalSize) {
216  bytesRemaining = maximalSize;
217  addNotification(NotificationType::Critical, "Frames are truncated.", context);
218  }
219 
220  // read frames
221  auto pos = stream.tellg();
222  Id3v2Frame frame;
223  while(bytesRemaining) {
224  // seek to next frame
225  stream.seekg(pos);
226  // parse frame
227  try {
228  frame.parse(reader, majorVersion, bytesRemaining);
229  if(frame.id()) {
230  // add frame if parsing was successfull
231  if(Id3v2FrameIds::isTextFrame(frame.id()) && fields().count(frame.id()) == 1) {
232  addNotification(NotificationType::Warning, "The text frame " % frame.frameIdString() + " exists more than once.", context);
233  }
234  fields().insert(make_pair(frame.id(), frame));
235  }
236  } catch(const NoDataFoundException &) {
237  if(frame.hasPaddingReached()) {
238  m_paddingSize = startOffset + m_size - pos;
239  break;
240  }
241  } catch(const Failure &) {
242  // nothing to do here since notifications will be added anyways
243  }
244 
245  // add parsing notifications of frame
246  addNotifications(context, frame);
247  frame.invalidateNotifications();
248 
249  // calculate next frame offset
250  if(frame.totalSize() <= bytesRemaining) {
251  pos += frame.totalSize();
252  bytesRemaining -= frame.totalSize();
253  } else {
254  pos += bytesRemaining;
255  bytesRemaining = 0;
256  }
257  }
258 
259  // check for extended header
260  if(hasFooter()) {
261  if(maximalSize && m_size + 10 < maximalSize) {
262  // the footer does not provide additional information, just check the signature
263  stream.seekg(startOffset + (m_size += 10));
264  if(reader.readUInt24LE() != 0x494433u) {
265  addNotification(NotificationType::Critical, "Footer signature is invalid.", context);
266  }
267  // skip remaining footer
268  stream.seekg(7, ios_base::cur);
269  } else {
270  addNotification(NotificationType::Critical, "Footer denoted but not present.", context);
271  throw TruncatedDataException();
272  }
273  }
274  }
275  } else {
276  addNotification(NotificationType::Critical, "Signature is invalid.", context);
277  throw InvalidDataException();
278  }
279 }
280 
291 Id3v2TagMaker Id3v2Tag::prepareMaking()
292 {
293  return Id3v2TagMaker(*this);
294 }
295 
303 void Id3v2Tag::make(ostream &stream, uint32 padding)
304 {
305  prepareMaking().make(stream, padding);
306 }
307 
312 void Id3v2Tag::setVersion(byte majorVersion, byte revisionVersion)
313 {
314  m_majorVersion = majorVersion;
315  m_revisionVersion = revisionVersion;
316  m_version = argsToString('2', '.', majorVersion, '.', revisionVersion);
317 }
318 
329 bool FrameComparer::operator()(const uint32 &lhs, const uint32 &rhs) const
330 {
331  if(lhs == rhs) {
332  return false;
333  }
334  const bool lhsLong = Id3v2FrameIds::isLongId(lhs);
335  const bool rhsLong = Id3v2FrameIds::isLongId(rhs);
336  if(((lhsLong && !rhsLong) && (lhs == Id3v2FrameIds::convertToLongId(rhs)))
337  || ((!lhsLong && rhsLong) && (Id3v2FrameIds::convertToLongId(lhs) == rhs))) {
338  return false;
339  }
340 
342  return true;
343  }
345  return false;
346  }
347  if(lhs == Id3v2FrameIds::lTitle || lhs == Id3v2FrameIds::sTitle) {
348  return true;
349  }
350  if(rhs == Id3v2FrameIds::lTitle || rhs == Id3v2FrameIds::sTitle) {
351  return false;
352  }
353  bool lhstextfield = Id3v2FrameIds::isTextFrame(lhs);
354  bool rhstextfield = Id3v2FrameIds::isTextFrame(rhs);
355  if(lhstextfield && !rhstextfield) {
356  return true;
357  }
358  if(!lhstextfield && rhstextfield) {
359  return false;
360  }
361  if(lhs == Id3v2FrameIds::lCover || lhs == Id3v2FrameIds::sCover) {
362  return false;
363  }
364  if(rhs == Id3v2FrameIds::lCover || rhs == Id3v2FrameIds::sCover) {
365  return true;
366  }
367  return lhs < rhs;
368 }
369 
381 Id3v2TagMaker::Id3v2TagMaker(Id3v2Tag &tag) :
382  m_tag(tag),
383  m_framesSize(0)
384 {
385  tag.invalidateStatus();
386  static const string context("making ID3v2 tag");
387 
388  // check if version is supported
389  // (the version could have been changed using setVersion())
390  if(!tag.isVersionSupported()) {
391  tag.addNotification(NotificationType::Critical, "The ID3v2 tag version isn't supported.", context);
393  }
394 
395  // prepare frames
396  m_maker.reserve(tag.fields().size());
397  for(auto &pair : tag.fields()) {
398  try {
399  m_maker.emplace_back(pair.second.prepareMaking(tag.majorVersion()));
400  m_framesSize += m_maker.back().requiredSize();
401  } catch(const Failure &) {
402  // nothing to do here; notifications will be added anyways
403  }
404  m_tag.addNotifications(pair.second);
405  }
406 
407  // calculate required size
408  // -> header + size of frames
409  m_requiredSize = 10 + m_framesSize;
410 }
411 
419 void Id3v2TagMaker::make(std::ostream &stream, uint32 padding)
420 {
421  BinaryWriter writer(&stream);
422 
423  // write header
424  // -> signature
425  writer.writeUInt24BE(0x494433u);
426  // -> version
427  writer.writeByte(m_tag.majorVersion());
428  writer.writeByte(m_tag.revisionVersion());
429  // -> flags, but without extended header or compression bit set
430  writer.writeByte(m_tag.flags() & 0xBF);
431  // -> size (excluding header)
432  writer.writeSynchsafeUInt32BE(m_framesSize + padding);
433 
434  // write frames
435  for(auto &maker : m_maker) {
436  maker.make(writer);
437  }
438 
439  // write padding
440  for(; padding; --padding) {
441  stream.put(0);
442  }
443 }
444 
445 }
The TagValue class wraps values of different types.
Definition: tagvalue.h:64
void invalidateStatus()
Invalidates the current status.
bool isLongId(uint32 id)
Returns an indication whether the specified id is a long frame id.
Definition: id3v2frameids.h:68
void parse(IoUtilities::BinaryReader &reader, const uint32 version, const uint32 maximalSize=0)
Parses a frame from the stream read using the specified reader.
Definition: id3v2frame.cpp:116
KnownField
Specifies the field.
Definition: tag.h:42
uint32 totalSize() const
Returns the total size of the frame in bytes.
Definition: id3v2frame.h:223
STL namespace.
byte majorVersion() const
Returns the major version if known; otherwise returns 0.
Definition: id3v2tag.h:145
void addNotification(const Notification &notification)
This protected method is meant to be called by the derived class to add a notification.
const identifierType & id() const
Returns the id of the current TagField.
The Id3v2Frame class is used by Id3v2Tag to store the fields.
Definition: id3v2frame.h:100
std::string frameIdString() const
Returns the frame ID as string.
Definition: id3v2frame.h:199
Contains utility classes helping to read and write streams.
const std::multimap< typename FieldType::identifierType, FieldType, Compare > & fields() const
Returns the fields of the tag by providing direct access to the field map of the tag.
The exception that is thrown when the data to be parsed or to be made seems invalid and therefore can...
Definition: exceptions.h:27
bool hasPaddingReached() const
Returns whether the padding has reached.
Definition: id3v2frame.h:190
The class inherits from std::exception and serves as base class for exceptions thrown by the elements...
Definition: exceptions.h:11
The FieldMapBasedTag provides a generic implementation of Tag which stores the tag fields using std::...
Definition: fieldbasedtag.h:25
bool isTextFrame(uint32 id)
Returns an indication whether the specified id is a text frame id.
Definition: id3v2frameids.h:84
Implementation of Media::Tag for ID3v2 tags.
Definition: id3v2tag.h:55
The exception that is thrown when the data to be parsed is truncated and therefore can not be parsed ...
Definition: exceptions.h:35
void invalidateNotifications()
Invalidates the object&#39;s notifications.
uint32 convertToLongId(uint32 id)
Converts the specified short frame ID to the equivalent long frame ID.
bool isVersionSupported() const
Returns an indication whether the version is supported by the Id3v2Tag class.
Definition: id3v2tag.h:164
TagDataType
Specifies the data type.
Definition: tagvalue.h:51
The exception that is thrown when an operation fails because the detected or specified version is not...
Definition: exceptions.h:51
The exception that is thrown when the data to be parsed holds no parsable information.
Definition: exceptions.h:19
Contains all classes and functions of the TagInfo library.
Definition: exceptions.h:9
The Id3v2TagMaker class helps writing ID3v2 tags.
Definition: id3v2tag.h:20