Tag Parser  6.1.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/misc/memory.h>
9 
10 #include <zlib.h>
11 
12 #include <algorithm>
13 #include <cstring>
14 
15 using namespace std;
16 using namespace ConversionUtilities;
17 using namespace ChronoUtilities;
18 using namespace IoUtilities;
19 
20 namespace Media {
21 
30 Id3v2Frame::Id3v2Frame() :
31  m_flag(0),
32  m_group(0),
33  m_parsedVersion(0),
34  m_dataSize(0),
35  m_totalSize(0),
36  m_padding(false)
37 {}
38 
42 Id3v2Frame::Id3v2Frame(const identifierType &id, const TagValue &value, const byte group, const int16 flag) :
43  TagField<Id3v2Frame>(id, value),
44  m_flag(flag),
45  m_group(group),
46  m_parsedVersion(0),
47  m_dataSize(0),
48  m_totalSize(0),
49  m_padding(false)
50 {}
51 
56 template<class stringtype>
57 int parseGenreIndex(const stringtype &denotation, bool isBigEndian = false)
58 {
59  int index = -1;
60  for(auto c : denotation) {
61  if(sizeof(typename stringtype::value_type) == 2 && isBigEndian != CONVERSION_UTILITIES_IS_BYTE_ORDER_BIG_ENDIAN) {
62  c = swapOrder(static_cast<uint16>(c));
63  }
64  if(index == -1) {
65  switch(c) {
66  case ' ':
67  break;
68  case '(':
69  index = 0;
70  break;
71  case '\0':
72  return -1;
73  default:
74  if(c >= '0' && c <= '9') {
75  index = c - '0';
76  } else {
77  return -1;
78  }
79  }
80  } else {
81  switch(c) {
82  case ')':
83  return index;
84  case '\0':
85  return index;
86  default:
87  if(c >= '0' && c <= '9') {
88  index = index * 10 + c - '0';
89  } else {
90  return -1;
91  }
92  }
93  }
94  }
95  return index;
96 }
97 
108 void Id3v2Frame::parse(BinaryReader &reader, const uint32 version, const uint32 maximalSize)
109 {
111  clear();
112  string context("parsing ID3v2 frame");
113 
114  // parse header
115  if(version < 3) {
116  // parse header for ID3v2.1 and ID3v2.2
117  // -> read ID
118  setId(reader.readUInt24BE());
119  if(id() & 0xFFFF0000u) {
120  m_padding = false;
121  } else {
122  // padding reached
123  m_padding = true;
124  addNotification(NotificationType::Debug, "Frame ID starts with null-byte -> padding reached.", context);
125  throw NoDataFoundException();
126  }
127 
128  // -> update context
129  context = "parsing " + frameIdString() + " frame";
130 
131  // -> read size, check whether frame is truncated
132  m_dataSize = reader.readUInt24BE();
133  m_totalSize = m_dataSize + 6;
134  if(m_totalSize > maximalSize) {
135  addNotification(NotificationType::Warning, "The frame is truncated and will be ignored.", "parsing " + frameIdString() + " frame");
136  throw TruncatedDataException();
137  }
138 
139  // -> no flags/group in ID3v2.2
140  m_flag = 0;
141  m_group = 0;
142 
143  } else {
144  // parse header for ID3v2.3 and ID3v2.4
145  // -> read ID
146  setId(reader.readUInt32BE());
147  if(id() & 0xFF000000u) {
148  m_padding = false;
149  } else {
150  // padding reached
151  m_padding = true;
152  addNotification(NotificationType::Debug, "Frame ID starts with null-byte -> padding reached.", context);
153  throw NoDataFoundException();
154  }
155 
156  // -> update context
157  context = "parsing " + frameIdString() + " frame";
158 
159  // -> read size, check whether frame is truncated
160  m_dataSize = version >= 4
161  ? reader.readSynchsafeUInt32BE()
162  : reader.readUInt32BE();
163  m_totalSize = m_dataSize + 10;
164  if(m_totalSize > maximalSize) {
165  addNotification(NotificationType::Warning, "The frame is truncated and will be ignored.", context);
166  throw TruncatedDataException();
167  }
168 
169  // -> read flags and group
170  m_flag = reader.readUInt16BE();
171  m_group = hasGroupInformation() ? reader.readByte() : 0;
172  if(isEncrypted()) {
173  // encryption is not implemented
174  addNotification(NotificationType::Critical, "Encrypted frames aren't supported.", context);
176  }
177  }
178 
179  // frame size mustn't be 0
180  if(m_dataSize <= 0) {
181  addNotification(NotificationType::Critical, "The frame size is 0.", context);
182  throw InvalidDataException();
183  }
184 
185  // parse the data
186  unique_ptr<char[]> buffer;
187 
188  // -> decompress data if compressed; otherwise just read it
189  if(isCompressed()) {
190  uLongf decompressedSize = version >= 4 ? reader.readSynchsafeUInt32BE() : reader.readUInt32BE();
191  if(decompressedSize < m_dataSize) {
192  addNotification(NotificationType::Critical, "The decompressed size is smaller then the compressed size.", context);
193  throw InvalidDataException();
194  }
195  auto bufferCompressed = make_unique<char[]>(m_dataSize);;
196  reader.read(bufferCompressed.get(), m_dataSize);
197  buffer = make_unique<char[]>(decompressedSize);
198  switch(uncompress(reinterpret_cast<Bytef *>(buffer.get()), &decompressedSize, reinterpret_cast<Bytef *>(bufferCompressed.get()), m_dataSize)) {
199  case Z_MEM_ERROR:
200  addNotification(NotificationType::Critical, "Decompressing failed. The source buffer was too small.", context);
201  throw InvalidDataException();
202  case Z_BUF_ERROR:
203  addNotification(NotificationType::Critical, "Decompressing failed. The destination buffer was too small.", context);
204  throw InvalidDataException();
205  case Z_DATA_ERROR:
206  addNotification(NotificationType::Critical, "Decompressing failed. The input data was corrupted or incomplete.", context);
207  throw InvalidDataException();
208  case Z_OK:
209  break;
210  default:
211  addNotification(NotificationType::Critical, "Decompressing failed (unknown reason).", context);
212  throw InvalidDataException();
213  }
214  m_dataSize = decompressedSize;
215  } else {
216  buffer = make_unique<char[]>(m_dataSize);
217  reader.read(buffer.get(), m_dataSize);
218  }
219 
220  // -> get tag value depending of field type
221  if(Id3v2FrameIds::isTextFrame(id())) {
222  // frame contains text
223  TagTextEncoding dataEncoding = parseTextEncodingByte(*buffer.get()); // the first byte stores the encoding
224  if((version >= 3 &&
226  || (version < 3 && id() == Id3v2FrameIds::sTrackPosition)) {
227  // the track number or the disk number frame
228  try {
229  PositionInSet position;
230  if(characterSize(dataEncoding) > 1) {
231  position = PositionInSet(parseWideString(buffer.get() + 1, m_dataSize - 1, dataEncoding));
232  } else {
233  position = PositionInSet(parseString(buffer.get() + 1, m_dataSize - 1, dataEncoding));
234  }
235  value().assignPosition(position);
236  } catch(const ConversionException &) {
237  addNotification(NotificationType::Warning, "The value of track/disk position frame is not numeric and will be ignored.", context);
238  }
239 
240  } else if((version >= 3 && id() == Id3v2FrameIds::lLength) || (version < 3 && id() == Id3v2FrameIds::sLength)) {
241  // frame contains length
242  double milliseconds;
243  try {
244  if(characterSize(dataEncoding) > 1) {
245  milliseconds = ConversionUtilities::stringToNumber<double>(parseWideString(buffer.get() + 1, m_dataSize - 1, dataEncoding), 10);
246  } else {
247  milliseconds = ConversionUtilities::stringToNumber<double>(parseString(buffer.get() + 1, m_dataSize - 1, dataEncoding), 10);
248  }
249  value().assignTimeSpan(TimeSpan::fromMilliseconds(milliseconds));
250  } catch (const ConversionException &) {
251  addNotification(NotificationType::Warning, "The value of the length frame is not numeric and will be ignored.", context);
252  }
253 
254  } else if((version >= 3 && id() == Id3v2FrameIds::lGenre) || (version < 3 && id() == Id3v2FrameIds::sGenre)) {
255  // genre/content type
256  int genreIndex;
257  if(characterSize(dataEncoding) > 1) {
258  auto genreDenotation = parseWideString(buffer.get() + 1, m_dataSize - 1, dataEncoding);
259  genreIndex = parseGenreIndex(genreDenotation, dataEncoding == TagTextEncoding::Utf16BigEndian);
260  } else {
261  auto genreDenotation = parseString(buffer.get() + 1, m_dataSize - 1, dataEncoding);
262  genreIndex = parseGenreIndex(genreDenotation);
263  }
264  if(genreIndex != -1) {
265  // genre is specified as ID3 genre number
266  value().assignStandardGenreIndex(genreIndex);
267  } else {
268  // genre is specified as string
269  // string might be null terminated
270  auto substr = parseSubstring(buffer.get() + 1, m_dataSize - 1, dataEncoding);
271  value().assignData(get<0>(substr), get<1>(substr), TagDataType::Text, dataEncoding);
272  }
273  } else { // any other text frame
274  // string might be null terminated
275  auto substr = parseSubstring(buffer.get() + 1, m_dataSize - 1, dataEncoding);
276  value().assignData(get<0>(substr), get<1>(substr), TagDataType::Text, dataEncoding);
277  }
278 
279  } else if(version >= 3 && id() == Id3v2FrameIds::lCover) {
280  // frame stores picture
281  byte type;
282  parsePicture(buffer.get(), m_dataSize, value(), type);
283  setTypeInfo(type);
284 
285  } else if(version < 3 && id() == Id3v2FrameIds::sCover) {
286  // frame stores legacy picutre
287  byte type;
288  parseLegacyPicture(buffer.get(), m_dataSize, value(), type);
289  setTypeInfo(type);
290 
291  } else if(((version >= 3 && id() == Id3v2FrameIds::lComment) || (version < 3 && id() == Id3v2FrameIds::sComment))
292  || ((version >= 3 && id() == Id3v2FrameIds::lUnsynchronizedLyrics) || (version < 3 && id() == Id3v2FrameIds::sUnsynchronizedLyrics))) {
293  // comment frame or unsynchronized lyrics frame (these two frame types have the same structure)
294  parseComment(buffer.get(), m_dataSize, value());
295 
296  } else {
297  // unknown frame
298  value().assignData(buffer.get(), m_dataSize, TagDataType::Undefined);
299  }
300 }
301 
313 {
314  return Id3v2FrameMaker(*this, version);
315 }
316 
325 void Id3v2Frame::make(BinaryWriter &writer, const uint32 version)
326 {
327  prepareMaking(version).make(writer);
328 }
329 
334 {
335  m_flag = 0;
336  m_group = 0;
337  m_parsedVersion = 0;
338  m_dataSize = 0;
339  m_totalSize = 0;
340  m_padding = false;
341 }
342 
354 Id3v2FrameMaker::Id3v2FrameMaker(Id3v2Frame &frame, const byte version) :
355  m_frame(frame),
356  m_frameId(m_frame.id()),
357  m_version(version)
358 {
359  m_frame.invalidateStatus();
360  const string context("making " + m_frame.frameIdString() + " frame");
361 
362  // validate assigned data
363  if(m_frame.value().isEmpty()) {
364  m_frame.addNotification(NotificationType::Critical, "Cannot make an empty frame.", context);
365  throw InvalidDataException();
366  }
367  if(m_frame.isEncrypted()) {
368  m_frame.addNotification(NotificationType::Critical, "Cannot make an encrypted frame (isn't supported by this tagging library).", context);
369  throw InvalidDataException();
370  }
371  if(m_frame.hasPaddingReached()) {
372  m_frame.addNotification(NotificationType::Critical, "Cannot make a frame which is marked as padding.", context);
373  throw InvalidDataException();
374  }
375  if(version < 3 && m_frame.isCompressed()) {
376  m_frame.addNotification(NotificationType::Warning, "Compression is not supported by the version of ID3v2 and won't be applied.", context);
377  }
378  if(version < 3 && (m_frame.flag() || m_frame.group())) {
379  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);
380  }
381 
382  // convert frame ID if necessary
383  if(version >= 3) {
384  if(Id3v2FrameIds::isShortId(m_frameId)) {
385  // try to convert the short frame ID to its long equivalent
386  if(!(m_frameId = Id3v2FrameIds::convertToLongId(m_frameId))) {
387  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);
388  throw InvalidDataException();
389  }
390  }
391  } else {
392  if(Id3v2FrameIds::isLongId(m_frameId)) {
393  // try to convert the long frame ID to its short equivalent
394  if(!(m_frameId = Id3v2FrameIds::convertToShortId(m_frameId))) {
395  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);
396  throw InvalidDataException();
397  }
398  }
399  }
400 
401  // make actual data depending on the frame ID
402  try {
403  if(Id3v2FrameIds::isTextFrame(m_frameId)) {
404  // it is a text frame
405  if((version >= 3 && (m_frameId == Id3v2FrameIds::lTrackPosition || m_frameId == Id3v2FrameIds::lDiskPosition))
406  || (version < 3 && m_frameId == Id3v2FrameIds::sTrackPosition)) {
407  // track number or the disk number frame
408  m_frame.makeString(m_data, m_decompressedSize, m_frame.value().toString(), TagTextEncoding::Latin1);
409  } else if((version >= 3 && m_frameId == Id3v2FrameIds::lLength)
410  || (version < 3 && m_frameId == Id3v2FrameIds::sLength)) {
411  // length frame
412  m_frame.makeString(m_data, m_decompressedSize, ConversionUtilities::numberToString(m_frame.value().toTimeSpan().totalMilliseconds()), TagTextEncoding::Latin1);
413  } else if(m_frame.value().type() == TagDataType::StandardGenreIndex && ((version >= 3 && m_frameId == Id3v2FrameIds::lGenre)
414  || (version < 3 && m_frameId == Id3v2FrameIds::sGenre))) {
415  // pre-defined genre frame
416  m_frame.makeString(m_data, m_decompressedSize, ConversionUtilities::numberToString(m_frame.value().toStandardGenreIndex()), TagTextEncoding::Latin1);
417  } else {
418  // any other text frame
419  m_frame.makeString(m_data, m_decompressedSize, m_frame.value().toString(), m_frame.value().dataEncoding()); // the same as a normal text frame
420  }
421 
422  } else if(version >= 3 && m_frameId == Id3v2FrameIds::lCover) {
423  // picture frame
424  m_frame.makePicture(m_data, m_decompressedSize, m_frame.value(), m_frame.isTypeInfoAssigned() ? m_frame.typeInfo() : 0);
425 
426  } else if(version < 3 && m_frameId == Id3v2FrameIds::sCover) {
427  // legacy picture frame
428  m_frame.makeLegacyPicture(m_data, m_decompressedSize, m_frame.value(), m_frame.isTypeInfoAssigned() ? m_frame.typeInfo() : 0);
429 
430  } else if(((version >= 3 && m_frameId == Id3v2FrameIds::lComment)
431  || (version < 3 && m_frameId == Id3v2FrameIds::sComment))
432  || ((version >= 3 && m_frameId == Id3v2FrameIds::lUnsynchronizedLyrics)
433  || (version < 3 && m_frameId == Id3v2FrameIds::sUnsynchronizedLyrics))) {
434  // the comment frame or the unsynchronized lyrics frame
435  m_frame.makeComment(m_data, m_decompressedSize, m_frame.value());
436 
437  } else {
438  // an unknown frame
439  // create buffer
440  m_data = make_unique<char[]>(m_decompressedSize = m_frame.value().dataSize());
441  // just write the data
442  copy(m_frame.value().dataPointer(), m_frame.value().dataPointer() + m_decompressedSize, m_data.get());
443  }
444  } catch(const ConversionException &) {
445  m_frame.addNotification(NotificationType::Critical, "Assigned value can not be converted appropriately.", context);
446  throw InvalidDataException();
447  }
448 
449  // apply compression if frame should be compressed
450  if(version >= 3 && m_frame.isCompressed()) {
451  m_dataSize = compressBound(m_decompressedSize);
452  auto compressedData = make_unique<char[]>(m_decompressedSize);
453  switch(compress(reinterpret_cast<Bytef *>(compressedData.get()), reinterpret_cast<uLongf *>(&m_dataSize), reinterpret_cast<Bytef *>(m_data.get()), m_decompressedSize)) {
454  case Z_MEM_ERROR:
455  m_frame.addNotification(NotificationType::Critical, "Decompressing failed. The source buffer was too small.", context);
456  throw InvalidDataException();
457  case Z_BUF_ERROR:
458  m_frame.addNotification(NotificationType::Critical, "Decompressing failed. The destination buffer was too small.", context);
459  throw InvalidDataException();
460  case Z_OK:
461  ;
462  }
463  m_data.swap(compressedData);
464  } else {
465  m_dataSize = m_decompressedSize;
466  }
467 
468  // calculate required size
469  // -> data size
470  m_requiredSize = m_dataSize;
471  if(version < 3) {
472  // -> header size
473  m_requiredSize += 6;
474  } else {
475  // -> header size
476  m_requiredSize += 10;
477  // -> group byte
478  if(m_frame.hasGroupInformation()) {
479  m_requiredSize += 1;
480  }
481  // -> decompressed size
482  if(version >= 3 && m_frame.isCompressed()) {
483  m_requiredSize += 4;
484  }
485  }
486 }
487 
495 void Id3v2FrameMaker::make(BinaryWriter &writer)
496 {
497  if(m_version < 3) {
498  writer.writeUInt24BE(m_frameId);
499  writer.writeUInt24BE(m_dataSize);
500  } else {
501  writer.writeUInt32BE(m_frameId);
502  if(m_version >= 4) {
503  writer.writeSynchsafeUInt32BE(m_dataSize);
504  } else {
505  writer.writeUInt32BE(m_dataSize);
506  }
507  writer.writeUInt16BE(m_frame.flag());
508  if(m_frame.hasGroupInformation()) {
509  writer.writeByte(m_frame.group());
510  }
511  if(m_version >= 3 && m_frame.isCompressed()) {
512  if(m_version >= 4) {
513  writer.writeSynchsafeUInt32BE(m_decompressedSize);
514  } else {
515  writer.writeUInt32BE(m_decompressedSize);
516  }
517  }
518  }
519  writer.write(m_data.get(), m_dataSize);
520 }
521 
529 {
530  switch(textEncodingByte) {
531  case 0: // Ascii
533  case 1: // Utf 16 with bom
535  case 2: // Utf 16 without bom
537  case 3: // Utf 8
538  return TagTextEncoding::Utf8;
539  default:
540  addNotification(NotificationType::Warning, "The charset of the frame is invalid. Latin-1 will be used.", "parsing encoding of frame " + frameIdString());
542  }
543 }
544 
549 {
550  switch(textEncoding) {
552  return 0;
554  return 3;
556  return 1;
558  return 2;
559  default:
560  return 0;
561  }
562 }
563 
578 tuple<const char *, size_t, const char *> Id3v2Frame::parseSubstring(const char *buffer, std::size_t bufferSize, TagTextEncoding &encoding, bool addWarnings)
579 {
580  tuple<const char *, size_t, const char *> res(buffer, 0, buffer + bufferSize);
581  switch(encoding) {
584  if(bufferSize >= 2) {
585  if(ConversionUtilities::LE::toUInt16(buffer) == 0xFEFF) {
586  if(encoding != TagTextEncoding::Utf16LittleEndian) {
587  addNotification(NotificationType::Critical, "Denoted character set doesn't match present BOM - assuming UTF-16 Little Endian.", "parsing frame " + frameIdString());
589  }
590  get<0>(res) += 2;
591  } else if(ConversionUtilities::BE::toUInt16(buffer) == 0xFEFF) {
592  if(encoding != TagTextEncoding::Utf16BigEndian) {
593  addNotification(NotificationType::Critical, "Denoted character set doesn't match present BOM - assuming UTF-16 Big Endian.", "parsing frame " + frameIdString());
595  }
596  get<0>(res) += 2;
597  }
598  }
599  const uint16 *pos = reinterpret_cast<const uint16 *>(get<0>(res));
600  for(; *pos != 0x0000; ++pos) {
601  if(pos < reinterpret_cast<const uint16 *>(get<2>(res))) {
602  get<1>(res) += 2;
603  } else {
604  if(addWarnings) {
605  addNotification(NotificationType::Warning, "Wide string in frame is not terminated proberly.", "parsing termination of frame " + frameIdString());
606  }
607  break;
608  }
609  }
610  get<2>(res) = reinterpret_cast<const char *>(++pos);
611  break;
612  }
613  default: {
614  if((bufferSize >= 3) && (ConversionUtilities::BE::toUInt24(buffer) == 0x00EFBBBF)) {
615  get<0>(res) += 3;
616  if(encoding != TagTextEncoding::Utf8) {
617  addNotification(NotificationType::Critical, "Denoted character set doesn't match present BOM - assuming UTF-8.", "parsing frame " + frameIdString());
618  encoding = TagTextEncoding::Utf8;
619  }
620  }
621  const char *pos = get<0>(res);
622  for(; *pos != 0x00; ++pos) {
623  if(pos < get<2>(res)) {
624  ++get<1>(res);
625  } else {
626  if(addWarnings) {
627  addNotification(NotificationType::Warning, "String in frame is not terminated proberly.", "parsing termination of frame " + frameIdString());
628  }
629  break;
630  }
631  }
632  get<2>(res) = ++pos;
633  break;
634  }
635  }
636  return res;
637 }
638 
644 string Id3v2Frame::parseString(const char *buffer, size_t dataSize, TagTextEncoding &encoding, bool addWarnings)
645 {
646  auto substr = parseSubstring(buffer, dataSize, encoding, addWarnings);
647  return string(get<0>(substr), get<1>(substr));
648 }
649 
657 u16string Id3v2Frame::parseWideString(const char *buffer, size_t dataSize, TagTextEncoding &encoding, bool addWarnings)
658 {
659  auto substr = parseSubstring(buffer, dataSize, encoding, addWarnings);
660  u16string res(reinterpret_cast<u16string::const_pointer>(get<0>(substr)), get<1>(substr) / 2);
661  if(encoding !=
662  #if defined(CONVERSION_UTILITIES_BYTE_ORDER_LITTLE_ENDIAN)
664  #elif defined(CONVERSION_UTILITIES_BYTE_ORDER_BIG_ENDIAN)
666  #else
667  # error "Host byte order not supported"
668  #endif
669  ) {
670  // ensure byte order matches host byte order
671  for(auto &c : res) {
672  c = ((c >> 8) & 0x00FF) | ((c << 8) & 0xFF00);
673  }
674  }
675  return res;
676 }
677 
687 void Id3v2Frame::parseBom(const char *buffer, size_t maxSize, TagTextEncoding &encoding)
688 {
689  switch(encoding) {
692  if((maxSize >= 2) && (ConversionUtilities::BE::toUInt16(buffer) == 0xFFFE)) {
694  } else if((maxSize >= 2) && (ConversionUtilities::BE::toUInt16(buffer) == 0xFEFF)) {
696  }
697  break;
698  default:
699  if((maxSize >= 3) && (ConversionUtilities::BE::toUInt24(buffer) == 0x00EFBBBF)) {
700  encoding = TagTextEncoding::Utf8;
701  addNotification(NotificationType::Warning, "UTF-8 byte order mark found in text frame.", "parsing byte oder mark of frame " + frameIdString());
702  }
703  }
704 }
705 
713 void Id3v2Frame::parseLegacyPicture(const char *buffer, std::size_t maxSize, TagValue &tagValue, byte &typeInfo)
714 {
715  static const string context("parsing ID3v2.2 picture frame");
716  if(maxSize < 6) {
717  addNotification(NotificationType::Critical, "Picture frame is incomplete.", context);
718  throw TruncatedDataException();
719  }
720  const char *end = buffer + maxSize;
721  auto dataEncoding = parseTextEncodingByte(*buffer); // the first byte stores the encoding
722  //auto imageFormat = parseSubstring(buffer + 1, 3, TagTextEncoding::Latin1);
723  typeInfo = static_cast<unsigned char>(*(buffer + 4));
724  auto substr = parseSubstring(buffer + 5, end - 5 - buffer, dataEncoding, true);
725  tagValue.setDescription(string(get<0>(substr), get<1>(substr)), dataEncoding);
726  if(get<2>(substr) >= end) {
727  addNotification(NotificationType::Critical, "Picture frame is incomplete (actual data is missing).", context);
728  throw TruncatedDataException();
729  }
730  tagValue.assignData(get<2>(substr), end - get<2>(substr), TagDataType::Picture, dataEncoding);
731 }
732 
740 void Id3v2Frame::parsePicture(const char *buffer, std::size_t maxSize, TagValue &tagValue, byte &typeInfo)
741 {
742  static const string context("parsing ID3v2.3 picture frame");
743  const char *end = buffer + maxSize;
744  auto dataEncoding = parseTextEncodingByte(*buffer); // the first byte stores the encoding
745  auto mimeTypeEncoding = TagTextEncoding::Latin1;
746  auto substr = parseSubstring(buffer + 1, maxSize - 1, mimeTypeEncoding, true);
747  if(get<1>(substr)) {
748  tagValue.setMimeType(string(get<0>(substr), get<1>(substr)));
749  }
750  if(get<2>(substr) >= end) {
751  addNotification(NotificationType::Critical, "Picture frame is incomplete (type info, description and actual data are missing).", context);
752  throw TruncatedDataException();
753  }
754  typeInfo = static_cast<unsigned char>(*get<2>(substr));
755  if(++get<2>(substr) >= end) {
756  addNotification(NotificationType::Critical, "Picture frame is incomplete (description and actual data are missing).", context);
757  throw TruncatedDataException();
758  }
759  substr = parseSubstring(get<2>(substr), end - get<2>(substr), dataEncoding, true);
760  tagValue.setDescription(string(get<0>(substr), get<1>(substr)), dataEncoding);
761  if(get<2>(substr) >= end) {
762  addNotification(NotificationType::Critical, "Picture frame is incomplete (actual data is missing).", context);
763  throw TruncatedDataException();
764  }
765  tagValue.assignData(get<2>(substr), end - get<2>(substr), TagDataType::Picture, dataEncoding);
766 }
767 
774 void Id3v2Frame::parseComment(const char *buffer, std::size_t dataSize, TagValue &tagValue)
775 {
776  static const string context("parsing comment/unsynchronized lyrics frame");
777  const char *end = buffer + dataSize;
778  if(dataSize < 5) {
779  addNotification(NotificationType::Critical, "Comment frame is incomplete.", context);
780  throw TruncatedDataException();
781  }
782  TagTextEncoding dataEncoding = parseTextEncodingByte(*buffer);
783  if(*(++buffer)) {
784  tagValue.setLanguage(string(buffer, 3));
785  }
786  auto substr = parseSubstring(buffer += 3, dataSize -= 4, dataEncoding, true);
787  tagValue.setDescription(string(get<0>(substr), get<1>(substr)), dataEncoding);
788  if(get<2>(substr) > end) {
789  addNotification(NotificationType::Critical, "Comment frame is incomplete (description not terminated?).", context);
790  throw TruncatedDataException();
791  }
792  substr = parseSubstring(get<2>(substr), end - get<2>(substr), dataEncoding, false);
793  tagValue.assignData(get<0>(substr), get<1>(substr), TagDataType::Text, dataEncoding);
794 }
795 
803 void Id3v2Frame::makeString(std::unique_ptr<char[]> &buffer, uint32 &bufferSize, const std::string &value, TagTextEncoding encoding)
804 {
805  makeEncodingAndData(buffer, bufferSize, encoding, value.data(), value.size());
806 }
807 
816 void Id3v2Frame::makeEncodingAndData(std::unique_ptr<char[]> &buffer, uint32 &bufferSize, TagTextEncoding encoding, const char *data, std::size_t dataSize)
817 {
818  // calculate buffer size
819  if(!data) {
820  dataSize = 0;
821  }
822  switch(encoding) {
825  case TagTextEncoding::Unspecified: // assumption
826  // allocate buffer
827  buffer = make_unique<char[]>(bufferSize = 1 + dataSize + 1);
828  break;
831  // allocate buffer
832  buffer = make_unique<char[]>(bufferSize = 1 + dataSize + 2);
833  break;
834  }
835  buffer[0] = makeTextEncodingByte(encoding); // set text encoding byte
836  if(dataSize > 0) {
837  copy(data, data + dataSize, buffer.get() + 1); // write string data
838  }
839 }
840 
844 void Id3v2Frame::makeLegacyPicture(unique_ptr<char[]> &buffer, uint32 &bufferSize, const TagValue &picture, byte typeInfo)
845 {
846  // calculate needed buffer size and create buffer
847  TagTextEncoding descriptionEncoding = picture.descriptionEncoding();
848  uint32 dataSize = picture.dataSize();
849  string::size_type descriptionLength = picture.description().find('\0');
850  if(descriptionLength == string::npos) {
851  descriptionLength = picture.description().length();
852  }
853  buffer = make_unique<char[]>(bufferSize = 1 + 3 + 1 + descriptionLength + (descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? 2 : 1) + dataSize);
854  // note: encoding byte + image format + picture type byte + description length + 1 or 2 null bytes (depends on encoding) + data size
855  char *offset = buffer.get();
856  // write encoding byte
857  *offset = makeTextEncodingByte(descriptionEncoding);
858  // write mime type
859  const char *imageFormat;
860  if(picture.mimeType() == "image/jpeg") {
861  imageFormat = "JPG";
862  } else if(picture.mimeType() == "image/png") {
863  imageFormat = "PNG";
864  } else if(picture.mimeType() == "image/gif") {
865  imageFormat = "GIF";
866  } else if(picture.mimeType() == "-->") {
867  imageFormat = picture.mimeType().data();
868  } else {
869  imageFormat = "UND";
870  }
871  strncpy(++offset, imageFormat, 3);
872  // write picture type
873  *(offset += 3) = typeInfo;
874  // write description
875  picture.description().copy(++offset, descriptionLength);
876  *(offset += descriptionLength) = 0x00; // terminate description and increase data offset
877  if(descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian) {
878  *(++offset) = 0x00;
879  }
880  // write actual data
881  copy(picture.dataPointer(), picture.dataPointer() + picture.dataSize(), ++offset);
882 }
883 
887 void Id3v2Frame::makePicture(unique_ptr<char[]> &buffer, uint32 &bufferSize, const TagValue &picture, byte typeInfo)
888 {
889  // calculate needed buffer size and create buffer
890  TagTextEncoding descriptionEncoding = picture.descriptionEncoding();
891  uint32 dataSize = picture.dataSize();
892  string::size_type mimeTypeLength = picture.mimeType().find('\0');
893  if(mimeTypeLength == string::npos) {
894  mimeTypeLength = picture.mimeType().length();
895  }
896  string::size_type descriptionLength = picture.description().find('\0');
897  if(descriptionLength == string::npos) {
898  descriptionLength = picture.description().length();
899  }
900  buffer = make_unique<char[]>(bufferSize = 1 + mimeTypeLength + 1 + 1 + descriptionLength + (descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? 2 : 1) + dataSize);
901  // note: encoding byte + mime type length + 0 byte + picture type byte + description length + 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  picture.mimeType().copy(++offset, mimeTypeLength);
907  *(offset += mimeTypeLength) = 0x00; // terminate mime type
908  // write picture type
909  *(++offset) = typeInfo;
910  // write description
911  picture.description().copy(++offset, descriptionLength);
912  *(offset += descriptionLength) = 0x00; // terminate description and increase data offset
913  if(descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian) {
914  *(++offset) = 0x00;
915  }
916  // write actual data
917  copy(picture.dataPointer(), picture.dataPointer() + picture.dataSize(), ++offset);
918 }
919 
923 void Id3v2Frame::makeComment(unique_ptr<char[]> &buffer, uint32 &bufferSize, const TagValue &comment)
924 {
925  static const string context("making comment frame");
926  // check type and other values are valid
927  TagTextEncoding encoding = comment.dataEncoding();
928  if(!comment.description().empty() && encoding != comment.descriptionEncoding()) {
929  addNotification(NotificationType::Critical, "Data enoding and description encoding aren't equal.", context);
930  throw InvalidDataException();
931  }
932  const string &lng = comment.language();
933  if(lng.length() > 3) {
934  addNotification(NotificationType::Critical, "The language must be 3 bytes long (ISO-639-2).", context);
935  throw InvalidDataException();
936  }
937  // calculate needed buffer size and create buffer
938  string::size_type descriptionLength = comment.description().find('\0');
939  if(descriptionLength == string::npos) {
940  descriptionLength = comment.description().length();
941  }
942  const auto data = comment.toString();
943  buffer = make_unique<char[]>(bufferSize = 1 + 3 + descriptionLength + (encoding == TagTextEncoding::Utf16BigEndian || encoding == TagTextEncoding::Utf16LittleEndian ? 2 : 1) + data.size());
944  // note: encoding byte + language + description length + 1 or 2 null bytes + data size
945  char *offset = buffer.get();
946  // write encoding
947  *offset = makeTextEncodingByte(encoding);
948  // write language
949  for(unsigned int i = 0; i < 3; ++i) {
950  *(++offset) = (lng.length() > i) ? lng[i] : 0x00;
951  }
952  // write description
953  comment.description().copy(++offset, descriptionLength);
954  offset += descriptionLength;
955  *offset = 0x00; // terminate description and increase data offset
957  *(++offset) = 0x00;
958  }
959  // write actual data
960  data.copy(++offset, data.size());
961 }
962 
963 }
The TagValue class wraps values of different types.
Definition: tagvalue.h:64
void setDescription(const std::string &value, TagTextEncoding encoding=TagTextEncoding::Latin1)
Sets the description.
Definition: tagvalue.h:387
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:333
TagTextEncoding dataEncoding() const
Returns the data encoding.
Definition: tagvalue.h:466
Id3v2FrameMaker prepareMaking(const uint32 version)
Prepares making.
Definition: id3v2frame.cpp:312
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:803
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:495
char * dataPointer() const
Returns a pointer to the raw data assigned to the current instance.
Definition: tagvalue.h:363
int parseGenreIndex(const stringtype &denotation, bool isBigEndian=false)
Helper function to parse the genre index.
Definition: id3v2frame.cpp:57
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:578
void parse(IoUtilities::BinaryReader &reader, const uint32 version, const uint32 maximalSize=0)
Parses a frame from the stream read using the specified reader.
Definition: id3v2frame.cpp:108
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:644
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:713
byte group() const
Returns the group.
Definition: id3v2frame.h:302
TagTextEncoding
Specifies the text encoding.
Definition: tagvalue.h:22
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:528
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:816
void assignTimeSpan(ChronoUtilities::TimeSpan value)
Assigns the given TimeSpan value.
Definition: tagvalue.h:279
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:192
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:419
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:548
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:325
TagTextEncoding descriptionEncoding() const
Returns the description encoding.
Definition: tagvalue.h:476
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:740
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:266
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:887
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:657
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:844
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:35
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:30
void parseBom(const char *buffer, std::size_t maxSize, TagTextEncoding &encoding)
Parses a byte order mark from the specified buffer.
Definition: id3v2frame.cpp:687
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:430
void parseComment(const char *buffer, std::size_t maxSize, TagValue &tagValue)
Parses the comment/unsynchronized lyrics from the specified buffer.
Definition: id3v2frame.cpp:774
void setMimeType(const std::string &value)
Sets the MIME type.
Definition: tagvalue.h:409
uint16 flag() const
Returns the flags.
Definition: id3v2frame.h:204
const std::string & mimeType() const
Returns the MIME type.
Definition: tagvalue.h:398
void makeComment(std::unique_ptr< char[]> &buffer, uint32 &bufferSize, const TagValue &comment)
Writes the specified comment to the specified buffer.
Definition: id3v2frame.cpp:923
const std::string & description() const
Returns the description.
Definition: tagvalue.h:374
size_t dataSize() const
Returns the size of the assigned value in bytes.
Definition: tagvalue.h:352
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:307
void setId(const identifierType &id)
Sets the id of the current Tag Field.
TAG_PARSER_EXPORT const char * version()