Tag Parser  6.2.2
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 
23 namespace Id3v2TextEncodingBytes {
25 {
30 };
31 }
32 
41 Id3v2Frame::Id3v2Frame() :
42  m_flag(0),
43  m_group(0),
44  m_parsedVersion(0),
45  m_dataSize(0),
46  m_totalSize(0),
47  m_padding(false)
48 {}
49 
53 Id3v2Frame::Id3v2Frame(const identifierType &id, const TagValue &value, const byte group, const int16 flag) :
54  TagField<Id3v2Frame>(id, value),
55  m_flag(flag),
56  m_group(group),
57  m_parsedVersion(0),
58  m_dataSize(0),
59  m_totalSize(0),
60  m_padding(false)
61 {}
62 
67 template<class stringtype>
68 int parseGenreIndex(const stringtype &denotation, bool isBigEndian = false)
69 {
70  int index = -1;
71  for(auto c : denotation) {
72  if(sizeof(typename stringtype::value_type) == 2 && isBigEndian != CONVERSION_UTILITIES_IS_BYTE_ORDER_BIG_ENDIAN) {
73  c = swapOrder(static_cast<uint16>(c));
74  }
75  if(index == -1) {
76  switch(c) {
77  case ' ':
78  break;
79  case '(':
80  index = 0;
81  break;
82  case '\0':
83  return -1;
84  default:
85  if(c >= '0' && c <= '9') {
86  index = c - '0';
87  } else {
88  return -1;
89  }
90  }
91  } else {
92  switch(c) {
93  case ')':
94  return index;
95  case '\0':
96  return index;
97  default:
98  if(c >= '0' && c <= '9') {
99  index = index * 10 + c - '0';
100  } else {
101  return -1;
102  }
103  }
104  }
105  }
106  return index;
107 }
108 
119 void Id3v2Frame::parse(BinaryReader &reader, const uint32 version, const uint32 maximalSize)
120 {
122  clear();
123  static const string defaultContext("parsing ID3v2 frame");
124  string context;
125 
126  // parse header
127  if(version < 3) {
128  // parse header for ID3v2.1 and ID3v2.2
129  // -> read ID
130  setId(reader.readUInt24BE());
131  if(id() & 0xFFFF0000u) {
132  m_padding = false;
133  } else {
134  // padding reached
135  m_padding = true;
136  addNotification(NotificationType::Debug, "Frame ID starts with null-byte -> padding reached.", defaultContext);
137  throw NoDataFoundException();
138  }
139 
140  // -> update context
141  context = "parsing " % frameIdString() + " frame";
142 
143  // -> read size, check whether frame is truncated
144  m_dataSize = reader.readUInt24BE();
145  m_totalSize = m_dataSize + 6;
146  if(m_totalSize > maximalSize) {
147  addNotification(NotificationType::Warning, "The frame is truncated and will be ignored.", context);
148  throw TruncatedDataException();
149  }
150 
151  // -> no flags/group in ID3v2.2
152  m_flag = 0;
153  m_group = 0;
154 
155  } else {
156  // parse header for ID3v2.3 and ID3v2.4
157  // -> read ID
158  setId(reader.readUInt32BE());
159  if(id() & 0xFF000000u) {
160  m_padding = false;
161  } else {
162  // padding reached
163  m_padding = true;
164  addNotification(NotificationType::Debug, "Frame ID starts with null-byte -> padding reached.", defaultContext);
165  throw NoDataFoundException();
166  }
167 
168  // -> update context
169  context = "parsing " % frameIdString() + " frame";
170 
171  // -> read size, check whether frame is truncated
172  m_dataSize = version >= 4
173  ? reader.readSynchsafeUInt32BE()
174  : reader.readUInt32BE();
175  m_totalSize = m_dataSize + 10;
176  if(m_totalSize > maximalSize) {
177  addNotification(NotificationType::Warning, "The frame is truncated and will be ignored.", context);
178  throw TruncatedDataException();
179  }
180 
181  // -> read flags and group
182  m_flag = reader.readUInt16BE();
183  m_group = hasGroupInformation() ? reader.readByte() : 0;
184  if(isEncrypted()) {
185  // encryption is not implemented
186  addNotification(NotificationType::Critical, "Encrypted frames aren't supported.", context);
188  }
189  }
190 
191  // frame size mustn't be 0
192  if(m_dataSize <= 0) {
193  addNotification(NotificationType::Critical, "The frame size is 0.", context);
194  throw InvalidDataException();
195  }
196 
197  // parse the data
198  unique_ptr<char[]> buffer;
199 
200  // -> decompress data if compressed; otherwise just read it
201  if(isCompressed()) {
202  uLongf decompressedSize = version >= 4 ? reader.readSynchsafeUInt32BE() : reader.readUInt32BE();
203  if(decompressedSize < m_dataSize) {
204  addNotification(NotificationType::Critical, "The decompressed size is smaller than the compressed size.", context);
205  throw InvalidDataException();
206  }
207  auto bufferCompressed = make_unique<char[]>(m_dataSize);;
208  reader.read(bufferCompressed.get(), m_dataSize);
209  buffer = make_unique<char[]>(decompressedSize);
210  switch(uncompress(reinterpret_cast<Bytef *>(buffer.get()), &decompressedSize, reinterpret_cast<Bytef *>(bufferCompressed.get()), m_dataSize)) {
211  case Z_MEM_ERROR:
212  addNotification(NotificationType::Critical, "Decompressing failed. The source buffer was too small.", context);
213  throw InvalidDataException();
214  case Z_BUF_ERROR:
215  addNotification(NotificationType::Critical, "Decompressing failed. The destination buffer was too small.", context);
216  throw InvalidDataException();
217  case Z_DATA_ERROR:
218  addNotification(NotificationType::Critical, "Decompressing failed. The input data was corrupted or incomplete.", context);
219  throw InvalidDataException();
220  case Z_OK:
221  break;
222  default:
223  addNotification(NotificationType::Critical, "Decompressing failed (unknown reason).", context);
224  throw InvalidDataException();
225  }
226  m_dataSize = decompressedSize;
227  } else {
228  buffer = make_unique<char[]>(m_dataSize);
229  reader.read(buffer.get(), m_dataSize);
230  }
231 
232  // -> get tag value depending of field type
233  if(Id3v2FrameIds::isTextFrame(id())) {
234  // frame contains text
235  TagTextEncoding dataEncoding = parseTextEncodingByte(*buffer.get()); // the first byte stores the encoding
236  if((version >= 3 &&
238  || (version < 3 && id() == Id3v2FrameIds::sTrackPosition)) {
239  // the track number or the disk number frame
240  try {
241  PositionInSet position;
242  if(characterSize(dataEncoding) > 1) {
243  position = PositionInSet(parseWideString(buffer.get() + 1, m_dataSize - 1, dataEncoding));
244  } else {
245  position = PositionInSet(parseString(buffer.get() + 1, m_dataSize - 1, dataEncoding));
246  }
247  value().assignPosition(position);
248  } catch(const ConversionException &) {
249  addNotification(NotificationType::Warning, "The value of track/disk position frame is not numeric and will be ignored.", context);
250  }
251 
252  } else if((version >= 3 && id() == Id3v2FrameIds::lLength) || (version < 3 && id() == Id3v2FrameIds::sLength)) {
253  // frame contains length
254  try {
255  string milliseconds;
256  if(dataEncoding == TagTextEncoding::Utf16BigEndian || dataEncoding == TagTextEncoding::Utf16LittleEndian) {
257  const auto parsedStringRef = parseSubstring(buffer.get() + 1, m_dataSize - 1, dataEncoding);
258  const auto convertedStringData = dataEncoding == TagTextEncoding::Utf16BigEndian
259  ? convertUtf16BEToUtf8(get<0>(parsedStringRef), get<1>(parsedStringRef))
260  : convertUtf16LEToUtf8(get<0>(parsedStringRef), get<1>(parsedStringRef));
261  milliseconds = string(convertedStringData.first.get(), convertedStringData.second);
262  } else { // Latin-1 or UTF-8
263  milliseconds = parseString(buffer.get() + 1, m_dataSize - 1, dataEncoding);
264  }
265  value().assignTimeSpan(TimeSpan::fromMilliseconds(stringToNumber<double>(milliseconds)));
266  } catch (const ConversionException &) {
267  addNotification(NotificationType::Warning, "The value of the length frame is not numeric and will be ignored.", context);
268  }
269 
270  } else if((version >= 3 && id() == Id3v2FrameIds::lGenre) || (version < 3 && id() == Id3v2FrameIds::sGenre)) {
271  // genre/content type
272  int genreIndex;
273  if(characterSize(dataEncoding) > 1) {
274  auto genreDenotation = parseWideString(buffer.get() + 1, m_dataSize - 1, dataEncoding);
275  genreIndex = parseGenreIndex(genreDenotation, dataEncoding == TagTextEncoding::Utf16BigEndian);
276  } else {
277  auto genreDenotation = parseString(buffer.get() + 1, m_dataSize - 1, dataEncoding);
278  genreIndex = parseGenreIndex(genreDenotation);
279  }
280  if(genreIndex != -1) {
281  // genre is specified as ID3 genre number
282  value().assignStandardGenreIndex(genreIndex);
283  } else {
284  // genre is specified as string
285  // string might be null terminated
286  auto substr = parseSubstring(buffer.get() + 1, m_dataSize - 1, dataEncoding);
287  value().assignData(get<0>(substr), get<1>(substr), TagDataType::Text, dataEncoding);
288  }
289  } else { // any other text frame
290  // string might be null terminated
291  auto substr = parseSubstring(buffer.get() + 1, m_dataSize - 1, dataEncoding);
292  value().assignData(get<0>(substr), get<1>(substr), TagDataType::Text, dataEncoding);
293  }
294 
295  } else if(version >= 3 && id() == Id3v2FrameIds::lCover) {
296  // frame stores picture
297  byte type;
298  parsePicture(buffer.get(), m_dataSize, value(), type);
299  setTypeInfo(type);
300 
301  } else if(version < 3 && id() == Id3v2FrameIds::sCover) {
302  // frame stores legacy picutre
303  byte type;
304  parseLegacyPicture(buffer.get(), m_dataSize, value(), type);
305  setTypeInfo(type);
306 
307  } else if(((version >= 3 && id() == Id3v2FrameIds::lComment) || (version < 3 && id() == Id3v2FrameIds::sComment))
308  || ((version >= 3 && id() == Id3v2FrameIds::lUnsynchronizedLyrics) || (version < 3 && id() == Id3v2FrameIds::sUnsynchronizedLyrics))) {
309  // comment frame or unsynchronized lyrics frame (these two frame types have the same structure)
310  parseComment(buffer.get(), m_dataSize, value());
311 
312  } else {
313  // unknown frame
314  value().assignData(buffer.get(), m_dataSize, TagDataType::Undefined);
315  }
316 }
317 
329 {
330  return Id3v2FrameMaker(*this, version);
331 }
332 
341 void Id3v2Frame::make(BinaryWriter &writer, const uint32 version)
342 {
343  prepareMaking(version).make(writer);
344 }
345 
350 {
351  m_flag = 0;
352  m_group = 0;
353  m_parsedVersion = 0;
354  m_dataSize = 0;
355  m_totalSize = 0;
356  m_padding = false;
357 }
358 
370 Id3v2FrameMaker::Id3v2FrameMaker(Id3v2Frame &frame, const byte version) :
371  m_frame(frame),
372  m_frameId(m_frame.id()),
373  m_version(version)
374 {
375  m_frame.invalidateStatus();
376  const string context("making " % m_frame.frameIdString() + " frame");
377 
378  // validate assigned data
379  if(m_frame.value().isEmpty()) {
380  m_frame.addNotification(NotificationType::Critical, "Cannot make an empty frame.", context);
381  throw InvalidDataException();
382  }
383  if(m_frame.isEncrypted()) {
384  m_frame.addNotification(NotificationType::Critical, "Cannot make an encrypted frame (isn't supported by this tagging library).", context);
385  throw InvalidDataException();
386  }
387  if(m_frame.hasPaddingReached()) {
388  m_frame.addNotification(NotificationType::Critical, "Cannot make a frame which is marked as padding.", context);
389  throw InvalidDataException();
390  }
391  if(version < 3 && m_frame.isCompressed()) {
392  m_frame.addNotification(NotificationType::Warning, "Compression is not supported by the version of ID3v2 and won't be applied.", context);
393  }
394  if(version < 3 && (m_frame.flag() || m_frame.group())) {
395  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);
396  }
397 
398  // convert frame ID if necessary
399  if(version >= 3) {
400  if(Id3v2FrameIds::isShortId(m_frameId)) {
401  // try to convert the short frame ID to its long equivalent
402  if(!(m_frameId = Id3v2FrameIds::convertToLongId(m_frameId))) {
403  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);
404  throw InvalidDataException();
405  }
406  }
407  } else {
408  if(Id3v2FrameIds::isLongId(m_frameId)) {
409  // try to convert the long frame ID to its short equivalent
410  if(!(m_frameId = Id3v2FrameIds::convertToShortId(m_frameId))) {
411  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);
412  throw InvalidDataException();
413  }
414  }
415  }
416 
417  // make actual data depending on the frame ID
418  try {
419  if(Id3v2FrameIds::isTextFrame(m_frameId)) {
420  // it is a text frame
421  if((version >= 3 && (m_frameId == Id3v2FrameIds::lTrackPosition || m_frameId == Id3v2FrameIds::lDiskPosition))
422  || (version < 3 && m_frameId == Id3v2FrameIds::sTrackPosition)) {
423  // track number or the disk number frame
424  m_frame.makeString(m_data, m_decompressedSize, m_frame.value().toString(), TagTextEncoding::Latin1);
425  } else if((version >= 3 && m_frameId == Id3v2FrameIds::lLength)
426  || (version < 3 && m_frameId == Id3v2FrameIds::sLength)) {
427  // length frame
428  m_frame.makeString(m_data, m_decompressedSize, ConversionUtilities::numberToString(m_frame.value().toTimeSpan().totalMilliseconds()), TagTextEncoding::Latin1);
429  } else if(m_frame.value().type() == TagDataType::StandardGenreIndex && ((version >= 3 && m_frameId == Id3v2FrameIds::lGenre)
430  || (version < 3 && m_frameId == Id3v2FrameIds::sGenre))) {
431  // pre-defined genre frame
432  m_frame.makeString(m_data, m_decompressedSize, ConversionUtilities::numberToString(m_frame.value().toStandardGenreIndex()), TagTextEncoding::Latin1);
433  } else {
434  // any other text frame
435  if(version <= 3 && m_frame.value().dataEncoding() == TagTextEncoding::Utf8) {
436  // UTF-8 is only supported by ID3v2.4, so convert back to UTF-16
437  m_frame.makeString(m_data, m_decompressedSize, m_frame.value().toString(TagTextEncoding::Utf16LittleEndian), TagTextEncoding::Utf16LittleEndian);
438  } else {
439  // just keep encoding of the assigned value
440  m_frame.makeString(m_data, m_decompressedSize, m_frame.value().toString(), m_frame.value().dataEncoding());
441  }
442  }
443 
444  } else if(version >= 3 && m_frameId == Id3v2FrameIds::lCover) {
445  // picture frame
446  m_frame.makePicture(m_data, m_decompressedSize, m_frame.value(), m_frame.isTypeInfoAssigned() ? m_frame.typeInfo() : 0);
447 
448  } else if(version < 3 && m_frameId == Id3v2FrameIds::sCover) {
449  // legacy picture frame
450  m_frame.makeLegacyPicture(m_data, m_decompressedSize, m_frame.value(), m_frame.isTypeInfoAssigned() ? m_frame.typeInfo() : 0);
451 
452  } else if(((version >= 3 && m_frameId == Id3v2FrameIds::lComment)
453  || (version < 3 && m_frameId == Id3v2FrameIds::sComment))
454  || ((version >= 3 && m_frameId == Id3v2FrameIds::lUnsynchronizedLyrics)
455  || (version < 3 && m_frameId == Id3v2FrameIds::sUnsynchronizedLyrics))) {
456  // the comment frame or the unsynchronized lyrics frame
457  m_frame.makeCommentConsideringVersion(m_data, m_decompressedSize, m_frame.value(), version);
458 
459  } else {
460  // an unknown frame
461  m_data = make_unique<char[]>(m_decompressedSize = m_frame.value().dataSize());
462  copy(m_frame.value().dataPointer(), m_frame.value().dataPointer() + m_decompressedSize, m_data.get());
463  }
464  } catch(const ConversionException &) {
465  m_frame.addNotification(NotificationType::Critical, "Assigned value can not be converted appropriately.", context);
466  throw InvalidDataException();
467  }
468 
469  // apply compression if frame should be compressed
470  if(version >= 3 && m_frame.isCompressed()) {
471  m_dataSize = compressBound(m_decompressedSize);
472  auto compressedData = make_unique<char[]>(m_decompressedSize);
473  switch(compress(reinterpret_cast<Bytef *>(compressedData.get()), reinterpret_cast<uLongf *>(&m_dataSize), reinterpret_cast<Bytef *>(m_data.get()), m_decompressedSize)) {
474  case Z_MEM_ERROR:
475  m_frame.addNotification(NotificationType::Critical, "Decompressing failed. The source buffer was too small.", context);
476  throw InvalidDataException();
477  case Z_BUF_ERROR:
478  m_frame.addNotification(NotificationType::Critical, "Decompressing failed. The destination buffer was too small.", context);
479  throw InvalidDataException();
480  case Z_OK:
481  ;
482  }
483  m_data.swap(compressedData);
484  } else {
485  m_dataSize = m_decompressedSize;
486  }
487 
488  // calculate required size
489  // -> data size
490  m_requiredSize = m_dataSize;
491  if(version < 3) {
492  // -> header size
493  m_requiredSize += 6;
494  } else {
495  // -> header size
496  m_requiredSize += 10;
497  // -> group byte
498  if(m_frame.hasGroupInformation()) {
499  m_requiredSize += 1;
500  }
501  // -> decompressed size
502  if(version >= 3 && m_frame.isCompressed()) {
503  m_requiredSize += 4;
504  }
505  }
506 }
507 
515 void Id3v2FrameMaker::make(BinaryWriter &writer)
516 {
517  if(m_version < 3) {
518  writer.writeUInt24BE(m_frameId);
519  writer.writeUInt24BE(m_dataSize);
520  } else {
521  writer.writeUInt32BE(m_frameId);
522  if(m_version >= 4) {
523  writer.writeSynchsafeUInt32BE(m_dataSize);
524  } else {
525  writer.writeUInt32BE(m_dataSize);
526  }
527  writer.writeUInt16BE(m_frame.flag());
528  if(m_frame.hasGroupInformation()) {
529  writer.writeByte(m_frame.group());
530  }
531  if(m_version >= 3 && m_frame.isCompressed()) {
532  if(m_version >= 4) {
533  writer.writeSynchsafeUInt32BE(m_decompressedSize);
534  } else {
535  writer.writeUInt32BE(m_decompressedSize);
536  }
537  }
538  }
539  writer.write(m_data.get(), m_dataSize);
540 }
541 
549 {
550  switch(textEncodingByte) {
558  return TagTextEncoding::Utf8;
559  default:
560  addNotification(NotificationType::Warning, "The charset of the frame is invalid. Latin-1 will be used.", "parsing encoding of frame " + frameIdString());
562  }
563 }
564 
569 {
570  switch(textEncoding) {
579  default:
580  return 0;
581  }
582 }
583 
598 tuple<const char *, size_t, const char *> Id3v2Frame::parseSubstring(const char *buffer, std::size_t bufferSize, TagTextEncoding &encoding, bool addWarnings)
599 {
600  tuple<const char *, size_t, const char *> res(buffer, 0, buffer + bufferSize);
601  switch(encoding) {
604  case TagTextEncoding::Utf8: {
605  if((bufferSize >= 3) && (ConversionUtilities::BE::toUInt24(buffer) == 0x00EFBBBF)) {
606  if(encoding == TagTextEncoding::Latin1) {
607  addNotification(NotificationType::Critical, "Denoted character set is Latin-1 but an UTF-8 BOM is present - assuming UTF-8.", "parsing frame " + frameIdString());
608  encoding = TagTextEncoding::Utf8;
609  }
610  get<0>(res) += 3;
611  }
612  const char *pos = get<0>(res);
613  for(; *pos != 0x00; ++pos) {
614  if(pos < get<2>(res)) {
615  ++get<1>(res);
616  } else {
617  if(addWarnings) {
618  addNotification(NotificationType::Warning, "String in frame is not terminated proberly.", "parsing termination of frame " + frameIdString());
619  }
620  break;
621  }
622  }
623  get<2>(res) = pos + 1;
624  break;
625  }
628  if(bufferSize >= 2) {
629  switch(ConversionUtilities::LE::toUInt16(buffer)) {
630  case 0xFEFF:
631  if(encoding == TagTextEncoding::Utf16BigEndian) {
632  addNotification(NotificationType::Critical, "Denoted character set is UTF-16 Big Endian but UTF-16 Little Endian BOM is present - assuming UTF-16 LE.", "parsing frame " + frameIdString());
634  }
635  get<0>(res) += 2;
636  break;
637  case 0xFFFE:
639  get<0>(res) += 2;
640  }
641  }
642  const uint16 *pos = reinterpret_cast<const uint16 *>(get<0>(res));
643  for(; *pos != 0x0000; ++pos) {
644  if(pos < reinterpret_cast<const uint16 *>(get<2>(res))) {
645  get<1>(res) += 2;
646  } else {
647  if(addWarnings) {
648  addNotification(NotificationType::Warning, "Wide string in frame is not terminated proberly.", "parsing termination of frame " + frameIdString());
649  }
650  break;
651  }
652  }
653  get<2>(res) = reinterpret_cast<const char *>(pos + 1);
654  break;
655  }
656  }
657  return res;
658 }
659 
665 string Id3v2Frame::parseString(const char *buffer, size_t dataSize, TagTextEncoding &encoding, bool addWarnings)
666 {
667  auto substr = parseSubstring(buffer, dataSize, encoding, addWarnings);
668  return string(get<0>(substr), get<1>(substr));
669 }
670 
678 u16string Id3v2Frame::parseWideString(const char *buffer, size_t dataSize, TagTextEncoding &encoding, bool addWarnings)
679 {
680  auto substr = parseSubstring(buffer, dataSize, encoding, addWarnings);
681  u16string res(reinterpret_cast<u16string::const_pointer>(get<0>(substr)), get<1>(substr) / 2);
682  if(encoding !=
683  #if defined(CONVERSION_UTILITIES_BYTE_ORDER_LITTLE_ENDIAN)
685  #elif defined(CONVERSION_UTILITIES_BYTE_ORDER_BIG_ENDIAN)
687  #else
688  # error "Host byte order not supported"
689  #endif
690  ) {
691  // ensure byte order matches host byte order
692  for(auto &c : res) {
693  c = ((c >> 8) & 0x00FF) | ((c << 8) & 0xFF00);
694  }
695  }
696  return res;
697 }
698 
708 void Id3v2Frame::parseBom(const char *buffer, size_t maxSize, TagTextEncoding &encoding)
709 {
710  switch(encoding) {
713  if((maxSize >= 2) && (ConversionUtilities::BE::toUInt16(buffer) == 0xFFFE)) {
715  } else if((maxSize >= 2) && (ConversionUtilities::BE::toUInt16(buffer) == 0xFEFF)) {
717  }
718  break;
719  default:
720  if((maxSize >= 3) && (ConversionUtilities::BE::toUInt24(buffer) == 0x00EFBBBF)) {
721  encoding = TagTextEncoding::Utf8;
722  addNotification(NotificationType::Warning, "UTF-8 byte order mark found in text frame.", "parsing byte oder mark of frame " + frameIdString());
723  }
724  }
725 }
726 
734 void Id3v2Frame::parseLegacyPicture(const char *buffer, std::size_t maxSize, TagValue &tagValue, byte &typeInfo)
735 {
736  static const string context("parsing ID3v2.2 picture frame");
737  if(maxSize < 6) {
738  addNotification(NotificationType::Critical, "Picture frame is incomplete.", context);
739  throw TruncatedDataException();
740  }
741  const char *end = buffer + maxSize;
742  auto dataEncoding = parseTextEncodingByte(*buffer); // the first byte stores the encoding
743  typeInfo = static_cast<unsigned char>(*(buffer + 4));
744  auto substr = parseSubstring(buffer + 5, end - 5 - buffer, dataEncoding, true);
745  tagValue.setDescription(string(get<0>(substr), get<1>(substr)), dataEncoding);
746  if(get<2>(substr) >= end) {
747  addNotification(NotificationType::Critical, "Picture frame is incomplete (actual data is missing).", context);
748  throw TruncatedDataException();
749  }
750  tagValue.assignData(get<2>(substr), end - get<2>(substr), TagDataType::Picture, dataEncoding);
751 }
752 
760 void Id3v2Frame::parsePicture(const char *buffer, std::size_t maxSize, TagValue &tagValue, byte &typeInfo)
761 {
762  static const string context("parsing ID3v2.3 picture frame");
763  const char *end = buffer + maxSize;
764  auto dataEncoding = parseTextEncodingByte(*buffer); // the first byte stores the encoding
765  auto mimeTypeEncoding = TagTextEncoding::Latin1;
766  auto substr = parseSubstring(buffer + 1, maxSize - 1, mimeTypeEncoding, true);
767  if(get<1>(substr)) {
768  tagValue.setMimeType(string(get<0>(substr), get<1>(substr)));
769  }
770  if(get<2>(substr) >= end) {
771  addNotification(NotificationType::Critical, "Picture frame is incomplete (type info, description and actual data are missing).", context);
772  throw TruncatedDataException();
773  }
774  typeInfo = static_cast<unsigned char>(*get<2>(substr));
775  if(++get<2>(substr) >= end) {
776  addNotification(NotificationType::Critical, "Picture frame is incomplete (description and actual data are missing).", context);
777  throw TruncatedDataException();
778  }
779  substr = parseSubstring(get<2>(substr), end - get<2>(substr), dataEncoding, true);
780  tagValue.setDescription(string(get<0>(substr), get<1>(substr)), dataEncoding);
781  if(get<2>(substr) >= end) {
782  addNotification(NotificationType::Critical, "Picture frame is incomplete (actual data is missing).", context);
783  throw TruncatedDataException();
784  }
785  tagValue.assignData(get<2>(substr), end - get<2>(substr), TagDataType::Picture, dataEncoding);
786 }
787 
794 void Id3v2Frame::parseComment(const char *buffer, std::size_t dataSize, TagValue &tagValue)
795 {
796  static const string context("parsing comment/unsynchronized lyrics frame");
797  const char *end = buffer + dataSize;
798  if(dataSize < 5) {
799  addNotification(NotificationType::Critical, "Comment frame is incomplete.", context);
800  throw TruncatedDataException();
801  }
802  TagTextEncoding dataEncoding = parseTextEncodingByte(*buffer);
803  if(*(++buffer)) {
804  tagValue.setLanguage(string(buffer, 3));
805  }
806  auto substr = parseSubstring(buffer += 3, dataSize -= 4, dataEncoding, true);
807  tagValue.setDescription(string(get<0>(substr), get<1>(substr)), dataEncoding);
808  if(get<2>(substr) > end) {
809  addNotification(NotificationType::Critical, "Comment frame is incomplete (description not terminated?).", context);
810  throw TruncatedDataException();
811  }
812  substr = parseSubstring(get<2>(substr), end - get<2>(substr), dataEncoding, false);
813  tagValue.assignData(get<0>(substr), get<1>(substr), TagDataType::Text, dataEncoding);
814 }
815 
823 void Id3v2Frame::makeString(std::unique_ptr<char[]> &buffer, uint32 &bufferSize, const std::string &value, TagTextEncoding encoding)
824 {
825  makeEncodingAndData(buffer, bufferSize, encoding, value.data(), value.size());
826 }
827 
836 void Id3v2Frame::makeEncodingAndData(std::unique_ptr<char[]> &buffer, uint32 &bufferSize, TagTextEncoding encoding, const char *data, std::size_t dataSize)
837 {
838  // calculate buffer size
839  if(!data) {
840  dataSize = 0;
841  }
842  char *bufferDataAddress;
843  switch(encoding) {
846  case TagTextEncoding::Unspecified: // assumption
847  // allocate buffer
848  buffer = make_unique<char[]>(bufferSize = 1 + dataSize + 1);
849  buffer[0] = makeTextEncodingByte(encoding); // set text encoding byte
850  bufferDataAddress = buffer.get() + 1;
851  break;
854  // allocate buffer
855  buffer = make_unique<char[]>(bufferSize = 1 + 2 + dataSize + 2);
856  buffer[0] = makeTextEncodingByte(encoding); // set text encoding byte
857  ConversionUtilities::LE::getBytes(encoding == TagTextEncoding::Utf16LittleEndian ? static_cast<uint16>(0xFEFF) : static_cast<uint16>(0xFFFE), buffer.get() + 1);
858  bufferDataAddress = buffer.get() + 3;
859  break;
860  }
861  if(dataSize) {
862  copy(data, data + dataSize, bufferDataAddress); // write string data
863  }
864 }
865 
870 size_t Id3v2Frame::makeBom(char *buffer, TagTextEncoding encoding)
871 {
872  switch(encoding) {
874  ConversionUtilities::LE::getBytes(static_cast<uint16>(0xFEFF), buffer);
875  return 2;
877  ConversionUtilities::BE::getBytes(static_cast<uint16>(0xFEFF), buffer);
878  return 2;
879  default:
880  return 0;
881  }
882 }
883 
887 void Id3v2Frame::makeLegacyPicture(unique_ptr<char[]> &buffer, uint32 &bufferSize, const TagValue &picture, byte typeInfo)
888 {
889  // determine description
890  TagTextEncoding descriptionEncoding = picture.descriptionEncoding();
891  StringData convertedDescription;
892  string::size_type descriptionSize = picture.description().find("\0\0", 0, descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? 2 : 1);
893  if(descriptionEncoding == TagTextEncoding::Utf8) {
894  // UTF-8 is only supported by ID3v2.4, so convert back to UTF-16
895  descriptionEncoding = TagTextEncoding::Utf16LittleEndian;
896  convertedDescription = convertUtf8ToUtf16LE(picture.description().data(), picture.description().size());
897  descriptionSize = convertedDescription.second;
898  } else {
899  descriptionSize = picture.description().find(descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? "\0\0" : "\0");
900  if(descriptionSize == string::npos) {
901  descriptionSize = picture.description().size();
902  }
903  }
904  // calculate needed buffer size and create buffer
905  const uint32 dataSize = picture.dataSize();
906  buffer = make_unique<char[]>(bufferSize = 1 + 3 + 1 + descriptionSize + (descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? 4 : 1) + dataSize);
907  // note: encoding byte + image format + picture type byte + description size + 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  const char *imageFormat;
913  if(picture.mimeType() == "image/jpeg") {
914  imageFormat = "JPG";
915  } else if(picture.mimeType() == "image/png") {
916  imageFormat = "PNG";
917  } else if(picture.mimeType() == "image/gif") {
918  imageFormat = "GIF";
919  } else if(picture.mimeType() == "-->") {
920  imageFormat = picture.mimeType().data();
921  } else {
922  imageFormat = "UND";
923  }
924  strncpy(++offset, imageFormat, 3);
925  // write picture type
926  *(offset += 3) = typeInfo;
927  // write description
928  offset += makeBom(offset + 1, descriptionEncoding);
929  if(convertedDescription.first) {
930  copy(convertedDescription.first.get(), convertedDescription.first.get() + descriptionSize, ++offset);
931  } else {
932  picture.description().copy(++offset, descriptionSize);
933  }
934  *(offset += descriptionSize) = 0x00; // terminate description and increase data offset
935  if(descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian) {
936  *(++offset) = 0x00;
937  }
938  // write actual data
939  copy(picture.dataPointer(), picture.dataPointer() + picture.dataSize(), ++offset);
940 }
941 
945 void Id3v2Frame::makePicture(unique_ptr<char[]> &buffer, uint32 &bufferSize, const TagValue &picture, byte typeInfo)
946 {
947  // determine description
948  TagTextEncoding descriptionEncoding = picture.descriptionEncoding();
949  StringData convertedDescription;
950  string::size_type descriptionSize = picture.description().find("\0\0", 0, descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? 2 : 1);
951  if(descriptionEncoding == TagTextEncoding::Utf8) {
952  // UTF-8 is only supported by ID3v2.4, so convert back to UTF-16
953  descriptionEncoding = TagTextEncoding::Utf16LittleEndian;
954  convertedDescription = convertUtf8ToUtf16LE(picture.description().data(), picture.description().size());
955  descriptionSize = convertedDescription.second;
956  } else {
957  descriptionSize = picture.description().find(descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? "\0\0" : "\0");
958  if(descriptionSize == string::npos) {
959  descriptionSize = picture.description().size();
960  }
961  }
962  // determine mime-type
963  string::size_type mimeTypeSize = picture.mimeType().find('\0');
964  if(mimeTypeSize == string::npos) {
965  mimeTypeSize = picture.mimeType().length();
966  }
967  // calculate needed buffer size and create buffer
968  const uint32 dataSize = picture.dataSize();
969  buffer = make_unique<char[]>(bufferSize = 1 + mimeTypeSize + 1 + 1 + descriptionSize + (descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? 4 : 1) + dataSize);
970  // note: encoding byte + mime type size + 0 byte + picture type byte + description size + 1 or 4 null bytes (depends on encoding) + data size
971  char *offset = buffer.get();
972  // write encoding byte
973  *offset = makeTextEncodingByte(descriptionEncoding);
974  // write mime type
975  picture.mimeType().copy(++offset, mimeTypeSize);
976  *(offset += mimeTypeSize) = 0x00; // terminate mime type
977  // write picture type
978  *(++offset) = typeInfo;
979  // write description
980  offset += makeBom(offset + 1, descriptionEncoding);
981  if(convertedDescription.first) {
982  copy(convertedDescription.first.get(), convertedDescription.first.get() + descriptionSize, ++offset);
983  } else {
984  picture.description().copy(++offset, descriptionSize);
985  }
986  *(offset += descriptionSize) = 0x00; // terminate description and increase data offset
987  if(descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian) {
988  *(++offset) = 0x00;
989  }
990  // write actual data
991  copy(picture.dataPointer(), picture.dataPointer() + picture.dataSize(), ++offset);
992 }
993 
997 void Id3v2Frame::makeComment(unique_ptr<char[]> &buffer, uint32 &bufferSize, const TagValue &comment)
998 {
999  makeCommentConsideringVersion(buffer, bufferSize, comment, 3);
1000 }
1001 
1005 void Id3v2Frame::makeCommentConsideringVersion(unique_ptr<char[]> &buffer, uint32 &bufferSize, const TagValue &comment, byte version)
1006 {
1007  static const string context("making comment frame");
1008  // check type and other values are valid
1009  TagTextEncoding encoding = comment.dataEncoding();
1010  if(!comment.description().empty() && encoding != comment.descriptionEncoding()) {
1011  addNotification(NotificationType::Critical, "Data enoding and description encoding aren't equal.", context);
1012  throw InvalidDataException();
1013  }
1014  const string &lng = comment.language();
1015  if(lng.length() > 3) {
1016  addNotification(NotificationType::Critical, "The language must be 3 bytes long (ISO-639-2).", context);
1017  throw InvalidDataException();
1018  }
1019  StringData convertedDescription;
1020  string::size_type descriptionSize;
1021  if(version < 4 && encoding == TagTextEncoding::Utf8) {
1022  // UTF-8 is only supported by ID3v2.4, so convert back to UTF-16
1024  convertedDescription = convertUtf8ToUtf16LE(comment.description().data(), comment.description().size());
1025  descriptionSize = convertedDescription.second;
1026  } else {
1027  descriptionSize = comment.description().find("\0\0", 0, encoding == TagTextEncoding::Utf16BigEndian || encoding == TagTextEncoding::Utf16LittleEndian ? 2 : 1);
1028  if(descriptionSize == string::npos) {
1029  descriptionSize = comment.description().size();
1030  }
1031  }
1032  // calculate needed buffer size and create buffer
1033  const auto data = comment.toString(encoding);
1034  buffer = make_unique<char[]>(bufferSize = 1 + 3 + descriptionSize + data.size() + (encoding == TagTextEncoding::Utf16BigEndian || encoding == TagTextEncoding::Utf16LittleEndian ? 6 : 1) + data.size());
1035  // note: encoding byte + language + description size + actual data size + BOMs and termination
1036  char *offset = buffer.get();
1037  // write encoding
1038  *offset = makeTextEncodingByte(encoding);
1039  // write language
1040  for(unsigned int i = 0; i < 3; ++i) {
1041  *(++offset) = (lng.length() > i) ? lng[i] : 0x00;
1042  }
1043  // write description
1044  offset += makeBom(offset + 1, encoding);
1045  if(convertedDescription.first) {
1046  copy(convertedDescription.first.get(), convertedDescription.first.get() + descriptionSize, ++offset);
1047  } else {
1048  comment.description().copy(++offset, descriptionSize);
1049  }
1050  offset += descriptionSize;
1051  *offset = 0x00; // terminate description and increase data offset
1053  *(++offset) = 0x00;
1054  }
1055  // write actual data
1056  offset += makeBom(offset + 1, encoding);
1057  data.copy(++offset, data.size());
1058 }
1059 
1060 }
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:396
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:349
TagTextEncoding dataEncoding() const
Returns the data encoding.
Definition: tagvalue.h:475
Id3v2FrameMaker prepareMaking(const uint32 version)
Prepares making.
Definition: id3v2frame.cpp:328
bool isLongId(uint32 id)
Returns an indication whether the specified id is a long frame id.
Definition: id3v2frameids.h:68
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:823
bool isShortId(uint32 id)
Returns an indication whether the specified id is a short frame id.
Definition: id3v2frameids.h:76
void make(IoUtilities::BinaryWriter &writer)
Saves the frame (specified when constructing the object) using the specified writer.
Definition: id3v2frame.cpp:515
char * dataPointer() const
Returns a pointer to the raw data assigned to the current instance.
Definition: tagvalue.h:372
int parseGenreIndex(const stringtype &denotation, bool isBigEndian=false)
Helper function to parse the genre index.
Definition: id3v2frame.cpp:68
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:598
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:119
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:665
STL namespace.
void assignStandardGenreIndex(int index)
Assigns the given standard genre index to be assigned.
Definition: tagvalue.cpp:615
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:734
byte group() const
Returns the group.
Definition: id3v2frame.h:307
TagTextEncoding
Specifies the text encoding.
Definition: tagvalue.h:21
bool isCompressed() const
Returns whether the frame is compressed.
Definition: id3v2frame.h:265
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:548
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:836
void assignTimeSpan(ChronoUtilities::TimeSpan value)
Assigns the given TimeSpan value.
Definition: tagvalue.h:288
bool hasGroupInformation() const
Returns whether the frame contains group information.
Definition: id3v2frame.h:282
std::string frameIdString() const
Returns the frame ID as string.
Definition: id3v2frame.h:201
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:428
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:568
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:341
TagTextEncoding descriptionEncoding() const
Returns the description encoding.
Definition: tagvalue.h:485
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:760
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:275
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:629
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:945
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:678
bool isTextFrame(uint32 id)
Returns an indication whether the specified id is a text frame id.
Definition: id3v2frameids.h:84
uint32 dataSize() const
Returns the size of the data stored in the frame in bytes.
Definition: id3v2frame.h:233
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:887
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:274
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:41
void parseBom(const char *buffer, std::size_t maxSize, TagTextEncoding &encoding)
Parses a byte order mark from the specified buffer.
Definition: id3v2frame.cpp:708
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:439
friend class Id3v2FrameMaker
Definition: id3v2frame.h:103
void parseComment(const char *buffer, std::size_t maxSize, TagValue &tagValue)
Parses the comment/unsynchronized lyrics from the specified buffer.
Definition: id3v2frame.cpp:794
void setMimeType(const std::string &value)
Sets the MIME type.
Definition: tagvalue.h:418
uint16 flag() const
Returns the flags.
Definition: id3v2frame.h:209
const std::string & mimeType() const
Returns the MIME type.
Definition: tagvalue.h:407
void makeComment(std::unique_ptr< char[]> &buffer, uint32 &bufferSize, const TagValue &comment)
Writes the specified comment to the specified buffer.
Definition: id3v2frame.cpp:997
const std::string & description() const
Returns the description.
Definition: tagvalue.h:383
size_t dataSize() const
Returns the size of the assigned value in bytes.
Definition: tagvalue.h:361
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:316
void setId(const identifierType &id)
Sets the id of the current Tag Field.
TAG_PARSER_EXPORT const char * version()