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