Tag Parser  6.3.0
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) || (version < 3 && m_frameId == Id3v2FrameIds::sCover)) {
445  // picture frame
446  m_frame.makePictureConsideringVersion(m_data, m_decompressedSize, m_frame.value(), m_frame.isTypeInfoAssigned() ? m_frame.typeInfo() : 0, version);
447 
448  } else if(((version >= 3 && m_frameId == Id3v2FrameIds::lComment)
449  || (version < 3 && m_frameId == Id3v2FrameIds::sComment))
450  || ((version >= 3 && m_frameId == Id3v2FrameIds::lUnsynchronizedLyrics)
451  || (version < 3 && m_frameId == Id3v2FrameIds::sUnsynchronizedLyrics))) {
452  // the comment frame or the unsynchronized lyrics frame
453  m_frame.makeCommentConsideringVersion(m_data, m_decompressedSize, m_frame.value(), version);
454 
455  } else {
456  // an unknown frame
457  m_data = make_unique<char[]>(m_decompressedSize = m_frame.value().dataSize());
458  copy(m_frame.value().dataPointer(), m_frame.value().dataPointer() + m_decompressedSize, m_data.get());
459  }
460  } catch(const ConversionException &) {
461  m_frame.addNotification(NotificationType::Critical, "Assigned value can not be converted appropriately.", context);
462  throw InvalidDataException();
463  }
464 
465  // apply compression if frame should be compressed
466  if(version >= 3 && m_frame.isCompressed()) {
467  m_dataSize = compressBound(m_decompressedSize);
468  auto compressedData = make_unique<char[]>(m_decompressedSize);
469  switch(compress(reinterpret_cast<Bytef *>(compressedData.get()), reinterpret_cast<uLongf *>(&m_dataSize), reinterpret_cast<Bytef *>(m_data.get()), m_decompressedSize)) {
470  case Z_MEM_ERROR:
471  m_frame.addNotification(NotificationType::Critical, "Decompressing failed. The source buffer was too small.", context);
472  throw InvalidDataException();
473  case Z_BUF_ERROR:
474  m_frame.addNotification(NotificationType::Critical, "Decompressing failed. The destination buffer was too small.", context);
475  throw InvalidDataException();
476  case Z_OK:
477  ;
478  }
479  m_data.swap(compressedData);
480  } else {
481  m_dataSize = m_decompressedSize;
482  }
483 
484  // calculate required size
485  // -> data size
486  m_requiredSize = m_dataSize;
487  if(version < 3) {
488  // -> header size
489  m_requiredSize += 6;
490  } else {
491  // -> header size
492  m_requiredSize += 10;
493  // -> group byte
494  if(m_frame.hasGroupInformation()) {
495  m_requiredSize += 1;
496  }
497  // -> decompressed size
498  if(version >= 3 && m_frame.isCompressed()) {
499  m_requiredSize += 4;
500  }
501  }
502 }
503 
511 void Id3v2FrameMaker::make(BinaryWriter &writer)
512 {
513  if(m_version < 3) {
514  writer.writeUInt24BE(m_frameId);
515  writer.writeUInt24BE(m_dataSize);
516  } else {
517  writer.writeUInt32BE(m_frameId);
518  if(m_version >= 4) {
519  writer.writeSynchsafeUInt32BE(m_dataSize);
520  } else {
521  writer.writeUInt32BE(m_dataSize);
522  }
523  writer.writeUInt16BE(m_frame.flag());
524  if(m_frame.hasGroupInformation()) {
525  writer.writeByte(m_frame.group());
526  }
527  if(m_version >= 3 && m_frame.isCompressed()) {
528  if(m_version >= 4) {
529  writer.writeSynchsafeUInt32BE(m_decompressedSize);
530  } else {
531  writer.writeUInt32BE(m_decompressedSize);
532  }
533  }
534  }
535  writer.write(m_data.get(), m_dataSize);
536 }
537 
545 {
546  switch(textEncodingByte) {
554  return TagTextEncoding::Utf8;
555  default:
556  addNotification(NotificationType::Warning, "The charset of the frame is invalid. Latin-1 will be used.", "parsing encoding of frame " + frameIdString());
558  }
559 }
560 
565 {
566  switch(textEncoding) {
575  default:
576  return 0;
577  }
578 }
579 
594 tuple<const char *, size_t, const char *> Id3v2Frame::parseSubstring(const char *buffer, std::size_t bufferSize, TagTextEncoding &encoding, bool addWarnings)
595 {
596  tuple<const char *, size_t, const char *> res(buffer, 0, buffer + bufferSize);
597  switch(encoding) {
600  case TagTextEncoding::Utf8: {
601  if((bufferSize >= 3) && (ConversionUtilities::BE::toUInt24(buffer) == 0x00EFBBBF)) {
602  if(encoding == TagTextEncoding::Latin1) {
603  addNotification(NotificationType::Critical, "Denoted character set is Latin-1 but an UTF-8 BOM is present - assuming UTF-8.", "parsing frame " + frameIdString());
604  encoding = TagTextEncoding::Utf8;
605  }
606  get<0>(res) += 3;
607  }
608  const char *pos = get<0>(res);
609  for(; *pos != 0x00; ++pos) {
610  if(pos < get<2>(res)) {
611  ++get<1>(res);
612  } else {
613  if(addWarnings) {
614  addNotification(NotificationType::Warning, "String in frame is not terminated proberly.", "parsing termination of frame " + frameIdString());
615  }
616  break;
617  }
618  }
619  get<2>(res) = pos + 1;
620  break;
621  }
624  if(bufferSize >= 2) {
625  switch(ConversionUtilities::LE::toUInt16(buffer)) {
626  case 0xFEFF:
627  if(encoding == TagTextEncoding::Utf16BigEndian) {
628  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());
630  }
631  get<0>(res) += 2;
632  break;
633  case 0xFFFE:
635  get<0>(res) += 2;
636  }
637  }
638  const uint16 *pos = reinterpret_cast<const uint16 *>(get<0>(res));
639  for(; *pos != 0x0000; ++pos) {
640  if(pos < reinterpret_cast<const uint16 *>(get<2>(res))) {
641  get<1>(res) += 2;
642  } else {
643  if(addWarnings) {
644  addNotification(NotificationType::Warning, "Wide string in frame is not terminated proberly.", "parsing termination of frame " + frameIdString());
645  }
646  break;
647  }
648  }
649  get<2>(res) = reinterpret_cast<const char *>(pos + 1);
650  break;
651  }
652  }
653  return res;
654 }
655 
661 string Id3v2Frame::parseString(const char *buffer, size_t dataSize, TagTextEncoding &encoding, bool addWarnings)
662 {
663  auto substr = parseSubstring(buffer, dataSize, encoding, addWarnings);
664  return string(get<0>(substr), get<1>(substr));
665 }
666 
674 u16string Id3v2Frame::parseWideString(const char *buffer, size_t dataSize, TagTextEncoding &encoding, bool addWarnings)
675 {
676  auto substr = parseSubstring(buffer, dataSize, encoding, addWarnings);
677  u16string res(reinterpret_cast<u16string::const_pointer>(get<0>(substr)), get<1>(substr) / 2);
678  if(encoding !=
679  #if defined(CONVERSION_UTILITIES_BYTE_ORDER_LITTLE_ENDIAN)
681  #elif defined(CONVERSION_UTILITIES_BYTE_ORDER_BIG_ENDIAN)
683  #else
684  # error "Host byte order not supported"
685  #endif
686  ) {
687  // ensure byte order matches host byte order
688  for(auto &c : res) {
689  c = ((c >> 8) & 0x00FF) | ((c << 8) & 0xFF00);
690  }
691  }
692  return res;
693 }
694 
704 void Id3v2Frame::parseBom(const char *buffer, size_t maxSize, TagTextEncoding &encoding)
705 {
706  switch(encoding) {
709  if((maxSize >= 2) && (ConversionUtilities::BE::toUInt16(buffer) == 0xFFFE)) {
711  } else if((maxSize >= 2) && (ConversionUtilities::BE::toUInt16(buffer) == 0xFEFF)) {
713  }
714  break;
715  default:
716  if((maxSize >= 3) && (ConversionUtilities::BE::toUInt24(buffer) == 0x00EFBBBF)) {
717  encoding = TagTextEncoding::Utf8;
718  addNotification(NotificationType::Warning, "UTF-8 byte order mark found in text frame.", "parsing byte oder mark of frame " + frameIdString());
719  }
720  }
721 }
722 
730 void Id3v2Frame::parseLegacyPicture(const char *buffer, std::size_t maxSize, TagValue &tagValue, byte &typeInfo)
731 {
732  static const string context("parsing ID3v2.2 picture frame");
733  if(maxSize < 6) {
734  addNotification(NotificationType::Critical, "Picture frame is incomplete.", context);
735  throw TruncatedDataException();
736  }
737  const char *end = buffer + maxSize;
738  auto dataEncoding = parseTextEncodingByte(*buffer); // the first byte stores the encoding
739  typeInfo = static_cast<unsigned char>(*(buffer + 4));
740  auto substr = parseSubstring(buffer + 5, end - 5 - buffer, dataEncoding, true);
741  tagValue.setDescription(string(get<0>(substr), get<1>(substr)), dataEncoding);
742  if(get<2>(substr) >= end) {
743  addNotification(NotificationType::Critical, "Picture frame is incomplete (actual data is missing).", context);
744  throw TruncatedDataException();
745  }
746  tagValue.assignData(get<2>(substr), end - get<2>(substr), TagDataType::Picture, dataEncoding);
747 }
748 
756 void Id3v2Frame::parsePicture(const char *buffer, std::size_t maxSize, TagValue &tagValue, byte &typeInfo)
757 {
758  static const string context("parsing ID3v2.3 picture frame");
759  const char *end = buffer + maxSize;
760  auto dataEncoding = parseTextEncodingByte(*buffer); // the first byte stores the encoding
761  auto mimeTypeEncoding = TagTextEncoding::Latin1;
762  auto substr = parseSubstring(buffer + 1, maxSize - 1, mimeTypeEncoding, true);
763  if(get<1>(substr)) {
764  tagValue.setMimeType(string(get<0>(substr), get<1>(substr)));
765  }
766  if(get<2>(substr) >= end) {
767  addNotification(NotificationType::Critical, "Picture frame is incomplete (type info, description and actual data are missing).", context);
768  throw TruncatedDataException();
769  }
770  typeInfo = static_cast<unsigned char>(*get<2>(substr));
771  if(++get<2>(substr) >= end) {
772  addNotification(NotificationType::Critical, "Picture frame is incomplete (description and actual data are missing).", context);
773  throw TruncatedDataException();
774  }
775  substr = parseSubstring(get<2>(substr), end - get<2>(substr), dataEncoding, true);
776  tagValue.setDescription(string(get<0>(substr), get<1>(substr)), dataEncoding);
777  if(get<2>(substr) >= end) {
778  addNotification(NotificationType::Critical, "Picture frame is incomplete (actual data is missing).", context);
779  throw TruncatedDataException();
780  }
781  tagValue.assignData(get<2>(substr), end - get<2>(substr), TagDataType::Picture, dataEncoding);
782 }
783 
790 void Id3v2Frame::parseComment(const char *buffer, std::size_t dataSize, TagValue &tagValue)
791 {
792  static const string context("parsing comment/unsynchronized lyrics frame");
793  const char *end = buffer + dataSize;
794  if(dataSize < 5) {
795  addNotification(NotificationType::Critical, "Comment frame is incomplete.", context);
796  throw TruncatedDataException();
797  }
798  TagTextEncoding dataEncoding = parseTextEncodingByte(*buffer);
799  if(*(++buffer)) {
800  tagValue.setLanguage(string(buffer, 3));
801  }
802  auto substr = parseSubstring(buffer += 3, dataSize -= 4, dataEncoding, true);
803  tagValue.setDescription(string(get<0>(substr), get<1>(substr)), dataEncoding);
804  if(get<2>(substr) > end) {
805  addNotification(NotificationType::Critical, "Comment frame is incomplete (description not terminated?).", context);
806  throw TruncatedDataException();
807  }
808  substr = parseSubstring(get<2>(substr), end - get<2>(substr), dataEncoding, false);
809  tagValue.assignData(get<0>(substr), get<1>(substr), TagDataType::Text, dataEncoding);
810 }
811 
819 void Id3v2Frame::makeString(std::unique_ptr<char[]> &buffer, uint32 &bufferSize, const std::string &value, TagTextEncoding encoding)
820 {
821  makeEncodingAndData(buffer, bufferSize, encoding, value.data(), value.size());
822 }
823 
832 void Id3v2Frame::makeEncodingAndData(std::unique_ptr<char[]> &buffer, uint32 &bufferSize, TagTextEncoding encoding, const char *data, std::size_t dataSize)
833 {
834  // calculate buffer size
835  if(!data) {
836  dataSize = 0;
837  }
838  char *bufferDataAddress;
839  switch(encoding) {
842  case TagTextEncoding::Unspecified: // assumption
843  // allocate buffer
844  buffer = make_unique<char[]>(bufferSize = 1 + dataSize + 1);
845  buffer[0] = makeTextEncodingByte(encoding); // set text encoding byte
846  bufferDataAddress = buffer.get() + 1;
847  break;
850  // allocate buffer
851  buffer = make_unique<char[]>(bufferSize = 1 + 2 + dataSize + 2);
852  buffer[0] = makeTextEncodingByte(encoding); // set text encoding byte
853  ConversionUtilities::LE::getBytes(encoding == TagTextEncoding::Utf16LittleEndian ? static_cast<uint16>(0xFEFF) : static_cast<uint16>(0xFFFE), buffer.get() + 1);
854  bufferDataAddress = buffer.get() + 3;
855  break;
856  }
857  if(dataSize) {
858  copy(data, data + dataSize, bufferDataAddress); // write string data
859  }
860 }
861 
866 size_t Id3v2Frame::makeBom(char *buffer, TagTextEncoding encoding)
867 {
868  switch(encoding) {
870  ConversionUtilities::LE::getBytes(static_cast<uint16>(0xFEFF), buffer);
871  return 2;
873  ConversionUtilities::BE::getBytes(static_cast<uint16>(0xFEFF), buffer);
874  return 2;
875  default:
876  return 0;
877  }
878 }
879 
883 void Id3v2Frame::makeLegacyPicture(unique_ptr<char[]> &buffer, uint32 &bufferSize, const TagValue &picture, byte typeInfo)
884 {
885  // determine description
886  TagTextEncoding descriptionEncoding = picture.descriptionEncoding();
887  StringData convertedDescription;
888  string::size_type descriptionSize = picture.description().find("\0\0", 0, descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? 2 : 1);
889  if(descriptionSize == string::npos) {
890  descriptionSize = picture.description().size();
891  }
892  if(descriptionEncoding == TagTextEncoding::Utf8) {
893  // UTF-8 is only supported by ID3v2.4, so convert back to UTF-16
894  descriptionEncoding = TagTextEncoding::Utf16LittleEndian;
895  convertedDescription = convertUtf8ToUtf16LE(picture.description().data(), descriptionSize);
896  descriptionSize = convertedDescription.second;
897  }
898  // calculate needed buffer size and create buffer
899  const uint32 dataSize = picture.dataSize();
900  buffer = make_unique<char[]>(bufferSize = 1 + 3 + 1 + descriptionSize + (descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? 4 : 1) + dataSize);
901  // note: encoding byte + image format + picture type byte + description size + 1 or 2 null bytes (depends on encoding) + data size
902  char *offset = buffer.get();
903  // write encoding byte
904  *offset = makeTextEncodingByte(descriptionEncoding);
905  // write mime type
906  const char *imageFormat;
907  if(picture.mimeType() == "image/jpeg") {
908  imageFormat = "JPG";
909  } else if(picture.mimeType() == "image/png") {
910  imageFormat = "PNG";
911  } else if(picture.mimeType() == "image/gif") {
912  imageFormat = "GIF";
913  } else if(picture.mimeType() == "-->") {
914  imageFormat = picture.mimeType().data();
915  } else {
916  imageFormat = "UND";
917  }
918  strncpy(++offset, imageFormat, 3);
919  // write picture type
920  *(offset += 3) = typeInfo;
921  // write description
922  offset += makeBom(offset + 1, descriptionEncoding);
923  if(convertedDescription.first) {
924  copy(convertedDescription.first.get(), convertedDescription.first.get() + descriptionSize, ++offset);
925  } else {
926  picture.description().copy(++offset, descriptionSize);
927  }
928  *(offset += descriptionSize) = 0x00; // terminate description and increase data offset
929  if(descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian) {
930  *(++offset) = 0x00;
931  }
932  // write actual data
933  copy(picture.dataPointer(), picture.dataPointer() + picture.dataSize(), ++offset);
934 }
935 
939 void Id3v2Frame::makePicture(unique_ptr<char[]> &buffer, uint32 &bufferSize, const TagValue &picture, byte typeInfo)
940 {
941  makePictureConsideringVersion(buffer, bufferSize, picture, typeInfo, 3);
942 }
943 
947 void Id3v2Frame::makePictureConsideringVersion(std::unique_ptr<char[]> &buffer, uint32 &bufferSize, const TagValue &picture, byte typeInfo, byte version)
948 {
949  if(version < 3) {
950  makeLegacyPicture(buffer, bufferSize, picture, typeInfo);
951  return;
952  }
953 
954  // determine description
955  TagTextEncoding descriptionEncoding = picture.descriptionEncoding();
956  StringData convertedDescription;
957  string::size_type descriptionSize = picture.description().find("\0\0", 0, descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? 2 : 1);
958  if(descriptionSize == string::npos) {
959  descriptionSize = picture.description().size();
960  }
961  if(version < 4 && descriptionEncoding == TagTextEncoding::Utf8) {
962  // UTF-8 is only supported by ID3v2.4, so convert back to UTF-16
963  descriptionEncoding = TagTextEncoding::Utf16LittleEndian;
964  convertedDescription = convertUtf8ToUtf16LE(picture.description().data(), descriptionSize);
965  descriptionSize = convertedDescription.second;
966  }
967  // determine mime-type
968  string::size_type mimeTypeSize = picture.mimeType().find('\0');
969  if(mimeTypeSize == string::npos) {
970  mimeTypeSize = picture.mimeType().length();
971  }
972  // calculate needed buffer size and create buffer
973  const uint32 dataSize = picture.dataSize();
974  buffer = make_unique<char[]>(bufferSize = 1 + mimeTypeSize + 1 + 1 + descriptionSize + (descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? 4 : 1) + dataSize);
975  // note: encoding byte + mime type size + 0 byte + picture type byte + description size + 1 or 4 null bytes (depends on encoding) + data size
976  char *offset = buffer.get();
977  // write encoding byte
978  *offset = makeTextEncodingByte(descriptionEncoding);
979  // write mime type
980  picture.mimeType().copy(++offset, mimeTypeSize);
981  *(offset += mimeTypeSize) = 0x00; // terminate mime type
982  // write picture type
983  *(++offset) = typeInfo;
984  // write description
985  offset += makeBom(offset + 1, descriptionEncoding);
986  if(convertedDescription.first) {
987  copy(convertedDescription.first.get(), convertedDescription.first.get() + descriptionSize, ++offset);
988  } else {
989  picture.description().copy(++offset, descriptionSize);
990  }
991  *(offset += descriptionSize) = 0x00; // terminate description and increase data offset
992  if(descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian) {
993  *(++offset) = 0x00;
994  }
995  // write actual data
996  copy(picture.dataPointer(), picture.dataPointer() + picture.dataSize(), ++offset);
997 }
998 
1002 void Id3v2Frame::makeComment(unique_ptr<char[]> &buffer, uint32 &bufferSize, const TagValue &comment)
1003 {
1004  makeCommentConsideringVersion(buffer, bufferSize, comment, 3);
1005 }
1006 
1010 void Id3v2Frame::makeCommentConsideringVersion(unique_ptr<char[]> &buffer, uint32 &bufferSize, const TagValue &comment, byte version)
1011 {
1012  static const string context("making comment frame");
1013  // check type and other values are valid
1014  TagTextEncoding encoding = comment.dataEncoding();
1015  if(!comment.description().empty() && encoding != comment.descriptionEncoding()) {
1016  addNotification(NotificationType::Critical, "Data enoding and description encoding aren't equal.", context);
1017  throw InvalidDataException();
1018  }
1019  const string &lng = comment.language();
1020  if(lng.length() > 3) {
1021  addNotification(NotificationType::Critical, "The language must be 3 bytes long (ISO-639-2).", context);
1022  throw InvalidDataException();
1023  }
1024  StringData convertedDescription;
1025  string::size_type descriptionSize = comment.description().find("\0\0", 0, encoding == TagTextEncoding::Utf16BigEndian || encoding == TagTextEncoding::Utf16LittleEndian ? 2 : 1);
1026  if(descriptionSize == string::npos) {
1027  descriptionSize = comment.description().size();
1028  }
1029  if(version < 4 && encoding == TagTextEncoding::Utf8) {
1030  // UTF-8 is only supported by ID3v2.4, so convert back to UTF-16
1032  convertedDescription = convertUtf8ToUtf16LE(comment.description().data(), descriptionSize);
1033  descriptionSize = convertedDescription.second;
1034  }
1035  // calculate needed buffer size and create buffer
1036  const auto data = comment.toString(encoding);
1037  buffer = make_unique<char[]>(bufferSize = 1 + 3 + descriptionSize + data.size() + (encoding == TagTextEncoding::Utf16BigEndian || encoding == TagTextEncoding::Utf16LittleEndian ? 6 : 1) + data.size());
1038  // note: encoding byte + language + description size + actual data size + BOMs and termination
1039  char *offset = buffer.get();
1040  // write encoding
1041  *offset = makeTextEncodingByte(encoding);
1042  // write language
1043  for(unsigned int i = 0; i < 3; ++i) {
1044  *(++offset) = (lng.length() > i) ? lng[i] : 0x00;
1045  }
1046  // write description
1047  offset += makeBom(offset + 1, encoding);
1048  if(convertedDescription.first) {
1049  copy(convertedDescription.first.get(), convertedDescription.first.get() + descriptionSize, ++offset);
1050  } else {
1051  comment.description().copy(++offset, descriptionSize);
1052  }
1053  offset += descriptionSize;
1054  *offset = 0x00; // terminate description and increase data offset
1056  *(++offset) = 0x00;
1057  }
1058  // write actual data
1059  offset += makeBom(offset + 1, encoding);
1060  data.copy(++offset, data.size());
1061 }
1062 
1063 }
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:819
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:511
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:594
void makePictureConsideringVersion(std::unique_ptr< char[]> &buffer, uint32 &bufferSize, const TagValue &picture, byte typeInfo, byte version)
Writes the specified picture to the specified buffer.
Definition: id3v2frame.cpp:947
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:661
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:730
byte group() const
Returns the group.
Definition: id3v2frame.h:305
TagTextEncoding
Specifies the text encoding.
Definition: tagvalue.h:21
bool isCompressed() const
Returns whether the frame is compressed.
Definition: id3v2frame.h:263
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:544
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:832
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:280
std::string frameIdString() const
Returns the frame ID as string.
Definition: id3v2frame.h:199
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:564
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:756
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 compatible).
Definition: id3v2frame.cpp:939
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:674
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:231
std::size_t makeBom(char *buffer, TagTextEncoding encoding)
Writes the BOM for the specified encoding to the specified buffer.
Definition: id3v2frame.cpp:866
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 compatible).
Definition: id3v2frame.cpp:883
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:272
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:704
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 makeCommentConsideringVersion(std::unique_ptr< char[]> &buffer, uint32 &bufferSize, const TagValue &comment, byte version)
Writes the specified comment to the specified buffer.
void setLanguage(const std::string &value)
Sets the language.
Definition: tagvalue.h:439
void parseComment(const char *buffer, std::size_t maxSize, TagValue &tagValue)
Parses the comment/unsynchronized lyrics from the specified buffer.
Definition: id3v2frame.cpp:790
void setMimeType(const std::string &value)
Sets the MIME type.
Definition: tagvalue.h:418
uint16 flag() const
Returns the flags.
Definition: id3v2frame.h:207
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.
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()