Tag Parser  6.5.1
C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags
id3v2frame.cpp
Go to the documentation of this file.
1 #include "./id3v2frame.h"
2 #include "./id3genres.h"
3 #include "./id3v2frameids.h"
4 
5 #include "../exceptions.h"
6 
7 #include <c++utilities/conversion/stringconversion.h>
8 #include <c++utilities/conversion/stringbuilder.h>
9 
10 #include <zlib.h>
11 
12 #include <algorithm>
13 #include <cstring>
14 #include <memory>
15 
16 using namespace std;
17 using namespace ConversionUtilities;
18 using namespace ChronoUtilities;
19 using namespace IoUtilities;
20 
21 namespace Media {
22 
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)
69 {
70  int index = -1;
71  for(auto c : denotation) {
72  if(index == -1) {
73  switch(c) {
74  case ' ':
75  break;
76  case '(':
77  index = 0;
78  break;
79  case '\0':
80  return -1;
81  default:
82  if(c >= '0' && c <= '9') {
83  index = c - '0';
84  } else {
85  return -1;
86  }
87  }
88  } else {
89  switch(c) {
90  case ')':
91  return index;
92  case '\0':
93  return index;
94  default:
95  if(c >= '0' && c <= '9') {
96  index = index * 10 + c - '0';
97  } else {
98  return -1;
99  }
100  }
101  }
102  }
103  return index;
104 }
105 
116 void Id3v2Frame::parse(BinaryReader &reader, const uint32 version, const uint32 maximalSize)
117 {
119  clear();
120  static const string defaultContext("parsing ID3v2 frame");
121  string context;
122 
123  // parse header
124  if(version < 3) {
125  // parse header for ID3v2.1 and ID3v2.2
126  // -> read ID
127  setId(reader.readUInt24BE());
128  if(id() & 0xFFFF0000u) {
129  m_padding = false;
130  } else {
131  // padding reached
132  m_padding = true;
133  addNotification(NotificationType::Debug, "Frame ID starts with null-byte -> padding reached.", defaultContext);
134  throw NoDataFoundException();
135  }
136 
137  // -> update context
138  context = "parsing " % frameIdString() + " frame";
139 
140  // -> read size, check whether frame is truncated
141  m_dataSize = reader.readUInt24BE();
142  m_totalSize = m_dataSize + 6;
143  if(m_totalSize > maximalSize) {
144  addNotification(NotificationType::Warning, "The frame is truncated and will be ignored.", context);
145  throw TruncatedDataException();
146  }
147 
148  // -> no flags/group in ID3v2.2
149  m_flag = 0;
150  m_group = 0;
151 
152  } else {
153  // parse header for ID3v2.3 and ID3v2.4
154  // -> read ID
155  setId(reader.readUInt32BE());
156  if(id() & 0xFF000000u) {
157  m_padding = false;
158  } else {
159  // padding reached
160  m_padding = true;
161  addNotification(NotificationType::Debug, "Frame ID starts with null-byte -> padding reached.", defaultContext);
162  throw NoDataFoundException();
163  }
164 
165  // -> update context
166  context = "parsing " % frameIdString() + " frame";
167 
168  // -> read size, check whether frame is truncated
169  m_dataSize = version >= 4
170  ? reader.readSynchsafeUInt32BE()
171  : reader.readUInt32BE();
172  m_totalSize = m_dataSize + 10;
173  if(m_totalSize > maximalSize) {
174  addNotification(NotificationType::Warning, "The frame is truncated and will be ignored.", context);
175  throw TruncatedDataException();
176  }
177 
178  // -> read flags and group
179  m_flag = reader.readUInt16BE();
180  m_group = hasGroupInformation() ? reader.readByte() : 0;
181  if(isEncrypted()) {
182  // encryption is not implemented
183  addNotification(NotificationType::Critical, "Encrypted frames aren't supported.", context);
185  }
186  }
187 
188  // frame size mustn't be 0
189  if(m_dataSize <= 0) {
190  addNotification(NotificationType::Critical, "The frame size is 0.", context);
191  throw InvalidDataException();
192  }
193 
194  // parse the data
195  unique_ptr<char[]> buffer;
196 
197  // -> decompress data if compressed; otherwise just read it
198  if(isCompressed()) {
199  uLongf decompressedSize = version >= 4 ? reader.readSynchsafeUInt32BE() : reader.readUInt32BE();
200  if(decompressedSize < m_dataSize) {
201  addNotification(NotificationType::Critical, "The decompressed size is smaller than the compressed size.", context);
202  throw InvalidDataException();
203  }
204  auto bufferCompressed = make_unique<char[]>(m_dataSize);;
205  reader.read(bufferCompressed.get(), m_dataSize);
206  buffer = make_unique<char[]>(decompressedSize);
207  switch(uncompress(reinterpret_cast<Bytef *>(buffer.get()), &decompressedSize, reinterpret_cast<Bytef *>(bufferCompressed.get()), m_dataSize)) {
208  case Z_MEM_ERROR:
209  addNotification(NotificationType::Critical, "Decompressing failed. The source buffer was too small.", context);
210  throw InvalidDataException();
211  case Z_BUF_ERROR:
212  addNotification(NotificationType::Critical, "Decompressing failed. The destination buffer was too small.", context);
213  throw InvalidDataException();
214  case Z_DATA_ERROR:
215  addNotification(NotificationType::Critical, "Decompressing failed. The input data was corrupted or incomplete.", context);
216  throw InvalidDataException();
217  case Z_OK:
218  break;
219  default:
220  addNotification(NotificationType::Critical, "Decompressing failed (unknown reason).", context);
221  throw InvalidDataException();
222  }
223  m_dataSize = decompressedSize;
224  } else {
225  buffer = make_unique<char[]>(m_dataSize);
226  reader.read(buffer.get(), m_dataSize);
227  }
228 
229  // -> get tag value depending of field type
230  if(Id3v2FrameIds::isTextFrame(id())) {
231  // frame contains text
232  TagTextEncoding dataEncoding = parseTextEncodingByte(*buffer.get()); // the first byte stores the encoding
233  if((version >= 3 &&
235  || (version < 3 && id() == Id3v2FrameIds::sTrackPosition)) {
236  // the track number or the disk number frame
237  try {
238  PositionInSet position;
239  if(characterSize(dataEncoding) > 1) {
240  position = PositionInSet(parseWideString(buffer.get() + 1, m_dataSize - 1, dataEncoding));
241  } else {
242  position = PositionInSet(parseString(buffer.get() + 1, m_dataSize - 1, dataEncoding));
243  }
244  value().assignPosition(position);
245  } catch(const ConversionException &) {
246  addNotification(NotificationType::Warning, "The value of track/disk position frame is not numeric and will be ignored.", context);
247  }
248 
249  } else if((version >= 3 && id() == Id3v2FrameIds::lLength) || (version < 3 && id() == Id3v2FrameIds::sLength)) {
250  // frame contains length
251  try {
252  string milliseconds;
253  if(dataEncoding == TagTextEncoding::Utf16BigEndian || dataEncoding == TagTextEncoding::Utf16LittleEndian) {
254  const auto parsedStringRef = parseSubstring(buffer.get() + 1, m_dataSize - 1, dataEncoding);
255  const auto convertedStringData = dataEncoding == TagTextEncoding::Utf16BigEndian
256  ? convertUtf16BEToUtf8(get<0>(parsedStringRef), get<1>(parsedStringRef))
257  : convertUtf16LEToUtf8(get<0>(parsedStringRef), get<1>(parsedStringRef));
258  milliseconds = string(convertedStringData.first.get(), convertedStringData.second);
259  } else { // Latin-1 or UTF-8
260  milliseconds = parseString(buffer.get() + 1, m_dataSize - 1, dataEncoding);
261  }
262  value().assignTimeSpan(TimeSpan::fromMilliseconds(stringToNumber<double>(milliseconds)));
263  } catch (const ConversionException &) {
264  addNotification(NotificationType::Warning, "The value of the length frame is not numeric and will be ignored.", context);
265  }
266 
267  } else if((version >= 3 && id() == Id3v2FrameIds::lGenre) || (version < 3 && id() == Id3v2FrameIds::sGenre)) {
268  // genre/content type
269  int genreIndex;
270  if(characterSize(dataEncoding) > 1) {
271  auto genreDenotation = parseWideString(buffer.get() + 1, m_dataSize - 1, dataEncoding);
272  genreIndex = parseGenreIndex(genreDenotation);
273  } else {
274  auto genreDenotation = parseString(buffer.get() + 1, m_dataSize - 1, dataEncoding);
275  genreIndex = parseGenreIndex(genreDenotation);
276  }
277  if(genreIndex != -1) {
278  // genre is specified as ID3 genre number
279  value().assignStandardGenreIndex(genreIndex);
280  } else {
281  // genre is specified as string
282  // string might be null terminated
283  auto substr = parseSubstring(buffer.get() + 1, m_dataSize - 1, dataEncoding);
284  value().assignData(get<0>(substr), get<1>(substr), TagDataType::Text, dataEncoding);
285  }
286  } else { // any other text frame
287  // string might be null terminated
288  auto substr = parseSubstring(buffer.get() + 1, m_dataSize - 1, dataEncoding);
289  value().assignData(get<0>(substr), get<1>(substr), TagDataType::Text, dataEncoding);
290  }
291 
292  } else if(version >= 3 && id() == Id3v2FrameIds::lCover) {
293  // frame stores picture
294  byte type;
295  parsePicture(buffer.get(), m_dataSize, value(), type);
296  setTypeInfo(type);
297 
298  } else if(version < 3 && id() == Id3v2FrameIds::sCover) {
299  // frame stores legacy picutre
300  byte type;
301  parseLegacyPicture(buffer.get(), m_dataSize, value(), type);
302  setTypeInfo(type);
303 
304  } else if(((version >= 3 && id() == Id3v2FrameIds::lComment) || (version < 3 && id() == Id3v2FrameIds::sComment))
306  // comment frame or unsynchronized lyrics frame (these two frame types have the same structure)
307  parseComment(buffer.get(), m_dataSize, value());
308 
309  } else {
310  // unknown frame
311  value().assignData(buffer.get(), m_dataSize, TagDataType::Undefined);
312  }
313 }
314 
326 {
327  return Id3v2FrameMaker(*this, version);
328 }
329 
338 void Id3v2Frame::make(BinaryWriter &writer, const uint32 version)
339 {
340  prepareMaking(version).make(writer);
341 }
342 
347 {
348  m_flag = 0;
349  m_group = 0;
350  m_parsedVersion = 0;
351  m_dataSize = 0;
352  m_totalSize = 0;
353  m_padding = false;
354 }
355 
367 Id3v2FrameMaker::Id3v2FrameMaker(Id3v2Frame &frame, const byte version) :
368  m_frame(frame),
369  m_frameId(m_frame.id()),
370  m_version(version)
371 {
372  m_frame.invalidateStatus();
373  const string context("making " % m_frame.frameIdString() + " frame");
374 
375  // validate assigned data
376  if(m_frame.value().isEmpty()) {
377  m_frame.addNotification(NotificationType::Critical, "Cannot make an empty frame.", context);
378  throw InvalidDataException();
379  }
380  if(m_frame.isEncrypted()) {
381  m_frame.addNotification(NotificationType::Critical, "Cannot make an encrypted frame (isn't supported by this tagging library).", context);
382  throw InvalidDataException();
383  }
384  if(m_frame.hasPaddingReached()) {
385  m_frame.addNotification(NotificationType::Critical, "Cannot make a frame which is marked as padding.", context);
386  throw InvalidDataException();
387  }
388  if(version < 3 && m_frame.isCompressed()) {
389  m_frame.addNotification(NotificationType::Warning, "Compression is not supported by the version of ID3v2 and won't be applied.", context);
390  }
391  if(version < 3 && (m_frame.flag() || m_frame.group())) {
392  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);
393  }
394 
395  // convert frame ID if necessary
396  if(version >= 3) {
397  if(Id3v2FrameIds::isShortId(m_frameId)) {
398  // try to convert the short frame ID to its long equivalent
399  if(!(m_frameId = Id3v2FrameIds::convertToLongId(m_frameId))) {
400  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);
401  throw InvalidDataException();
402  }
403  }
404  } else {
405  if(Id3v2FrameIds::isLongId(m_frameId)) {
406  // try to convert the long frame ID to its short equivalent
407  if(!(m_frameId = Id3v2FrameIds::convertToShortId(m_frameId))) {
408  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);
409  throw InvalidDataException();
410  }
411  }
412  }
413 
414  // make actual data depending on the frame ID
415  try {
416  if(Id3v2FrameIds::isTextFrame(m_frameId)) {
417  // it is a text frame
418  if((version >= 3 && (m_frameId == Id3v2FrameIds::lTrackPosition || m_frameId == Id3v2FrameIds::lDiskPosition))
419  || (version < 3 && m_frameId == Id3v2FrameIds::sTrackPosition)) {
420  // track number or the disk number frame
421  m_frame.makeString(m_data, m_decompressedSize, m_frame.value().toString(), TagTextEncoding::Latin1);
422  } else if((version >= 3 && m_frameId == Id3v2FrameIds::lLength)
423  || (version < 3 && m_frameId == Id3v2FrameIds::sLength)) {
424  // length frame
425  m_frame.makeString(m_data, m_decompressedSize, ConversionUtilities::numberToString(m_frame.value().toTimeSpan().totalMilliseconds()), TagTextEncoding::Latin1);
426  } else if(m_frame.value().type() == TagDataType::StandardGenreIndex && ((version >= 3 && m_frameId == Id3v2FrameIds::lGenre)
427  || (version < 3 && m_frameId == Id3v2FrameIds::sGenre))) {
428  // pre-defined genre frame
429  m_frame.makeString(m_data, m_decompressedSize, ConversionUtilities::numberToString(m_frame.value().toStandardGenreIndex()), TagTextEncoding::Latin1);
430  } else {
431  // any other text frame
432  if(version <= 3 && m_frame.value().dataEncoding() == TagTextEncoding::Utf8) {
433  // UTF-8 is only supported by ID3v2.4, so convert back to UTF-16
434  m_frame.makeString(m_data, m_decompressedSize, m_frame.value().toString(TagTextEncoding::Utf16LittleEndian), TagTextEncoding::Utf16LittleEndian);
435  } else {
436  // just keep encoding of the assigned value
437  m_frame.makeString(m_data, m_decompressedSize, m_frame.value().toString(), m_frame.value().dataEncoding());
438  }
439  }
440 
441  } else if((version >= 3 && m_frameId == Id3v2FrameIds::lCover) || (version < 3 && m_frameId == Id3v2FrameIds::sCover)) {
442  // picture frame
443  m_frame.makePictureConsideringVersion(m_data, m_decompressedSize, m_frame.value(), m_frame.isTypeInfoAssigned() ? m_frame.typeInfo() : 0, version);
444 
445  } else if(((version >= 3 && m_frameId == Id3v2FrameIds::lComment)
446  || (version < 3 && m_frameId == Id3v2FrameIds::sComment))
447  || ((version >= 3 && m_frameId == Id3v2FrameIds::lUnsynchronizedLyrics)
448  || (version < 3 && m_frameId == Id3v2FrameIds::sUnsynchronizedLyrics))) {
449  // the comment frame or the unsynchronized lyrics frame
450  m_frame.makeCommentConsideringVersion(m_data, m_decompressedSize, m_frame.value(), version);
451 
452  } else {
453  // an unknown frame
454  m_data = make_unique<char[]>(m_decompressedSize = m_frame.value().dataSize());
455  copy(m_frame.value().dataPointer(), m_frame.value().dataPointer() + m_decompressedSize, m_data.get());
456  }
457  } catch(const ConversionException &) {
458  m_frame.addNotification(NotificationType::Critical, "Assigned value can not be converted appropriately.", context);
459  throw InvalidDataException();
460  }
461 
462  // apply compression if frame should be compressed
463  if(version >= 3 && m_frame.isCompressed()) {
464  m_dataSize = compressBound(m_decompressedSize);
465  auto compressedData = make_unique<char[]>(m_decompressedSize);
466  switch(compress(reinterpret_cast<Bytef *>(compressedData.get()), reinterpret_cast<uLongf *>(&m_dataSize), reinterpret_cast<Bytef *>(m_data.get()), m_decompressedSize)) {
467  case Z_MEM_ERROR:
468  m_frame.addNotification(NotificationType::Critical, "Decompressing failed. The source buffer was too small.", context);
469  throw InvalidDataException();
470  case Z_BUF_ERROR:
471  m_frame.addNotification(NotificationType::Critical, "Decompressing failed. The destination buffer was too small.", context);
472  throw InvalidDataException();
473  case Z_OK:
474  ;
475  }
476  m_data.swap(compressedData);
477  } else {
478  m_dataSize = m_decompressedSize;
479  }
480 
481  // calculate required size
482  // -> data size
483  m_requiredSize = m_dataSize;
484  if(version < 3) {
485  // -> header size
486  m_requiredSize += 6;
487  } else {
488  // -> header size
489  m_requiredSize += 10;
490  // -> group byte
491  if(m_frame.hasGroupInformation()) {
492  m_requiredSize += 1;
493  }
494  // -> decompressed size
495  if(version >= 3 && m_frame.isCompressed()) {
496  m_requiredSize += 4;
497  }
498  }
499 }
500 
508 void Id3v2FrameMaker::make(BinaryWriter &writer)
509 {
510  if(m_version < 3) {
511  writer.writeUInt24BE(m_frameId);
512  writer.writeUInt24BE(m_dataSize);
513  } else {
514  writer.writeUInt32BE(m_frameId);
515  if(m_version >= 4) {
516  writer.writeSynchsafeUInt32BE(m_dataSize);
517  } else {
518  writer.writeUInt32BE(m_dataSize);
519  }
520  writer.writeUInt16BE(m_frame.flag());
521  if(m_frame.hasGroupInformation()) {
522  writer.writeByte(m_frame.group());
523  }
524  if(m_version >= 3 && m_frame.isCompressed()) {
525  if(m_version >= 4) {
526  writer.writeSynchsafeUInt32BE(m_decompressedSize);
527  } else {
528  writer.writeUInt32BE(m_decompressedSize);
529  }
530  }
531  }
532  writer.write(m_data.get(), m_dataSize);
533 }
534 
542 {
543  switch(textEncodingByte) {
551  return TagTextEncoding::Utf8;
552  default:
553  addNotification(NotificationType::Warning, "The charset of the frame is invalid. Latin-1 will be used.", "parsing encoding of frame " + frameIdString());
555  }
556 }
557 
562 {
563  switch(textEncoding) {
572  default:
573  return 0;
574  }
575 }
576 
591 tuple<const char *, size_t, const char *> Id3v2Frame::parseSubstring(const char *buffer, std::size_t bufferSize, TagTextEncoding &encoding, bool addWarnings)
592 {
593  tuple<const char *, size_t, const char *> res(buffer, 0, buffer + bufferSize);
594  switch(encoding) {
597  case TagTextEncoding::Utf8: {
598  if((bufferSize >= 3) && (ConversionUtilities::BE::toUInt24(buffer) == 0x00EFBBBF)) {
599  if(encoding == TagTextEncoding::Latin1) {
600  addNotification(NotificationType::Critical, "Denoted character set is Latin-1 but an UTF-8 BOM is present - assuming UTF-8.", "parsing frame " + frameIdString());
601  encoding = TagTextEncoding::Utf8;
602  }
603  get<0>(res) += 3;
604  }
605  const char *pos = get<0>(res);
606  for(; *pos != 0x00; ++pos) {
607  if(pos < get<2>(res)) {
608  ++get<1>(res);
609  } else {
610  if(addWarnings) {
611  addNotification(NotificationType::Warning, "String in frame is not terminated properly.", "parsing termination of frame " + frameIdString());
612  }
613  break;
614  }
615  }
616  get<2>(res) = pos + 1;
617  break;
618  }
621  if(bufferSize >= 2) {
622  switch(ConversionUtilities::LE::toUInt16(buffer)) {
623  case 0xFEFF:
624  if(encoding == TagTextEncoding::Utf16BigEndian) {
625  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());
627  }
628  get<0>(res) += 2;
629  break;
630  case 0xFFFE:
632  get<0>(res) += 2;
633  }
634  }
635  const uint16 *pos = reinterpret_cast<const uint16 *>(get<0>(res));
636  for(; *pos != 0x0000; ++pos) {
637  if(pos < reinterpret_cast<const uint16 *>(get<2>(res))) {
638  get<1>(res) += 2;
639  } else {
640  if(addWarnings) {
641  addNotification(NotificationType::Warning, "Wide string in frame is not terminated properly.", "parsing termination of frame " + frameIdString());
642  }
643  break;
644  }
645  }
646  get<2>(res) = reinterpret_cast<const char *>(pos + 1);
647  break;
648  }
649  }
650  return res;
651 }
652 
658 string Id3v2Frame::parseString(const char *buffer, size_t dataSize, TagTextEncoding &encoding, bool addWarnings)
659 {
660  auto substr = parseSubstring(buffer, dataSize, encoding, addWarnings);
661  return string(get<0>(substr), get<1>(substr));
662 }
663 
671 u16string Id3v2Frame::parseWideString(const char *buffer, size_t dataSize, TagTextEncoding &encoding, bool addWarnings)
672 {
673  auto substr = parseSubstring(buffer, dataSize, encoding, addWarnings);
674  u16string res(reinterpret_cast<u16string::const_pointer>(get<0>(substr)), get<1>(substr) / 2);
675  TagValue::ensureHostByteOrder(res, encoding);
676  return res;
677 }
678 
688 void Id3v2Frame::parseBom(const char *buffer, size_t maxSize, TagTextEncoding &encoding)
689 {
690  switch(encoding) {
693  if((maxSize >= 2) && (ConversionUtilities::BE::toUInt16(buffer) == 0xFFFE)) {
695  } else if((maxSize >= 2) && (ConversionUtilities::BE::toUInt16(buffer) == 0xFEFF)) {
697  }
698  break;
699  default:
700  if((maxSize >= 3) && (ConversionUtilities::BE::toUInt24(buffer) == 0x00EFBBBF)) {
701  encoding = TagTextEncoding::Utf8;
702  addNotification(NotificationType::Warning, "UTF-8 byte order mark found in text frame.", "parsing byte oder mark of frame " + frameIdString());
703  }
704  }
705 }
706 
714 void Id3v2Frame::parseLegacyPicture(const char *buffer, std::size_t maxSize, TagValue &tagValue, byte &typeInfo)
715 {
716  static const string context("parsing ID3v2.2 picture frame");
717  if(maxSize < 6) {
718  addNotification(NotificationType::Critical, "Picture frame is incomplete.", context);
719  throw TruncatedDataException();
720  }
721  const char *end = buffer + maxSize;
722  auto dataEncoding = parseTextEncodingByte(*buffer); // the first byte stores the encoding
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 and allocate buffer
819  if(!data) {
820  dataSize = 0;
821  }
822  char *bufferDataAddress;
823  switch(encoding) {
826  case TagTextEncoding::Unspecified: // assumption
827  // allocate buffer
828  buffer = make_unique<char[]>(bufferSize = 1 + dataSize + 1);
829  buffer[0] = makeTextEncodingByte(encoding); // set text encoding byte
830  bufferDataAddress = buffer.get() + 1;
831  break;
834  // allocate buffer
835  buffer = make_unique<char[]>(bufferSize = 1 + 2 + dataSize + 2);
836  buffer[0] = makeTextEncodingByte(encoding); // set text encoding byte
837  ConversionUtilities::LE::getBytes(encoding == TagTextEncoding::Utf16LittleEndian ? static_cast<uint16>(0xFEFF) : static_cast<uint16>(0xFFFE), buffer.get() + 1);
838  bufferDataAddress = buffer.get() + 3;
839  break;
840  default:
841  return;
842  }
843 
844  // write string data
845  if(dataSize) {
846  copy(data, data + dataSize, bufferDataAddress);
847  }
848 }
849 
854 size_t Id3v2Frame::makeBom(char *buffer, TagTextEncoding encoding)
855 {
856  switch(encoding) {
858  ConversionUtilities::LE::getBytes(static_cast<uint16>(0xFEFF), buffer);
859  return 2;
861  ConversionUtilities::BE::getBytes(static_cast<uint16>(0xFEFF), buffer);
862  return 2;
863  default:
864  return 0;
865  }
866 }
867 
871 void Id3v2Frame::makeLegacyPicture(unique_ptr<char[]> &buffer, uint32 &bufferSize, const TagValue &picture, byte typeInfo)
872 {
873  // determine description
874  TagTextEncoding descriptionEncoding = picture.descriptionEncoding();
875  StringData convertedDescription;
876  string::size_type descriptionSize = picture.description().find("\0\0", 0, descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? 2 : 1);
877  if(descriptionSize == string::npos) {
878  descriptionSize = picture.description().size();
879  }
880  if(descriptionEncoding == TagTextEncoding::Utf8) {
881  // UTF-8 is only supported by ID3v2.4, so convert back to UTF-16
882  descriptionEncoding = TagTextEncoding::Utf16LittleEndian;
883  convertedDescription = convertUtf8ToUtf16LE(picture.description().data(), descriptionSize);
884  descriptionSize = convertedDescription.second;
885  }
886  // calculate needed buffer size and create buffer
887  const uint32 dataSize = picture.dataSize();
888  buffer = make_unique<char[]>(bufferSize = 1 + 3 + 1 + descriptionSize + (descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? 4 : 1) + dataSize);
889  // note: encoding byte + image format + picture type byte + description size + 1 or 2 null bytes (depends on encoding) + data size
890  char *offset = buffer.get();
891  // write encoding byte
892  *offset = makeTextEncodingByte(descriptionEncoding);
893  // write mime type
894  const char *imageFormat;
895  if(picture.mimeType() == "image/jpeg") {
896  imageFormat = "JPG";
897  } else if(picture.mimeType() == "image/png") {
898  imageFormat = "PNG";
899  } else if(picture.mimeType() == "image/gif") {
900  imageFormat = "GIF";
901  } else if(picture.mimeType() == "-->") {
902  imageFormat = picture.mimeType().data();
903  } else {
904  imageFormat = "UND";
905  }
906  strncpy(++offset, imageFormat, 3);
907  // write picture type
908  *(offset += 3) = typeInfo;
909  // write description
910  offset += makeBom(offset + 1, descriptionEncoding);
911  if(convertedDescription.first) {
912  copy(convertedDescription.first.get(), convertedDescription.first.get() + descriptionSize, ++offset);
913  } else {
914  picture.description().copy(++offset, descriptionSize);
915  }
916  *(offset += descriptionSize) = 0x00; // terminate description and increase data offset
917  if(descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian) {
918  *(++offset) = 0x00;
919  }
920  // write actual data
921  copy(picture.dataPointer(), picture.dataPointer() + picture.dataSize(), ++offset);
922 }
923 
927 void Id3v2Frame::makePicture(unique_ptr<char[]> &buffer, uint32 &bufferSize, const TagValue &picture, byte typeInfo)
928 {
929  makePictureConsideringVersion(buffer, bufferSize, picture, typeInfo, 3);
930 }
931 
935 void Id3v2Frame::makePictureConsideringVersion(std::unique_ptr<char[]> &buffer, uint32 &bufferSize, const TagValue &picture, byte typeInfo, byte version)
936 {
937  if(version < 3) {
938  makeLegacyPicture(buffer, bufferSize, picture, typeInfo);
939  return;
940  }
941 
942  // determine description
943  TagTextEncoding descriptionEncoding = picture.descriptionEncoding();
944  StringData convertedDescription;
945  string::size_type descriptionSize = picture.description().find("\0\0", 0, descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? 2 : 1);
946  if(descriptionSize == string::npos) {
947  descriptionSize = picture.description().size();
948  }
949  if(version < 4 && descriptionEncoding == TagTextEncoding::Utf8) {
950  // UTF-8 is only supported by ID3v2.4, so convert back to UTF-16
951  descriptionEncoding = TagTextEncoding::Utf16LittleEndian;
952  convertedDescription = convertUtf8ToUtf16LE(picture.description().data(), descriptionSize);
953  descriptionSize = convertedDescription.second;
954  }
955  // determine mime-type
956  string::size_type mimeTypeSize = picture.mimeType().find('\0');
957  if(mimeTypeSize == string::npos) {
958  mimeTypeSize = picture.mimeType().length();
959  }
960  // calculate needed buffer size and create buffer
961  const uint32 dataSize = picture.dataSize();
962  buffer = make_unique<char[]>(bufferSize = 1 + mimeTypeSize + 1 + 1 + descriptionSize + (descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? 4 : 1) + dataSize);
963  // note: encoding byte + mime type size + 0 byte + picture type byte + description size + 1 or 4 null bytes (depends on encoding) + data size
964  char *offset = buffer.get();
965  // write encoding byte
966  *offset = makeTextEncodingByte(descriptionEncoding);
967  // write mime type
968  picture.mimeType().copy(++offset, mimeTypeSize);
969  *(offset += mimeTypeSize) = 0x00; // terminate mime type
970  // write picture type
971  *(++offset) = typeInfo;
972  // write description
973  offset += makeBom(offset + 1, descriptionEncoding);
974  if(convertedDescription.first) {
975  copy(convertedDescription.first.get(), convertedDescription.first.get() + descriptionSize, ++offset);
976  } else {
977  picture.description().copy(++offset, descriptionSize);
978  }
979  *(offset += descriptionSize) = 0x00; // terminate description and increase data offset
980  if(descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian) {
981  *(++offset) = 0x00;
982  }
983  // write actual data
984  copy(picture.dataPointer(), picture.dataPointer() + picture.dataSize(), ++offset);
985 }
986 
990 void Id3v2Frame::makeComment(unique_ptr<char[]> &buffer, uint32 &bufferSize, const TagValue &comment)
991 {
992  makeCommentConsideringVersion(buffer, bufferSize, comment, 3);
993 }
994 
998 void Id3v2Frame::makeCommentConsideringVersion(unique_ptr<char[]> &buffer, uint32 &bufferSize, const TagValue &comment, byte version)
999 {
1000  static const string context("making comment frame");
1001  // check type and other values are valid
1002  TagTextEncoding encoding = comment.dataEncoding();
1003  if(!comment.description().empty() && encoding != comment.descriptionEncoding()) {
1004  addNotification(NotificationType::Critical, "Data enoding and description encoding aren't equal.", context);
1005  throw InvalidDataException();
1006  }
1007  const string &lng = comment.language();
1008  if(lng.length() > 3) {
1009  addNotification(NotificationType::Critical, "The language must be 3 bytes long (ISO-639-2).", context);
1010  throw InvalidDataException();
1011  }
1012  StringData convertedDescription;
1013  string::size_type descriptionSize = comment.description().find("\0\0", 0, encoding == TagTextEncoding::Utf16BigEndian || encoding == TagTextEncoding::Utf16LittleEndian ? 2 : 1);
1014  if(descriptionSize == string::npos) {
1015  descriptionSize = comment.description().size();
1016  }
1017  if(version < 4 && encoding == TagTextEncoding::Utf8) {
1018  // UTF-8 is only supported by ID3v2.4, so convert back to UTF-16
1020  convertedDescription = convertUtf8ToUtf16LE(comment.description().data(), descriptionSize);
1021  descriptionSize = convertedDescription.second;
1022  }
1023  // calculate needed buffer size and create buffer
1024  const auto data = comment.toString(encoding);
1025  buffer = make_unique<char[]>(bufferSize = 1 + 3 + descriptionSize + data.size() + (encoding == TagTextEncoding::Utf16BigEndian || encoding == TagTextEncoding::Utf16LittleEndian ? 6 : 1) + data.size());
1026  // note: encoding byte + language + description size + actual data size + BOMs and termination
1027  char *offset = buffer.get();
1028  // write encoding
1029  *offset = makeTextEncodingByte(encoding);
1030  // write language
1031  for(unsigned int i = 0; i < 3; ++i) {
1032  *(++offset) = (lng.length() > i) ? lng[i] : 0x00;
1033  }
1034  // write description
1035  offset += makeBom(offset + 1, encoding);
1036  if(convertedDescription.first) {
1037  copy(convertedDescription.first.get(), convertedDescription.first.get() + descriptionSize, ++offset);
1038  } else {
1039  comment.description().copy(++offset, descriptionSize);
1040  }
1041  offset += descriptionSize;
1042  *offset = 0x00; // terminate description and increase data offset
1044  *(++offset) = 0x00;
1045  }
1046  // write actual data
1047  offset += makeBom(offset + 1, encoding);
1048  data.copy(++offset, data.size());
1049 }
1050 
1051 }
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:400
void invalidateStatus()
Invalidates the current status.
void setTypeInfo(const typeInfoType &typeInfo)
Sets the type info of the current TagField.
void cleared()
Ensures the field is cleared.
Definition: id3v2frame.cpp:346
Id3v2FrameMaker prepareMaking(const uint32 version)
Prepares making.
Definition: id3v2frame.cpp:325
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:803
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:508
char * dataPointer() const
Returns a pointer to the raw data assigned to the current instance.
Definition: tagvalue.h:376
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:591
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:935
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:116
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:658
STL namespace.
void assignStandardGenreIndex(int index)
Assigns the given standard genre index to be assigned.
Definition: tagvalue.cpp:634
void addNotification(const Notification &notification)
This 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:714
byte group() const
Returns the group.
Definition: id3v2frame.h:305
TagTextEncoding
Specifies the text encoding.
Definition: tagvalue.h:22
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:541
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:292
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
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:561
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:338
TagTextEncoding descriptionEncoding() const
Returns the description encoding.
Definition: tagvalue.h:489
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:279
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:648
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:927
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:671
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:854
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:871
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: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:688
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.
Definition: id3v2frame.cpp:998
void setLanguage(const std::string &value)
Sets the language.
Definition: tagvalue.h:443
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:422
uint16 flag() const
Returns the flags.
Definition: id3v2frame.h:207
const std::string & mimeType() const
Returns the MIME type.
Definition: tagvalue.h:411
void makeComment(std::unique_ptr< char[]> &buffer, uint32 &bufferSize, const TagValue &comment)
Writes the specified comment to the specified buffer.
Definition: id3v2frame.cpp:990
const std::string & description() const
Returns the description.
Definition: tagvalue.h:387
int parseGenreIndex(const stringtype &denotation)
Helper function to parse the genre index.
Definition: id3v2frame.cpp:68
size_t dataSize() const
Returns the size of the assigned value in bytes.
Definition: tagvalue.h:365
TAG_PARSER_EXPORT const char * comment()
void setId(const identifierType &id)
Sets the id of the current Tag Field.
TAG_PARSER_EXPORT const char * version()