Tag Parser  6.2.1
C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags
id3v2frame.cpp
Go to the documentation of this file.
1 #include "./id3v2frame.h"
2 #include "./id3genres.h"
3 #include "./id3v2frameids.h"
4 
5 #include "../exceptions.h"
6 
7 #include <c++utilities/conversion/stringconversion.h>
8 #include <c++utilities/conversion/stringbuilder.h>
9 
10 #include <zlib.h>
11 
12 #include <algorithm>
13 #include <cstring>
14 #include <memory>
15 
16 using namespace std;
17 using namespace ConversionUtilities;
18 using namespace ChronoUtilities;
19 using namespace IoUtilities;
20 
21 namespace Media {
22 
31 Id3v2Frame::Id3v2Frame() :
32  m_flag(0),
33  m_group(0),
34  m_parsedVersion(0),
35  m_dataSize(0),
36  m_totalSize(0),
37  m_padding(false)
38 {}
39 
43 Id3v2Frame::Id3v2Frame(const identifierType &id, const TagValue &value, const byte group, const int16 flag) :
44  TagField<Id3v2Frame>(id, value),
45  m_flag(flag),
46  m_group(group),
47  m_parsedVersion(0),
48  m_dataSize(0),
49  m_totalSize(0),
50  m_padding(false)
51 {}
52 
57 template<class stringtype>
58 int parseGenreIndex(const stringtype &denotation, bool isBigEndian = false)
59 {
60  int index = -1;
61  for(auto c : denotation) {
62  if(sizeof(typename stringtype::value_type) == 2 && isBigEndian != CONVERSION_UTILITIES_IS_BYTE_ORDER_BIG_ENDIAN) {
63  c = swapOrder(static_cast<uint16>(c));
64  }
65  if(index == -1) {
66  switch(c) {
67  case ' ':
68  break;
69  case '(':
70  index = 0;
71  break;
72  case '\0':
73  return -1;
74  default:
75  if(c >= '0' && c <= '9') {
76  index = c - '0';
77  } else {
78  return -1;
79  }
80  }
81  } else {
82  switch(c) {
83  case ')':
84  return index;
85  case '\0':
86  return index;
87  default:
88  if(c >= '0' && c <= '9') {
89  index = index * 10 + c - '0';
90  } else {
91  return -1;
92  }
93  }
94  }
95  }
96  return index;
97 }
98 
109 void Id3v2Frame::parse(BinaryReader &reader, const uint32 version, const uint32 maximalSize)
110 {
112  clear();
113  static const string defaultContext("parsing ID3v2 frame");
114  string context;
115 
116  // parse header
117  if(version < 3) {
118  // parse header for ID3v2.1 and ID3v2.2
119  // -> read ID
120  setId(reader.readUInt24BE());
121  if(id() & 0xFFFF0000u) {
122  m_padding = false;
123  } else {
124  // padding reached
125  m_padding = true;
126  addNotification(NotificationType::Debug, "Frame ID starts with null-byte -> padding reached.", defaultContext);
127  throw NoDataFoundException();
128  }
129 
130  // -> update context
131  context = "parsing " % frameIdString() + " frame";
132 
133  // -> read size, check whether frame is truncated
134  m_dataSize = reader.readUInt24BE();
135  m_totalSize = m_dataSize + 6;
136  if(m_totalSize > maximalSize) {
137  addNotification(NotificationType::Warning, "The frame is truncated and will be ignored.", context);
138  throw TruncatedDataException();
139  }
140 
141  // -> no flags/group in ID3v2.2
142  m_flag = 0;
143  m_group = 0;
144 
145  } else {
146  // parse header for ID3v2.3 and ID3v2.4
147  // -> read ID
148  setId(reader.readUInt32BE());
149  if(id() & 0xFF000000u) {
150  m_padding = false;
151  } else {
152  // padding reached
153  m_padding = true;
154  addNotification(NotificationType::Debug, "Frame ID starts with null-byte -> padding reached.", defaultContext);
155  throw NoDataFoundException();
156  }
157 
158  // -> update context
159  context = "parsing " % frameIdString() + " frame";
160 
161  // -> read size, check whether frame is truncated
162  m_dataSize = version >= 4
163  ? reader.readSynchsafeUInt32BE()
164  : reader.readUInt32BE();
165  m_totalSize = m_dataSize + 10;
166  if(m_totalSize > maximalSize) {
167  addNotification(NotificationType::Warning, "The frame is truncated and will be ignored.", context);
168  throw TruncatedDataException();
169  }
170 
171  // -> read flags and group
172  m_flag = reader.readUInt16BE();
173  m_group = hasGroupInformation() ? reader.readByte() : 0;
174  if(isEncrypted()) {
175  // encryption is not implemented
176  addNotification(NotificationType::Critical, "Encrypted frames aren't supported.", context);
178  }
179  }
180 
181  // frame size mustn't be 0
182  if(m_dataSize <= 0) {
183  addNotification(NotificationType::Critical, "The frame size is 0.", context);
184  throw InvalidDataException();
185  }
186 
187  // parse the data
188  unique_ptr<char[]> buffer;
189 
190  // -> decompress data if compressed; otherwise just read it
191  if(isCompressed()) {
192  uLongf decompressedSize = version >= 4 ? reader.readSynchsafeUInt32BE() : reader.readUInt32BE();
193  if(decompressedSize < m_dataSize) {
194  addNotification(NotificationType::Critical, "The decompressed size is smaller then the compressed size.", context);
195  throw InvalidDataException();
196  }
197  auto bufferCompressed = make_unique<char[]>(m_dataSize);;
198  reader.read(bufferCompressed.get(), m_dataSize);
199  buffer = make_unique<char[]>(decompressedSize);
200  switch(uncompress(reinterpret_cast<Bytef *>(buffer.get()), &decompressedSize, reinterpret_cast<Bytef *>(bufferCompressed.get()), m_dataSize)) {
201  case Z_MEM_ERROR:
202  addNotification(NotificationType::Critical, "Decompressing failed. The source buffer was too small.", context);
203  throw InvalidDataException();
204  case Z_BUF_ERROR:
205  addNotification(NotificationType::Critical, "Decompressing failed. The destination buffer was too small.", context);
206  throw InvalidDataException();
207  case Z_DATA_ERROR:
208  addNotification(NotificationType::Critical, "Decompressing failed. The input data was corrupted or incomplete.", context);
209  throw InvalidDataException();
210  case Z_OK:
211  break;
212  default:
213  addNotification(NotificationType::Critical, "Decompressing failed (unknown reason).", context);
214  throw InvalidDataException();
215  }
216  m_dataSize = decompressedSize;
217  } else {
218  buffer = make_unique<char[]>(m_dataSize);
219  reader.read(buffer.get(), m_dataSize);
220  }
221 
222  // -> get tag value depending of field type
223  if(Id3v2FrameIds::isTextFrame(id())) {
224  // frame contains text
225  TagTextEncoding dataEncoding = parseTextEncodingByte(*buffer.get()); // the first byte stores the encoding
226  if((version >= 3 &&
228  || (version < 3 && id() == Id3v2FrameIds::sTrackPosition)) {
229  // the track number or the disk number frame
230  try {
231  PositionInSet position;
232  if(characterSize(dataEncoding) > 1) {
233  position = PositionInSet(parseWideString(buffer.get() + 1, m_dataSize - 1, dataEncoding));
234  } else {
235  position = PositionInSet(parseString(buffer.get() + 1, m_dataSize - 1, dataEncoding));
236  }
237  value().assignPosition(position);
238  } catch(const ConversionException &) {
239  addNotification(NotificationType::Warning, "The value of track/disk position frame is not numeric and will be ignored.", context);
240  }
241 
242  } else if((version >= 3 && id() == Id3v2FrameIds::lLength) || (version < 3 && id() == Id3v2FrameIds::sLength)) {
243  // frame contains length
244  try {
245  string milliseconds;
246  if(dataEncoding == TagTextEncoding::Utf16BigEndian || dataEncoding == TagTextEncoding::Utf16LittleEndian) {
247  const auto parsedStringRef = parseSubstring(buffer.get() + 1, m_dataSize - 1, dataEncoding);
248  const auto convertedStringData = dataEncoding == TagTextEncoding::Utf16BigEndian
249  ? convertUtf16BEToUtf8(get<0>(parsedStringRef), get<1>(parsedStringRef))
250  : convertUtf16LEToUtf8(get<0>(parsedStringRef), get<1>(parsedStringRef));
251  milliseconds = string(convertedStringData.first.get(), convertedStringData.second);
252  } else { // Latin-1 or UTF-8
253  milliseconds = parseString(buffer.get() + 1, m_dataSize - 1, dataEncoding);
254  }
255  value().assignTimeSpan(TimeSpan::fromMilliseconds(stringToNumber<double>(milliseconds)));
256  } catch (const ConversionException &) {
257  addNotification(NotificationType::Warning, "The value of the length frame is not numeric and will be ignored.", context);
258  }
259 
260  } else if((version >= 3 && id() == Id3v2FrameIds::lGenre) || (version < 3 && id() == Id3v2FrameIds::sGenre)) {
261  // genre/content type
262  int genreIndex;
263  if(characterSize(dataEncoding) > 1) {
264  auto genreDenotation = parseWideString(buffer.get() + 1, m_dataSize - 1, dataEncoding);
265  genreIndex = parseGenreIndex(genreDenotation, dataEncoding == TagTextEncoding::Utf16BigEndian);
266  } else {
267  auto genreDenotation = parseString(buffer.get() + 1, m_dataSize - 1, dataEncoding);
268  genreIndex = parseGenreIndex(genreDenotation);
269  }
270  if(genreIndex != -1) {
271  // genre is specified as ID3 genre number
272  value().assignStandardGenreIndex(genreIndex);
273  } else {
274  // genre is specified as string
275  // string might be null terminated
276  auto substr = parseSubstring(buffer.get() + 1, m_dataSize - 1, dataEncoding);
277  value().assignData(get<0>(substr), get<1>(substr), TagDataType::Text, dataEncoding);
278  }
279  } else { // any other text frame
280  // string might be null terminated
281  auto substr = parseSubstring(buffer.get() + 1, m_dataSize - 1, dataEncoding);
282  value().assignData(get<0>(substr), get<1>(substr), TagDataType::Text, dataEncoding);
283  }
284 
285  } else if(version >= 3 && id() == Id3v2FrameIds::lCover) {
286  // frame stores picture
287  byte type;
288  parsePicture(buffer.get(), m_dataSize, value(), type);
289  setTypeInfo(type);
290 
291  } else if(version < 3 && id() == Id3v2FrameIds::sCover) {
292  // frame stores legacy picutre
293  byte type;
294  parseLegacyPicture(buffer.get(), m_dataSize, value(), type);
295  setTypeInfo(type);
296 
297  } else if(((version >= 3 && id() == Id3v2FrameIds::lComment) || (version < 3 && id() == Id3v2FrameIds::sComment))
298  || ((version >= 3 && id() == Id3v2FrameIds::lUnsynchronizedLyrics) || (version < 3 && id() == Id3v2FrameIds::sUnsynchronizedLyrics))) {
299  // comment frame or unsynchronized lyrics frame (these two frame types have the same structure)
300  parseComment(buffer.get(), m_dataSize, value());
301 
302  } else {
303  // unknown frame
304  value().assignData(buffer.get(), m_dataSize, TagDataType::Undefined);
305  }
306 }
307 
319 {
320  return Id3v2FrameMaker(*this, version);
321 }
322 
331 void Id3v2Frame::make(BinaryWriter &writer, const uint32 version)
332 {
333  prepareMaking(version).make(writer);
334 }
335 
340 {
341  m_flag = 0;
342  m_group = 0;
343  m_parsedVersion = 0;
344  m_dataSize = 0;
345  m_totalSize = 0;
346  m_padding = false;
347 }
348 
360 Id3v2FrameMaker::Id3v2FrameMaker(Id3v2Frame &frame, const byte version) :
361  m_frame(frame),
362  m_frameId(m_frame.id()),
363  m_version(version)
364 {
365  m_frame.invalidateStatus();
366  const string context("making " % m_frame.frameIdString() + " frame");
367 
368  // validate assigned data
369  if(m_frame.value().isEmpty()) {
370  m_frame.addNotification(NotificationType::Critical, "Cannot make an empty frame.", context);
371  throw InvalidDataException();
372  }
373  if(m_frame.isEncrypted()) {
374  m_frame.addNotification(NotificationType::Critical, "Cannot make an encrypted frame (isn't supported by this tagging library).", context);
375  throw InvalidDataException();
376  }
377  if(m_frame.hasPaddingReached()) {
378  m_frame.addNotification(NotificationType::Critical, "Cannot make a frame which is marked as padding.", context);
379  throw InvalidDataException();
380  }
381  if(version < 3 && m_frame.isCompressed()) {
382  m_frame.addNotification(NotificationType::Warning, "Compression is not supported by the version of ID3v2 and won't be applied.", context);
383  }
384  if(version < 3 && (m_frame.flag() || m_frame.group())) {
385  m_frame.addNotification(NotificationType::Warning, "The existing flag and group information is not supported by the version of ID3v2 and will be ignored/discarted.", context);
386  }
387 
388  // convert frame ID if necessary
389  if(version >= 3) {
390  if(Id3v2FrameIds::isShortId(m_frameId)) {
391  // try to convert the short frame ID to its long equivalent
392  if(!(m_frameId = Id3v2FrameIds::convertToLongId(m_frameId))) {
393  m_frame.addNotification(NotificationType::Critical, "The short frame ID can't be converted to its long equivalent which is needed to use the frame in a newer version of ID3v2.", context);
394  throw InvalidDataException();
395  }
396  }
397  } else {
398  if(Id3v2FrameIds::isLongId(m_frameId)) {
399  // try to convert the long frame ID to its short equivalent
400  if(!(m_frameId = Id3v2FrameIds::convertToShortId(m_frameId))) {
401  m_frame.addNotification(NotificationType::Critical, "The long frame ID can't be converted to its short equivalent which is needed to use the frame in the old version of ID3v2.", context);
402  throw InvalidDataException();
403  }
404  }
405  }
406 
407  // make actual data depending on the frame ID
408  try {
409  if(Id3v2FrameIds::isTextFrame(m_frameId)) {
410  // it is a text frame
411  if((version >= 3 && (m_frameId == Id3v2FrameIds::lTrackPosition || m_frameId == Id3v2FrameIds::lDiskPosition))
412  || (version < 3 && m_frameId == Id3v2FrameIds::sTrackPosition)) {
413  // track number or the disk number frame
414  m_frame.makeString(m_data, m_decompressedSize, m_frame.value().toString(), TagTextEncoding::Latin1);
415  } else if((version >= 3 && m_frameId == Id3v2FrameIds::lLength)
416  || (version < 3 && m_frameId == Id3v2FrameIds::sLength)) {
417  // length frame
418  m_frame.makeString(m_data, m_decompressedSize, ConversionUtilities::numberToString(m_frame.value().toTimeSpan().totalMilliseconds()), TagTextEncoding::Latin1);
419  } else if(m_frame.value().type() == TagDataType::StandardGenreIndex && ((version >= 3 && m_frameId == Id3v2FrameIds::lGenre)
420  || (version < 3 && m_frameId == Id3v2FrameIds::sGenre))) {
421  // pre-defined genre frame
422  m_frame.makeString(m_data, m_decompressedSize, ConversionUtilities::numberToString(m_frame.value().toStandardGenreIndex()), TagTextEncoding::Latin1);
423  } else {
424  // any other text frame
425  m_frame.makeString(m_data, m_decompressedSize, m_frame.value().toString(), m_frame.value().dataEncoding()); // the same as a normal text frame
426  }
427 
428  } else if(version >= 3 && m_frameId == Id3v2FrameIds::lCover) {
429  // picture frame
430  m_frame.makePicture(m_data, m_decompressedSize, m_frame.value(), m_frame.isTypeInfoAssigned() ? m_frame.typeInfo() : 0);
431 
432  } else if(version < 3 && m_frameId == Id3v2FrameIds::sCover) {
433  // legacy picture frame
434  m_frame.makeLegacyPicture(m_data, m_decompressedSize, m_frame.value(), m_frame.isTypeInfoAssigned() ? m_frame.typeInfo() : 0);
435 
436  } else if(((version >= 3 && m_frameId == Id3v2FrameIds::lComment)
437  || (version < 3 && m_frameId == Id3v2FrameIds::sComment))
438  || ((version >= 3 && m_frameId == Id3v2FrameIds::lUnsynchronizedLyrics)
439  || (version < 3 && m_frameId == Id3v2FrameIds::sUnsynchronizedLyrics))) {
440  // the comment frame or the unsynchronized lyrics frame
441  m_frame.makeComment(m_data, m_decompressedSize, m_frame.value());
442 
443  } else {
444  // an unknown frame
445  // create buffer
446  m_data = make_unique<char[]>(m_decompressedSize = m_frame.value().dataSize());
447  // just write the data
448  copy(m_frame.value().dataPointer(), m_frame.value().dataPointer() + m_decompressedSize, m_data.get());
449  }
450  } catch(const ConversionException &) {
451  m_frame.addNotification(NotificationType::Critical, "Assigned value can not be converted appropriately.", context);
452  throw InvalidDataException();
453  }
454 
455  // apply compression if frame should be compressed
456  if(version >= 3 && m_frame.isCompressed()) {
457  m_dataSize = compressBound(m_decompressedSize);
458  auto compressedData = make_unique<char[]>(m_decompressedSize);
459  switch(compress(reinterpret_cast<Bytef *>(compressedData.get()), reinterpret_cast<uLongf *>(&m_dataSize), reinterpret_cast<Bytef *>(m_data.get()), m_decompressedSize)) {
460  case Z_MEM_ERROR:
461  m_frame.addNotification(NotificationType::Critical, "Decompressing failed. The source buffer was too small.", context);
462  throw InvalidDataException();
463  case Z_BUF_ERROR:
464  m_frame.addNotification(NotificationType::Critical, "Decompressing failed. The destination buffer was too small.", context);
465  throw InvalidDataException();
466  case Z_OK:
467  ;
468  }
469  m_data.swap(compressedData);
470  } else {
471  m_dataSize = m_decompressedSize;
472  }
473 
474  // calculate required size
475  // -> data size
476  m_requiredSize = m_dataSize;
477  if(version < 3) {
478  // -> header size
479  m_requiredSize += 6;
480  } else {
481  // -> header size
482  m_requiredSize += 10;
483  // -> group byte
484  if(m_frame.hasGroupInformation()) {
485  m_requiredSize += 1;
486  }
487  // -> decompressed size
488  if(version >= 3 && m_frame.isCompressed()) {
489  m_requiredSize += 4;
490  }
491  }
492 }
493 
501 void Id3v2FrameMaker::make(BinaryWriter &writer)
502 {
503  if(m_version < 3) {
504  writer.writeUInt24BE(m_frameId);
505  writer.writeUInt24BE(m_dataSize);
506  } else {
507  writer.writeUInt32BE(m_frameId);
508  if(m_version >= 4) {
509  writer.writeSynchsafeUInt32BE(m_dataSize);
510  } else {
511  writer.writeUInt32BE(m_dataSize);
512  }
513  writer.writeUInt16BE(m_frame.flag());
514  if(m_frame.hasGroupInformation()) {
515  writer.writeByte(m_frame.group());
516  }
517  if(m_version >= 3 && m_frame.isCompressed()) {
518  if(m_version >= 4) {
519  writer.writeSynchsafeUInt32BE(m_decompressedSize);
520  } else {
521  writer.writeUInt32BE(m_decompressedSize);
522  }
523  }
524  }
525  writer.write(m_data.get(), m_dataSize);
526 }
527 
535 {
536  switch(textEncodingByte) {
537  case 0: // Ascii
539  case 1: // Utf 16 with bom
541  case 2: // Utf 16 without bom
543  case 3: // Utf 8
544  return TagTextEncoding::Utf8;
545  default:
546  addNotification(NotificationType::Warning, "The charset of the frame is invalid. Latin-1 will be used.", "parsing encoding of frame " + frameIdString());
548  }
549 }
550 
555 {
556  switch(textEncoding) {
558  return 0;
560  return 3;
562  return 1;
564  return 2;
565  default:
566  return 0;
567  }
568 }
569 
584 tuple<const char *, size_t, const char *> Id3v2Frame::parseSubstring(const char *buffer, std::size_t bufferSize, TagTextEncoding &encoding, bool addWarnings)
585 {
586  tuple<const char *, size_t, const char *> res(buffer, 0, buffer + bufferSize);
587  switch(encoding) {
590  if(bufferSize >= 2) {
591  if(ConversionUtilities::LE::toUInt16(buffer) == 0xFEFF) {
592  if(encoding != TagTextEncoding::Utf16LittleEndian) {
593  addNotification(NotificationType::Critical, "Denoted character set doesn't match present BOM - assuming UTF-16 Little Endian.", "parsing frame " + frameIdString());
595  }
596  get<0>(res) += 2;
597  } else if(ConversionUtilities::BE::toUInt16(buffer) == 0xFEFF) {
598  if(encoding != TagTextEncoding::Utf16BigEndian) {
599  addNotification(NotificationType::Critical, "Denoted character set doesn't match present BOM - assuming UTF-16 Big Endian.", "parsing frame " + frameIdString());
601  }
602  get<0>(res) += 2;
603  }
604  }
605  const uint16 *pos = reinterpret_cast<const uint16 *>(get<0>(res));
606  for(; *pos != 0x0000; ++pos) {
607  if(pos < reinterpret_cast<const uint16 *>(get<2>(res))) {
608  get<1>(res) += 2;
609  } else {
610  if(addWarnings) {
611  addNotification(NotificationType::Warning, "Wide string in frame is not terminated proberly.", "parsing termination of frame " + frameIdString());
612  }
613  break;
614  }
615  }
616  get<2>(res) = reinterpret_cast<const char *>(++pos);
617  break;
618  }
619  default: {
620  if((bufferSize >= 3) && (ConversionUtilities::BE::toUInt24(buffer) == 0x00EFBBBF)) {
621  get<0>(res) += 3;
622  if(encoding != TagTextEncoding::Utf8) {
623  addNotification(NotificationType::Critical, "Denoted character set doesn't match present BOM - assuming UTF-8.", "parsing frame " + frameIdString());
624  encoding = TagTextEncoding::Utf8;
625  }
626  }
627  const char *pos = get<0>(res);
628  for(; *pos != 0x00; ++pos) {
629  if(pos < get<2>(res)) {
630  ++get<1>(res);
631  } else {
632  if(addWarnings) {
633  addNotification(NotificationType::Warning, "String in frame is not terminated proberly.", "parsing termination of frame " + frameIdString());
634  }
635  break;
636  }
637  }
638  get<2>(res) = ++pos;
639  break;
640  }
641  }
642  return res;
643 }
644 
650 string Id3v2Frame::parseString(const char *buffer, size_t dataSize, TagTextEncoding &encoding, bool addWarnings)
651 {
652  auto substr = parseSubstring(buffer, dataSize, encoding, addWarnings);
653  return string(get<0>(substr), get<1>(substr));
654 }
655 
663 u16string Id3v2Frame::parseWideString(const char *buffer, size_t dataSize, TagTextEncoding &encoding, bool addWarnings)
664 {
665  auto substr = parseSubstring(buffer, dataSize, encoding, addWarnings);
666  u16string res(reinterpret_cast<u16string::const_pointer>(get<0>(substr)), get<1>(substr) / 2);
667  if(encoding !=
668  #if defined(CONVERSION_UTILITIES_BYTE_ORDER_LITTLE_ENDIAN)
670  #elif defined(CONVERSION_UTILITIES_BYTE_ORDER_BIG_ENDIAN)
672  #else
673  # error "Host byte order not supported"
674  #endif
675  ) {
676  // ensure byte order matches host byte order
677  for(auto &c : res) {
678  c = ((c >> 8) & 0x00FF) | ((c << 8) & 0xFF00);
679  }
680  }
681  return res;
682 }
683 
693 void Id3v2Frame::parseBom(const char *buffer, size_t maxSize, TagTextEncoding &encoding)
694 {
695  switch(encoding) {
698  if((maxSize >= 2) && (ConversionUtilities::BE::toUInt16(buffer) == 0xFFFE)) {
700  } else if((maxSize >= 2) && (ConversionUtilities::BE::toUInt16(buffer) == 0xFEFF)) {
702  }
703  break;
704  default:
705  if((maxSize >= 3) && (ConversionUtilities::BE::toUInt24(buffer) == 0x00EFBBBF)) {
706  encoding = TagTextEncoding::Utf8;
707  addNotification(NotificationType::Warning, "UTF-8 byte order mark found in text frame.", "parsing byte oder mark of frame " + frameIdString());
708  }
709  }
710 }
711 
719 void Id3v2Frame::parseLegacyPicture(const char *buffer, std::size_t maxSize, TagValue &tagValue, byte &typeInfo)
720 {
721  static const string context("parsing ID3v2.2 picture frame");
722  if(maxSize < 6) {
723  addNotification(NotificationType::Critical, "Picture frame is incomplete.", context);
724  throw TruncatedDataException();
725  }
726  const char *end = buffer + maxSize;
727  auto dataEncoding = parseTextEncodingByte(*buffer); // the first byte stores the encoding
728  //auto imageFormat = parseSubstring(buffer + 1, 3, TagTextEncoding::Latin1);
729  typeInfo = static_cast<unsigned char>(*(buffer + 4));
730  auto substr = parseSubstring(buffer + 5, end - 5 - buffer, dataEncoding, true);
731  tagValue.setDescription(string(get<0>(substr), get<1>(substr)), dataEncoding);
732  if(get<2>(substr) >= end) {
733  addNotification(NotificationType::Critical, "Picture frame is incomplete (actual data is missing).", context);
734  throw TruncatedDataException();
735  }
736  tagValue.assignData(get<2>(substr), end - get<2>(substr), TagDataType::Picture, dataEncoding);
737 }
738 
746 void Id3v2Frame::parsePicture(const char *buffer, std::size_t maxSize, TagValue &tagValue, byte &typeInfo)
747 {
748  static const string context("parsing ID3v2.3 picture frame");
749  const char *end = buffer + maxSize;
750  auto dataEncoding = parseTextEncodingByte(*buffer); // the first byte stores the encoding
751  auto mimeTypeEncoding = TagTextEncoding::Latin1;
752  auto substr = parseSubstring(buffer + 1, maxSize - 1, mimeTypeEncoding, true);
753  if(get<1>(substr)) {
754  tagValue.setMimeType(string(get<0>(substr), get<1>(substr)));
755  }
756  if(get<2>(substr) >= end) {
757  addNotification(NotificationType::Critical, "Picture frame is incomplete (type info, description and actual data are missing).", context);
758  throw TruncatedDataException();
759  }
760  typeInfo = static_cast<unsigned char>(*get<2>(substr));
761  if(++get<2>(substr) >= end) {
762  addNotification(NotificationType::Critical, "Picture frame is incomplete (description and actual data are missing).", context);
763  throw TruncatedDataException();
764  }
765  substr = parseSubstring(get<2>(substr), end - get<2>(substr), dataEncoding, true);
766  tagValue.setDescription(string(get<0>(substr), get<1>(substr)), dataEncoding);
767  if(get<2>(substr) >= end) {
768  addNotification(NotificationType::Critical, "Picture frame is incomplete (actual data is missing).", context);
769  throw TruncatedDataException();
770  }
771  tagValue.assignData(get<2>(substr), end - get<2>(substr), TagDataType::Picture, dataEncoding);
772 }
773 
780 void Id3v2Frame::parseComment(const char *buffer, std::size_t dataSize, TagValue &tagValue)
781 {
782  static const string context("parsing comment/unsynchronized lyrics frame");
783  const char *end = buffer + dataSize;
784  if(dataSize < 5) {
785  addNotification(NotificationType::Critical, "Comment frame is incomplete.", context);
786  throw TruncatedDataException();
787  }
788  TagTextEncoding dataEncoding = parseTextEncodingByte(*buffer);
789  if(*(++buffer)) {
790  tagValue.setLanguage(string(buffer, 3));
791  }
792  auto substr = parseSubstring(buffer += 3, dataSize -= 4, dataEncoding, true);
793  tagValue.setDescription(string(get<0>(substr), get<1>(substr)), dataEncoding);
794  if(get<2>(substr) > end) {
795  addNotification(NotificationType::Critical, "Comment frame is incomplete (description not terminated?).", context);
796  throw TruncatedDataException();
797  }
798  substr = parseSubstring(get<2>(substr), end - get<2>(substr), dataEncoding, false);
799  tagValue.assignData(get<0>(substr), get<1>(substr), TagDataType::Text, dataEncoding);
800 }
801 
809 void Id3v2Frame::makeString(std::unique_ptr<char[]> &buffer, uint32 &bufferSize, const std::string &value, TagTextEncoding encoding)
810 {
811  makeEncodingAndData(buffer, bufferSize, encoding, value.data(), value.size());
812 }
813 
822 void Id3v2Frame::makeEncodingAndData(std::unique_ptr<char[]> &buffer, uint32 &bufferSize, TagTextEncoding encoding, const char *data, std::size_t dataSize)
823 {
824  // calculate buffer size
825  if(!data) {
826  dataSize = 0;
827  }
828  switch(encoding) {
831  case TagTextEncoding::Unspecified: // assumption
832  // allocate buffer
833  buffer = make_unique<char[]>(bufferSize = 1 + dataSize + 1);
834  break;
837  // allocate buffer
838  buffer = make_unique<char[]>(bufferSize = 1 + dataSize + 2);
839  break;
840  }
841  buffer[0] = makeTextEncodingByte(encoding); // set text encoding byte
842  if(dataSize > 0) {
843  copy(data, data + dataSize, buffer.get() + 1); // write string data
844  }
845 }
846 
850 void Id3v2Frame::makeLegacyPicture(unique_ptr<char[]> &buffer, uint32 &bufferSize, const TagValue &picture, byte typeInfo)
851 {
852  // calculate needed buffer size and create buffer
853  TagTextEncoding descriptionEncoding = picture.descriptionEncoding();
854  uint32 dataSize = picture.dataSize();
855  string::size_type descriptionLength = picture.description().find('\0');
856  if(descriptionLength == string::npos) {
857  descriptionLength = picture.description().length();
858  }
859  buffer = make_unique<char[]>(bufferSize = 1 + 3 + 1 + descriptionLength + (descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? 2 : 1) + dataSize);
860  // note: encoding byte + image format + picture type byte + description length + 1 or 2 null bytes (depends on encoding) + data size
861  char *offset = buffer.get();
862  // write encoding byte
863  *offset = makeTextEncodingByte(descriptionEncoding);
864  // write mime type
865  const char *imageFormat;
866  if(picture.mimeType() == "image/jpeg") {
867  imageFormat = "JPG";
868  } else if(picture.mimeType() == "image/png") {
869  imageFormat = "PNG";
870  } else if(picture.mimeType() == "image/gif") {
871  imageFormat = "GIF";
872  } else if(picture.mimeType() == "-->") {
873  imageFormat = picture.mimeType().data();
874  } else {
875  imageFormat = "UND";
876  }
877  strncpy(++offset, imageFormat, 3);
878  // write picture type
879  *(offset += 3) = typeInfo;
880  // write description
881  picture.description().copy(++offset, descriptionLength);
882  *(offset += descriptionLength) = 0x00; // terminate description and increase data offset
883  if(descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian) {
884  *(++offset) = 0x00;
885  }
886  // write actual data
887  copy(picture.dataPointer(), picture.dataPointer() + picture.dataSize(), ++offset);
888 }
889 
893 void Id3v2Frame::makePicture(unique_ptr<char[]> &buffer, uint32 &bufferSize, const TagValue &picture, byte typeInfo)
894 {
895  // calculate needed buffer size and create buffer
896  TagTextEncoding descriptionEncoding = picture.descriptionEncoding();
897  uint32 dataSize = picture.dataSize();
898  string::size_type mimeTypeLength = picture.mimeType().find('\0');
899  if(mimeTypeLength == string::npos) {
900  mimeTypeLength = picture.mimeType().length();
901  }
902  string::size_type descriptionLength = picture.description().find('\0');
903  if(descriptionLength == string::npos) {
904  descriptionLength = picture.description().length();
905  }
906  buffer = make_unique<char[]>(bufferSize = 1 + mimeTypeLength + 1 + 1 + descriptionLength + (descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? 2 : 1) + dataSize);
907  // note: encoding byte + mime type length + 0 byte + picture type byte + description length + 1 or 2 null bytes (depends on encoding) + data size
908  char *offset = buffer.get();
909  // write encoding byte
910  *offset = makeTextEncodingByte(descriptionEncoding);
911  // write mime type
912  picture.mimeType().copy(++offset, mimeTypeLength);
913  *(offset += mimeTypeLength) = 0x00; // terminate mime type
914  // write picture type
915  *(++offset) = typeInfo;
916  // write description
917  picture.description().copy(++offset, descriptionLength);
918  *(offset += descriptionLength) = 0x00; // terminate description and increase data offset
919  if(descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian) {
920  *(++offset) = 0x00;
921  }
922  // write actual data
923  copy(picture.dataPointer(), picture.dataPointer() + picture.dataSize(), ++offset);
924 }
925 
929 void Id3v2Frame::makeComment(unique_ptr<char[]> &buffer, uint32 &bufferSize, const TagValue &comment)
930 {
931  static const string context("making comment frame");
932  // check type and other values are valid
933  TagTextEncoding encoding = comment.dataEncoding();
934  if(!comment.description().empty() && encoding != comment.descriptionEncoding()) {
935  addNotification(NotificationType::Critical, "Data enoding and description encoding aren't equal.", context);
936  throw InvalidDataException();
937  }
938  const string &lng = comment.language();
939  if(lng.length() > 3) {
940  addNotification(NotificationType::Critical, "The language must be 3 bytes long (ISO-639-2).", context);
941  throw InvalidDataException();
942  }
943  // calculate needed buffer size and create buffer
944  string::size_type descriptionLength = comment.description().find('\0');
945  if(descriptionLength == string::npos) {
946  descriptionLength = comment.description().length();
947  }
948  const auto data = comment.toString();
949  buffer = make_unique<char[]>(bufferSize = 1 + 3 + descriptionLength + (encoding == TagTextEncoding::Utf16BigEndian || encoding == TagTextEncoding::Utf16LittleEndian ? 2 : 1) + data.size());
950  // note: encoding byte + language + description length + 1 or 2 null bytes + data size
951  char *offset = buffer.get();
952  // write encoding
953  *offset = makeTextEncodingByte(encoding);
954  // write language
955  for(unsigned int i = 0; i < 3; ++i) {
956  *(++offset) = (lng.length() > i) ? lng[i] : 0x00;
957  }
958  // write description
959  comment.description().copy(++offset, descriptionLength);
960  offset += descriptionLength;
961  *offset = 0x00; // terminate description and increase data offset
963  *(++offset) = 0x00;
964  }
965  // write actual data
966  data.copy(++offset, data.size());
967 }
968 
969 }
The TagValue class wraps values of different types.
Definition: tagvalue.h:63
void setDescription(const std::string &value, TagTextEncoding encoding=TagTextEncoding::Latin1)
Sets the description.
Definition: tagvalue.h:386
void invalidateStatus()
Invalidates the current status.
void setTypeInfo(const typeInfoType &typeInfo)
Sets the type info of the current TagField.
TagFieldTraits< Id3v2Frame >::identifierType identifierType
void cleared()
Ensures the field is cleared.
Definition: id3v2frame.cpp:339
TagTextEncoding dataEncoding() const
Returns the data encoding.
Definition: tagvalue.h:465
Id3v2FrameMaker prepareMaking(const uint32 version)
Prepares making.
Definition: id3v2frame.cpp:318
bool isLongId(uint32 id)
Returns an indication whether the specified id is a long frame id.
Definition: id3v2frameids.h:66
void clear()
Clears id, value, type info and sets default flag to false.
void makeString(std::unique_ptr< char[]> &buffer, uint32 &bufferSize, const std::string &value, TagTextEncoding encoding)
Writes an encoding denoation and the specified string value to a buffer.
Definition: id3v2frame.cpp:809
bool isShortId(uint32 id)
Returns an indication whether the specified id is a short frame id.
Definition: id3v2frameids.h:74
void make(IoUtilities::BinaryWriter &writer)
Saves the frame (specified when constructing the object) using the specified writer.
Definition: id3v2frame.cpp:501
char * dataPointer() const
Returns a pointer to the raw data assigned to the current instance.
Definition: tagvalue.h:362
int parseGenreIndex(const stringtype &denotation, bool isBigEndian=false)
Helper function to parse the genre index.
Definition: id3v2frame.cpp:58
std::tuple< const char *, size_t, const char * > parseSubstring(const char *buffer, std::size_t maxSize, TagTextEncoding &encoding, bool addWarnings=false)
Parses a substring in the specified buffer.
Definition: id3v2frame.cpp:584
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:109
std::string parseString(const char *buffer, std::size_t maxSize, TagTextEncoding &encoding, bool addWarnings=false)
Parses a substring in the specified buffer.
Definition: id3v2frame.cpp:650
STL namespace.
void assignStandardGenreIndex(int index)
Assigns the given standard genre index to be assigned.
Definition: tagvalue.cpp:610
void addNotification(const Notification &notification)
This protected method is meant to be called by the derived class to add a notification.
void parseLegacyPicture(const char *buffer, std::size_t maxSize, TagValue &tagValue, byte &typeInfo)
Parses the ID3v2.2 picture from the specified buffer.
Definition: id3v2frame.cpp:719
byte group() const
Returns the group.
Definition: id3v2frame.h:302
TagTextEncoding
Specifies the text encoding.
Definition: tagvalue.h:21
bool isCompressed() const
Returns whether the frame is compressed.
Definition: id3v2frame.h:260
const identifierType & id() const
Returns the id of the current TagField.
TagTextEncoding parseTextEncodingByte(byte textEncodingByte)
Returns the text encoding for the specified textEncodingByte.
Definition: id3v2frame.cpp:534
The Id3v2Frame class is used by Id3v2Tag to store the fields.
Definition: id3v2frame.h:100
void makeEncodingAndData(std::unique_ptr< char[]> &buffer, uint32 &bufferSize, TagTextEncoding encoding, const char *data, std::size_t m_dataSize)
Writes an encoding denoation and the specified data to a buffer.
Definition: id3v2frame.cpp:822
void assignTimeSpan(ChronoUtilities::TimeSpan value)
Assigns the given TimeSpan value.
Definition: tagvalue.h:278
bool hasGroupInformation() const
Returns whether the frame contains group information.
Definition: id3v2frame.h:277
std::string frameIdString() const
Returns the frame ID as string.
Definition: id3v2frame.h:196
The PositionInSet class describes the position of an element in a set which consists of a certain num...
Definition: positioninset.h:20
const std::string & language() const
Returns the language.
Definition: tagvalue.h:418
uint32 convertToShortId(uint32 id)
Converts the specified long frame ID to the equivalent short frame ID.
byte makeTextEncodingByte(TagTextEncoding textEncoding)
Returns a text encoding byte for the specified textEncoding.
Definition: id3v2frame.cpp:554
Contains utility classes helping to read and write streams.
The exception that is thrown when the data to be parsed or to be made seems invalid and therefore can...
Definition: exceptions.h:27
void make(IoUtilities::BinaryWriter &writer, const uint32 version)
Writes the frame to a stream using the specified writer and the specified ID3v2 version.
Definition: id3v2frame.cpp:331
TagTextEncoding descriptionEncoding() const
Returns the description encoding.
Definition: tagvalue.h:475
void parsePicture(const char *buffer, std::size_t maxSize, TagValue &tagValue, byte &typeInfo)
Parses the ID3v2.3 picture from the specified buffer.
Definition: id3v2frame.cpp:746
TagValue & value()
Returns the value of the current TagField.
const typeInfoType & typeInfo() const
Returns the type info of the current TagField.
The TagField class is used by FieldMapBasedTag to store the fields.
void assignPosition(PositionInSet value)
Assigns the given PositionInSet value.
Definition: tagvalue.h:265
void assignData(const char *data, size_t length, TagDataType type=TagDataType::Binary, TagTextEncoding encoding=TagTextEncoding::Latin1)
Assigns a copy of the given data.
Definition: tagvalue.cpp:624
void makePicture(std::unique_ptr< char[]> &buffer, uint32 &bufferSize, const TagValue &picture, byte typeInfo)
Writes the specified picture to the specified buffer (ID3v2.3).
Definition: id3v2frame.cpp:893
std::u16string parseWideString(const char *buffer, std::size_t dataSize, TagTextEncoding &encoding, bool addWarnings=false)
Parses a substring in the specified buffer.
Definition: id3v2frame.cpp:663
bool isTextFrame(uint32 id)
Returns an indication whether the specified id is a text frame id.
Definition: id3v2frameids.h:82
uint32 dataSize() const
Returns the size of the data stored in the frame in bytes.
Definition: id3v2frame.h:228
The exception that is thrown when the data to be parsed is truncated and therefore can not be parsed ...
Definition: exceptions.h:35
void makeLegacyPicture(std::unique_ptr< char[]> &buffer, uint32 &bufferSize, const TagValue &picture, byte typeInfo)
Writes the specified picture to the specified buffer (ID3v2.2).
Definition: id3v2frame.cpp:850
uint32 convertToLongId(uint32 id)
Converts the specified short frame ID to the equivalent long frame ID.
int characterSize(TagTextEncoding encoding)
Returns the size of one character for the specified encoding in bytes.
Definition: tagvalue.h:34
bool isEncrypted() const
Returns whether the frame is encrypted.
Definition: id3v2frame.h:269
The Id3v2FrameMaker class helps making ID3v2 frames.
Definition: id3v2frame.h:23
The exception that is thrown when an operation fails because the detected or specified version is not...
Definition: exceptions.h:51
Id3v2Frame()
Constructs a new Id3v2Frame.
Definition: id3v2frame.cpp:31
void parseBom(const char *buffer, std::size_t maxSize, TagTextEncoding &encoding)
Parses a byte order mark from the specified buffer.
Definition: id3v2frame.cpp:693
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
void setLanguage(const std::string &value)
Sets the language.
Definition: tagvalue.h:429
void parseComment(const char *buffer, std::size_t maxSize, TagValue &tagValue)
Parses the comment/unsynchronized lyrics from the specified buffer.
Definition: id3v2frame.cpp:780
void setMimeType(const std::string &value)
Sets the MIME type.
Definition: tagvalue.h:408
uint16 flag() const
Returns the flags.
Definition: id3v2frame.h:204
const std::string & mimeType() const
Returns the MIME type.
Definition: tagvalue.h:397
void makeComment(std::unique_ptr< char[]> &buffer, uint32 &bufferSize, const TagValue &comment)
Writes the specified comment to the specified buffer.
Definition: id3v2frame.cpp:929
const std::string & description() const
Returns the description.
Definition: tagvalue.h:373
size_t dataSize() const
Returns the size of the assigned value in bytes.
Definition: tagvalue.h:351
TAG_PARSER_EXPORT const char * comment()
std::string toString(TagTextEncoding encoding=TagTextEncoding::Unspecified) const
Converts the value of the current TagValue object to its equivalent std::string representation.
Definition: tagvalue.h:306
void setId(const identifierType &id)
Sets the id of the current Tag Field.
TAG_PARSER_EXPORT const char * version()