Tag Parser  8.2.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 "../diagnostics.h"
6 #include "../exceptions.h"
7 
8 #include <c++utilities/conversion/stringbuilder.h>
9 #include <c++utilities/conversion/stringconversion.h>
10 
11 #include <zlib.h>
12 
13 #include <algorithm>
14 #include <cstring>
15 #include <limits>
16 #include <memory>
17 
18 using namespace std;
19 using namespace ConversionUtilities;
20 using namespace ChronoUtilities;
21 using namespace IoUtilities;
22 
23 namespace TagParser {
24 
25 namespace Id3v2TextEncodingBytes {
27 }
28 
30 constexpr auto maxId3v2FrameDataSize(numeric_limits<uint32>::max() - 15);
31 
40 Id3v2Frame::Id3v2Frame()
41  : m_parsedVersion(0)
42  , m_dataSize(0)
43  , m_totalSize(0)
44  , m_flag(0)
45  , m_group(0)
46  , m_padding(false)
47 {
48 }
49 
53 Id3v2Frame::Id3v2Frame(const IdentifierType &id, const TagValue &value, byte group, uint16 flag)
54  : TagField<Id3v2Frame>(id, value)
55  , m_parsedVersion(0)
56  , m_dataSize(0)
57  , m_totalSize(0)
58  , m_flag(flag)
59  , m_group(group)
60  , m_padding(false)
61 {
62 }
63 
68 template <class stringtype> 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 
109 string stringFromSubstring(tuple<const char *, size_t, const char *> substr)
110 {
111  return string(get<0>(substr), get<1>(substr));
112 }
113 
117 u16string wideStringFromSubstring(tuple<const char *, size_t, const char *> substr, TagTextEncoding encoding)
118 {
119  u16string res(reinterpret_cast<u16string::const_pointer>(get<0>(substr)), get<1>(substr) / 2);
120  TagValue::ensureHostByteOrder(res, encoding);
121  return res;
122 }
123 
134 void Id3v2Frame::parse(BinaryReader &reader, uint32 version, uint32 maximalSize, Diagnostics &diag)
135 {
136  static const string defaultContext("parsing ID3v2 frame");
137  string context;
138 
139  // parse header
140  if (version < 3) {
141  // parse header for ID3v2.1 and ID3v2.2
142  // -> read ID
143  setId(reader.readUInt24BE());
144  if (id() & 0xFFFF0000u) {
145  m_padding = false;
146  } else {
147  // padding reached
148  m_padding = true;
149  throw NoDataFoundException();
150  }
151 
152  // -> update context
153  context = "parsing " % idToString() + " frame";
154 
155  // -> read size, check whether frame is truncated
156  m_dataSize = reader.readUInt24BE();
157  m_totalSize = m_dataSize + 6;
158  if (m_totalSize > maximalSize) {
159  diag.emplace_back(DiagLevel::Warning, "The frame is truncated and will be ignored.", context);
160  throw TruncatedDataException();
161  }
162 
163  // -> no flags/group in ID3v2.2
164  m_flag = 0;
165  m_group = 0;
166 
167  } else {
168  // parse header for ID3v2.3 and ID3v2.4
169  // -> read ID
170  setId(reader.readUInt32BE());
171  if (id() & 0xFF000000u) {
172  m_padding = false;
173  } else {
174  // padding reached
175  m_padding = true;
176  throw NoDataFoundException();
177  }
178 
179  // -> update context
180  context = "parsing " % idToString() + " frame";
181 
182  // -> read size, check whether frame is truncated
183  m_dataSize = version >= 4 ? reader.readSynchsafeUInt32BE() : reader.readUInt32BE();
184  m_totalSize = m_dataSize + 10;
185  if (m_totalSize > maximalSize) {
186  diag.emplace_back(DiagLevel::Warning, "The frame is truncated and will be ignored.", context);
187  throw TruncatedDataException();
188  }
189 
190  // -> read flags and group
191  m_flag = reader.readUInt16BE();
192  m_group = hasGroupInformation() ? reader.readByte() : 0;
193  if (isEncrypted()) {
194  // encryption is not implemented
195  diag.emplace_back(DiagLevel::Critical, "Encrypted frames aren't supported.", context);
197  }
198  }
199 
200  // frame size mustn't be 0
201  if (m_dataSize <= 0) {
202  diag.emplace_back(DiagLevel::Warning, "The frame size is 0.", context);
203  throw InvalidDataException();
204  }
205 
206  // parse the data
207  unique_ptr<char[]> buffer;
208 
209  // -> decompress data if compressed; otherwise just read it
210  if (isCompressed()) {
211  uLongf decompressedSize = version >= 4 ? reader.readSynchsafeUInt32BE() : reader.readUInt32BE();
212  if (decompressedSize < m_dataSize) {
213  diag.emplace_back(DiagLevel::Critical, "The decompressed size is smaller than the compressed size.", context);
214  throw InvalidDataException();
215  }
216  const auto bufferCompressed = make_unique<char[]>(m_dataSize);
217  reader.read(bufferCompressed.get(), m_dataSize);
218  buffer = make_unique<char[]>(decompressedSize);
219  switch (
220  uncompress(reinterpret_cast<Bytef *>(buffer.get()), &decompressedSize, reinterpret_cast<Bytef *>(bufferCompressed.get()), m_dataSize)) {
221  case Z_MEM_ERROR:
222  diag.emplace_back(DiagLevel::Critical, "Decompressing failed. The source buffer was too small.", context);
223  throw InvalidDataException();
224  case Z_BUF_ERROR:
225  diag.emplace_back(DiagLevel::Critical, "Decompressing failed. The destination buffer was too small.", context);
226  throw InvalidDataException();
227  case Z_DATA_ERROR:
228  diag.emplace_back(DiagLevel::Critical, "Decompressing failed. The input data was corrupted or incomplete.", context);
229  throw InvalidDataException();
230  case Z_OK:
231  break;
232  default:
233  diag.emplace_back(DiagLevel::Critical, "Decompressing failed (unknown reason).", context);
234  throw InvalidDataException();
235  }
236  if (decompressedSize > maxId3v2FrameDataSize) {
237  diag.emplace_back(DiagLevel::Critical, "The decompressed data exceeds the maximum supported frame size.", context);
238  throw InvalidDataException();
239  }
240  m_dataSize = static_cast<uint32>(decompressedSize);
241  } else {
242  buffer = make_unique<char[]>(m_dataSize);
243  reader.read(buffer.get(), m_dataSize);
244  }
245 
246  // read tag value depending on frame ID/type
247  if (Id3v2FrameIds::isTextFrame(id())) {
248  // parse text encoding byte
249  TagTextEncoding dataEncoding = parseTextEncodingByte(static_cast<byte>(*buffer.get()), diag);
250 
251  // parse string values (since ID3v2.4 a text frame may contain multiple strings)
252  const char *currentOffset = buffer.get() + 1;
253  for (size_t currentIndex = 1; currentIndex < m_dataSize;) {
254  // determine the next substring
255  const auto substr(parseSubstring(currentOffset, m_dataSize - currentIndex, dataEncoding, false, diag));
256 
257  // handle case when string is empty
258  if (!get<1>(substr)) {
259  if (currentIndex == 1) {
261  }
262  currentIndex = static_cast<size_t>(get<2>(substr) - buffer.get());
263  currentOffset = get<2>(substr);
264  continue;
265  }
266 
267  // determine the TagValue instance to store the value
268  TagValue *const value = [&] {
269  if (this->value().isEmpty()) {
270  return &this->value();
271  }
272  m_additionalValues.emplace_back();
273  return &m_additionalValues.back();
274  }();
275 
276  // apply further parsing for some text frame types (eg. convert track number to PositionInSet)
277  if ((version >= 3 && (id() == Id3v2FrameIds::lTrackPosition || id() == Id3v2FrameIds::lDiskPosition))
279  // parse the track number or the disk number frame
280  try {
281  if (characterSize(dataEncoding) > 1) {
283  } else {
285  }
286  } catch (const ConversionException &) {
287  diag.emplace_back(DiagLevel::Warning, "The value of track/disk position frame is not numeric and will be ignored.", context);
288  }
289 
290  } else if ((version >= 3 && id() == Id3v2FrameIds::lLength) || (version < 3 && id() == Id3v2FrameIds::sLength)) {
291  // parse frame contains length
292  try {
293  const auto milliseconds = [&] {
294  if (dataEncoding == TagTextEncoding::Utf16BigEndian || dataEncoding == TagTextEncoding::Utf16LittleEndian) {
295  const auto parsedStringRef = parseSubstring(buffer.get() + 1, m_dataSize - 1, dataEncoding, false, diag);
296  const auto convertedStringData = dataEncoding == TagTextEncoding::Utf16BigEndian
297  ? convertUtf16BEToUtf8(get<0>(parsedStringRef), get<1>(parsedStringRef))
298  : convertUtf16LEToUtf8(get<0>(parsedStringRef), get<1>(parsedStringRef));
299  return string(convertedStringData.first.get(), convertedStringData.second);
300  } else { // Latin-1 or UTF-8
301  return stringFromSubstring(substr);
302  }
303  }();
304  value->assignTimeSpan(TimeSpan::fromMilliseconds(stringToNumber<double>(milliseconds)));
305  } catch (const ConversionException &) {
306  diag.emplace_back(DiagLevel::Warning, "The value of the length frame is not numeric and will be ignored.", context);
307  }
308 
309  } else if ((version >= 3 && id() == Id3v2FrameIds::lGenre) || (version < 3 && id() == Id3v2FrameIds::sGenre)) {
310  // parse genre/content type
311  const auto genreIndex = [&] {
312  if (characterSize(dataEncoding) > 1) {
313  return parseGenreIndex(wideStringFromSubstring(substr, dataEncoding));
314  } else {
315  return parseGenreIndex(stringFromSubstring(substr));
316  }
317  }();
318  if (genreIndex != -1) {
319  // genre is specified as ID3 genre number
320  value->assignStandardGenreIndex(genreIndex);
321  } else {
322  // genre is specified as string
323  value->assignData(get<0>(substr), get<1>(substr), TagDataType::Text, dataEncoding);
324  }
325  } else {
326  // store any other text frames as-is
327  value->assignData(get<0>(substr), get<1>(substr), TagDataType::Text, dataEncoding);
328  }
329 
330  currentIndex = static_cast<size_t>(get<2>(substr) - buffer.get());
331  currentOffset = get<2>(substr);
332  }
333 
334  // add warning about additional values
335  if (version < 4 && !m_additionalValues.empty()) {
336  diag.emplace_back(
337  DiagLevel::Warning, "Multiple strings found though the tag is pre-ID3v2.4. " + ignoreAdditionalValuesDiagMsg(), context);
338  }
339 
340  } else if (version >= 3 && id() == Id3v2FrameIds::lCover) {
341  // parse picture frame
342  byte type;
343  parsePicture(buffer.get(), m_dataSize, value(), type, diag);
344  setTypeInfo(type);
345 
346  } else if (version < 3 && id() == Id3v2FrameIds::sCover) {
347  // parse legacy picutre
348  byte type;
349  parseLegacyPicture(buffer.get(), m_dataSize, value(), type, diag);
350  setTypeInfo(type);
351 
352  } else if (((version >= 3 && id() == Id3v2FrameIds::lComment) || (version < 3 && id() == Id3v2FrameIds::sComment))
354  // parse comment frame or unsynchronized lyrics frame (these two frame types have the same structure)
355  parseComment(buffer.get(), m_dataSize, value(), diag);
356 
357  } else {
358  // parse unknown/unsupported frame
359  value().assignData(buffer.get(), m_dataSize, TagDataType::Undefined);
360  }
361 }
362 
374 {
375  return Id3v2FrameMaker(*this, version, diag);
376 }
377 
386 void Id3v2Frame::make(BinaryWriter &writer, byte version, Diagnostics &diag)
387 {
388  prepareMaking(version, diag).make(writer);
389 }
390 
394 void Id3v2Frame::reset()
395 {
396  m_flag = 0;
397  m_group = 0;
398  m_parsedVersion = 0;
399  m_dataSize = 0;
400  m_totalSize = 0;
401  m_padding = false;
402  m_additionalValues.clear();
403 }
404 
408 std::string Id3v2Frame::ignoreAdditionalValuesDiagMsg() const
409 {
410  if (m_additionalValues.size() == 1) {
411  return argsToString("Additional value \"", m_additionalValues.front().toString(TagTextEncoding::Utf8), "\" is supposed to be ignored.");
412  }
413  return argsToString("Additional values ", DiagMessage::formatList(TagValue::toStrings(m_additionalValues)), " are supposed to be ignored.");
414 }
415 
427 Id3v2FrameMaker::Id3v2FrameMaker(Id3v2Frame &frame, byte version, Diagnostics &diag)
428  : m_frame(frame)
429  , m_frameId(m_frame.id())
430  , m_version(version)
431 {
432  const string context("making " % m_frame.idToString() + " frame");
433 
434  // validate frame's configuration
435  if (m_frame.isEncrypted()) {
436  diag.emplace_back(DiagLevel::Critical, "Cannot make an encrypted frame (isn't supported by this tagging library).", context);
437  throw InvalidDataException();
438  }
439  if (m_frame.hasPaddingReached()) {
440  diag.emplace_back(DiagLevel::Critical, "Cannot make a frame which is marked as padding.", context);
441  throw InvalidDataException();
442  }
443  if (version < 3 && m_frame.isCompressed()) {
444  diag.emplace_back(DiagLevel::Warning, "Compression is not supported by the version of ID3v2 and won't be applied.", context);
445  }
446  if (version < 3 && (m_frame.flag() || m_frame.group())) {
447  diag.emplace_back(DiagLevel::Warning,
448  "The existing flag and group information is not supported by the version of ID3v2 and will be ignored/discarted.", context);
449  }
450 
451  // get non-empty, assigned values
452  vector<const TagValue *> values;
453  values.reserve(1 + frame.additionalValues().size());
454  if (!frame.value().isEmpty()) {
455  values.emplace_back(&frame.value());
456  }
457  for (const auto &value : frame.additionalValues()) {
458  if (!value.isEmpty()) {
459  values.emplace_back(&value);
460  }
461  }
462 
463  // validate assigned values
464  if (values.empty()) {
465  throw NoDataProvidedException();
466  // note: This is not really an issue because in the case we're not provided with any value here just means that the field
467  // is supposed to be removed. So don't add any diagnostic messages here.
468  }
469  const bool isTextFrame = Id3v2FrameIds::isTextFrame(m_frameId);
470  if (values.size() != 1) {
471  if (!isTextFrame) {
472  diag.emplace_back(DiagLevel::Critical, "Multiple values are not supported for non-text-frames.", context);
473  throw InvalidDataException();
474  } else if (version < 4) {
475  diag.emplace_back(
476  DiagLevel::Warning, "Multiple strings assigned to pre-ID3v2.4 text frame. " + frame.ignoreAdditionalValuesDiagMsg(), context);
477  }
478  }
479 
480  // convert frame ID if necessary
481  if (version >= 3) {
482  if (Id3v2FrameIds::isShortId(m_frameId)) {
483  // try to convert the short frame ID to its long equivalent
484  if (!(m_frameId = Id3v2FrameIds::convertToLongId(m_frameId))) {
485  diag.emplace_back(DiagLevel::Critical,
486  "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.",
487  context);
488  throw InvalidDataException();
489  }
490  }
491  } else {
492  if (Id3v2FrameIds::isLongId(m_frameId)) {
493  // try to convert the long frame ID to its short equivalent
494  if (!(m_frameId = Id3v2FrameIds::convertToShortId(m_frameId))) {
495  diag.emplace_back(DiagLevel::Critical,
496  "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.",
497  context);
498  throw InvalidDataException();
499  }
500  }
501  }
502 
503  // make actual data depending on the frame ID
504  try {
505  if (isTextFrame) {
506  // make text frame
507  vector<string> substrings;
508  substrings.reserve(1 + frame.additionalValues().size());
510 
511  if ((version >= 3 && (m_frameId == Id3v2FrameIds::lTrackPosition || m_frameId == Id3v2FrameIds::lDiskPosition))
512  || (version < 3 && (m_frameId == Id3v2FrameIds::sTrackPosition || m_frameId == Id3v2FrameIds::sDiskPosition))) {
513  // make track number or disk number frame
515  for (const auto *const value : values) {
516  // convert the position to string
517  substrings.emplace_back(value->toString(encoding));
518  // warn if value is no valid position (although we just store a string after all)
520  continue;
521  }
522  try {
524  } catch (const ConversionException &) {
525  diag.emplace_back(DiagLevel::Warning,
526  argsToString("The track/disk number \"", substrings.back(), "\" is not of the expected form, eg. \"4/10\"."), context);
527  }
528  }
529 
530  } else if ((version >= 3 && m_frameId == Id3v2FrameIds::lLength) || (version < 3 && m_frameId == Id3v2FrameIds::sLength)) {
531  // make length frame
532  encoding = TagTextEncoding::Latin1;
533  for (const auto *const value : values) {
534  const auto duration(value->toTimeSpan());
535  if (duration.isNegative()) {
536  diag.emplace_back(DiagLevel::Critical, argsToString("Assigned duration \"", duration.toString(), "\" is negative."), context);
537  throw InvalidDataException();
538  }
539  substrings.emplace_back(ConversionUtilities::numberToString(static_cast<uint64>(duration.totalMilliseconds())));
540  }
541 
542  } else {
543  // make standard genre index and other text frames
544  // -> find text encoding suitable for all assigned values
545  for (const auto *const value : values) {
546  switch (encoding) {
548  switch (value->type()) {
550  encoding = TagTextEncoding::Latin1;
551  break;
552  default:
553  encoding = value->dataEncoding();
554  }
555  break;
557  switch (value->dataEncoding()) {
559  break;
560  default:
561  encoding = value->dataEncoding();
562  }
563  break;
564  default:;
565  }
566  }
567  if (version <= 3 && encoding == TagTextEncoding::Utf8) {
569  }
570  // -> format values
571  for (const auto *const value : values) {
573  && ((version >= 3 && m_frameId == Id3v2FrameIds::lGenre) || (version < 3 && m_frameId == Id3v2FrameIds::sGenre))) {
574  // make standard genere index
575  substrings.emplace_back(ConversionUtilities::numberToString(value->toStandardGenreIndex()));
576 
577  } else {
578  // make other text frame
579  substrings.emplace_back(value->toString(encoding));
580  }
581  }
582  }
583 
584  // concatenate substrings using encoding specific byte order mark and termination
585  const auto terminationLength = (encoding == TagTextEncoding::Utf16BigEndian || encoding == TagTextEncoding::Utf16LittleEndian) ? 2u : 1u;
586  const auto byteOrderMark = [&] {
587  switch (encoding) {
589  return string({ '\xFF', '\xFE' });
591  return string({ '\xFE', '\xFF' });
592  default:
593  return string();
594  }
595  }();
596  const auto concatenatedSubstrings = joinStrings(substrings, string(), false, byteOrderMark, string(terminationLength, '\0'));
597 
598  // write text encoding byte and concatenated strings to data buffer
599  m_data = make_unique<char[]>(m_decompressedSize = static_cast<uint32>(1 + concatenatedSubstrings.size()));
600  m_data[0] = static_cast<char>(Id3v2Frame::makeTextEncodingByte(encoding));
601  concatenatedSubstrings.copy(&m_data[1], concatenatedSubstrings.size());
602 
603  } else if ((version >= 3 && m_frameId == Id3v2FrameIds::lCover) || (version < 3 && m_frameId == Id3v2FrameIds::sCover)) {
604  // make picture frame
605  m_frame.makePicture(m_data, m_decompressedSize, *values.front(), m_frame.isTypeInfoAssigned() ? m_frame.typeInfo() : 0, version);
606 
607  } else if (((version >= 3 && m_frameId == Id3v2FrameIds::lComment) || (version < 3 && m_frameId == Id3v2FrameIds::sComment))
608  || ((version >= 3 && m_frameId == Id3v2FrameIds::lUnsynchronizedLyrics)
609  || (version < 3 && m_frameId == Id3v2FrameIds::sUnsynchronizedLyrics))) {
610  // make comment frame or the unsynchronized lyrics frame
611  m_frame.makeComment(m_data, m_decompressedSize, *values.front(), version, diag);
612 
613  } else {
614  // make unknown frame
615  const auto &value(*values.front());
617  diag.emplace_back(DiagLevel::Critical, "Assigned value exceeds maximum size.", context);
618  throw InvalidDataException();
619  }
620  m_data = make_unique<char[]>(m_decompressedSize = static_cast<uint32>(value.dataSize()));
621  copy(value.dataPointer(), value.dataPointer() + m_decompressedSize, m_data.get());
622  }
623  } catch (const ConversionException &) {
624  try {
625  const auto valuesAsString = TagValue::toStrings(values);
626  diag.emplace_back(DiagLevel::Critical,
627  argsToString("Assigned value(s) \"", DiagMessage::formatList(valuesAsString), "\" can not be converted appropriately."), context);
628  } catch (const ConversionException &) {
629  diag.emplace_back(DiagLevel::Critical, "Assigned value(s) can not be converted appropriately.", context);
630  }
631  throw InvalidDataException();
632  }
633 
634  // apply compression if frame should be compressed
635  if (version >= 3 && m_frame.isCompressed()) {
636  auto compressedSize = compressBound(m_decompressedSize);
637  auto compressedData = make_unique<char[]>(compressedSize);
638  switch (compress(reinterpret_cast<Bytef *>(compressedData.get()), reinterpret_cast<uLongf *>(&compressedSize),
639  reinterpret_cast<Bytef *>(m_data.get()), m_decompressedSize)) {
640  case Z_MEM_ERROR:
641  diag.emplace_back(DiagLevel::Critical, "Decompressing failed. The source buffer was too small.", context);
642  throw InvalidDataException();
643  case Z_BUF_ERROR:
644  diag.emplace_back(DiagLevel::Critical, "Decompressing failed. The destination buffer was too small.", context);
645  throw InvalidDataException();
646  case Z_OK:;
647  }
648  if (compressedSize > maxId3v2FrameDataSize) {
649  diag.emplace_back(DiagLevel::Critical, "Compressed size exceeds maximum data size.", context);
650  throw InvalidDataException();
651  }
652  m_data.swap(compressedData);
653  m_dataSize = static_cast<uint32>(compressedSize);
654  } else {
655  m_dataSize = m_decompressedSize;
656  }
657 
658  // calculate required size
659  // -> data size
660  m_requiredSize = m_dataSize;
661  if (version < 3) {
662  // -> header size
663  m_requiredSize += 6;
664  } else {
665  // -> header size
666  m_requiredSize += 10;
667  // -> group byte
668  if (m_frame.hasGroupInformation()) {
669  m_requiredSize += 1;
670  }
671  // -> decompressed size
672  if (version >= 3 && m_frame.isCompressed()) {
673  m_requiredSize += 4;
674  }
675  }
676 }
677 
685 void Id3v2FrameMaker::make(BinaryWriter &writer)
686 {
687  if (m_version < 3) {
688  writer.writeUInt24BE(m_frameId);
689  writer.writeUInt24BE(m_dataSize);
690  } else {
691  writer.writeUInt32BE(m_frameId);
692  if (m_version >= 4) {
693  writer.writeSynchsafeUInt32BE(m_dataSize);
694  } else {
695  writer.writeUInt32BE(m_dataSize);
696  }
697  writer.writeUInt16BE(m_frame.flag());
698  if (m_frame.hasGroupInformation()) {
699  writer.writeByte(m_frame.group());
700  }
701  if (m_version >= 3 && m_frame.isCompressed()) {
702  if (m_version >= 4) {
703  writer.writeSynchsafeUInt32BE(m_decompressedSize);
704  } else {
705  writer.writeUInt32BE(m_decompressedSize);
706  }
707  }
708  }
709  writer.write(m_data.get(), m_dataSize);
710 }
711 
719 {
720  switch (textEncodingByte) {
728  return TagTextEncoding::Utf8;
729  default:
730  diag.emplace_back(
731  DiagLevel::Warning, "The charset of the frame is invalid. Latin-1 will be used.", "parsing encoding of frame " + idToString());
733  }
734 }
735 
740 {
741  switch (textEncoding) {
750  default:
751  return 0;
752  }
753 }
754 
769 tuple<const char *, size_t, const char *> Id3v2Frame::parseSubstring(
770  const char *buffer, std::size_t bufferSize, TagTextEncoding &encoding, bool addWarnings, Diagnostics &diag)
771 {
772  tuple<const char *, size_t, const char *> res(buffer, 0, buffer + bufferSize);
773  switch (encoding) {
776  case TagTextEncoding::Utf8: {
777  if ((bufferSize >= 3) && (ConversionUtilities::BE::toUInt24(buffer) == 0x00EFBBBF)) {
778  if (encoding == TagTextEncoding::Latin1) {
779  diag.emplace_back(DiagLevel::Critical, "Denoted character set is Latin-1 but an UTF-8 BOM is present - assuming UTF-8.",
780  "parsing frame " + idToString());
781  encoding = TagTextEncoding::Utf8;
782  }
783  get<0>(res) += 3;
784  }
785  const char *pos = get<0>(res);
786  for (; *pos != 0x00; ++pos) {
787  if (pos < get<2>(res)) {
788  ++get<1>(res);
789  } else {
790  if (addWarnings) {
791  diag.emplace_back(
792  DiagLevel::Warning, "String in frame is not terminated properly.", "parsing termination of frame " + idToString());
793  }
794  break;
795  }
796  }
797  get<2>(res) = pos + 1;
798  break;
799  }
802  if (bufferSize >= 2) {
803  switch (ConversionUtilities::LE::toUInt16(buffer)) {
804  case 0xFEFF:
805  if (encoding == TagTextEncoding::Utf16BigEndian) {
806  diag.emplace_back(DiagLevel::Critical,
807  "Denoted character set is UTF-16 Big Endian but UTF-16 Little Endian BOM is present - assuming UTF-16 LE.",
808  "parsing frame " + idToString());
810  }
811  get<0>(res) += 2;
812  break;
813  case 0xFFFE:
815  get<0>(res) += 2;
816  }
817  }
818  const uint16 *pos = reinterpret_cast<const uint16 *>(get<0>(res));
819  for (; *pos != 0x0000; ++pos) {
820  if (pos < reinterpret_cast<const uint16 *>(get<2>(res))) {
821  get<1>(res) += 2;
822  } else {
823  if (addWarnings) {
824  diag.emplace_back(
825  DiagLevel::Warning, "Wide string in frame is not terminated properly.", "parsing termination of frame " + idToString());
826  }
827  break;
828  }
829  }
830  get<2>(res) = reinterpret_cast<const char *>(pos + 1);
831  break;
832  }
833  }
834  return res;
835 }
836 
842 string Id3v2Frame::parseString(const char *buffer, size_t dataSize, TagTextEncoding &encoding, bool addWarnings, Diagnostics &diag)
843 {
844  return stringFromSubstring(parseSubstring(buffer, dataSize, encoding, addWarnings, diag));
845 }
846 
854 u16string Id3v2Frame::parseWideString(const char *buffer, size_t dataSize, TagTextEncoding &encoding, bool addWarnings, Diagnostics &diag)
855 {
856  return wideStringFromSubstring(parseSubstring(buffer, dataSize, encoding, addWarnings, diag), encoding);
857 }
858 
866 void Id3v2Frame::parseBom(const char *buffer, size_t maxSize, TagTextEncoding &encoding, Diagnostics &diag)
867 {
868  switch (encoding) {
871  if ((maxSize >= 2) && (ConversionUtilities::BE::toUInt16(buffer) == 0xFFFE)) {
873  } else if ((maxSize >= 2) && (ConversionUtilities::BE::toUInt16(buffer) == 0xFEFF)) {
875  }
876  break;
877  default:
878  if ((maxSize >= 3) && (ConversionUtilities::BE::toUInt24(buffer) == 0x00EFBBBF)) {
879  encoding = TagTextEncoding::Utf8;
880  diag.emplace_back(DiagLevel::Warning, "UTF-8 byte order mark found in text frame.", "parsing byte oder mark of frame " + idToString());
881  }
882  }
883 }
884 
892 void Id3v2Frame::parseLegacyPicture(const char *buffer, std::size_t maxSize, TagValue &tagValue, byte &typeInfo, Diagnostics &diag)
893 {
894  static const string context("parsing ID3v2.2 picture frame");
895  if (maxSize < 6) {
896  diag.emplace_back(DiagLevel::Critical, "Picture frame is incomplete.", context);
897  throw TruncatedDataException();
898  }
899  const char *end = buffer + maxSize;
900  auto dataEncoding = parseTextEncodingByte(static_cast<byte>(*buffer), diag); // the first byte stores the encoding
901  typeInfo = static_cast<unsigned char>(*(buffer + 4));
902  auto substr = parseSubstring(buffer + 5, static_cast<size_t>(end - 5 - buffer), dataEncoding, true, diag);
903  tagValue.setDescription(string(get<0>(substr), get<1>(substr)), dataEncoding);
904  if (get<2>(substr) >= end) {
905  diag.emplace_back(DiagLevel::Critical, "Picture frame is incomplete (actual data is missing).", context);
906  throw TruncatedDataException();
907  }
908  tagValue.assignData(get<2>(substr), static_cast<size_t>(end - get<2>(substr)), TagDataType::Picture, dataEncoding);
909 }
910 
918 void Id3v2Frame::parsePicture(const char *buffer, std::size_t maxSize, TagValue &tagValue, byte &typeInfo, Diagnostics &diag)
919 {
920  static const string context("parsing ID3v2.3 picture frame");
921  const char *end = buffer + maxSize;
922  auto dataEncoding = parseTextEncodingByte(static_cast<byte>(*buffer), diag); // the first byte stores the encoding
923  auto mimeTypeEncoding = TagTextEncoding::Latin1;
924  auto substr = parseSubstring(buffer + 1, maxSize - 1, mimeTypeEncoding, true, diag);
925  if (get<1>(substr)) {
926  tagValue.setMimeType(string(get<0>(substr), get<1>(substr)));
927  }
928  if (get<2>(substr) >= end) {
929  diag.emplace_back(DiagLevel::Critical, "Picture frame is incomplete (type info, description and actual data are missing).", context);
930  throw TruncatedDataException();
931  }
932  typeInfo = static_cast<unsigned char>(*get<2>(substr));
933  if (++get<2>(substr) >= end) {
934  diag.emplace_back(DiagLevel::Critical, "Picture frame is incomplete (description and actual data are missing).", context);
935  throw TruncatedDataException();
936  }
937  substr = parseSubstring(get<2>(substr), static_cast<size_t>(end - get<2>(substr)), dataEncoding, true, diag);
938  tagValue.setDescription(string(get<0>(substr), get<1>(substr)), dataEncoding);
939  if (get<2>(substr) >= end) {
940  diag.emplace_back(DiagLevel::Critical, "Picture frame is incomplete (actual data is missing).", context);
941  throw TruncatedDataException();
942  }
943  tagValue.assignData(get<2>(substr), static_cast<size_t>(end - get<2>(substr)), TagDataType::Picture, dataEncoding);
944 }
945 
952 void Id3v2Frame::parseComment(const char *buffer, std::size_t dataSize, TagValue &tagValue, Diagnostics &diag)
953 {
954  static const string context("parsing comment/unsynchronized lyrics frame");
955  const char *end = buffer + dataSize;
956  if (dataSize < 5) {
957  diag.emplace_back(DiagLevel::Critical, "Comment frame is incomplete.", context);
958  throw TruncatedDataException();
959  }
960  TagTextEncoding dataEncoding = parseTextEncodingByte(static_cast<byte>(*buffer), diag);
961  if (*(++buffer)) {
962  tagValue.setLanguage(string(buffer, 3));
963  }
964  auto substr = parseSubstring(buffer += 3, dataSize -= 4, dataEncoding, true, diag);
965  tagValue.setDescription(string(get<0>(substr), get<1>(substr)), dataEncoding);
966  if (get<2>(substr) > end) {
967  diag.emplace_back(DiagLevel::Critical, "Comment frame is incomplete (description not terminated?).", context);
968  throw TruncatedDataException();
969  }
970  substr = parseSubstring(get<2>(substr), static_cast<size_t>(end - get<2>(substr)), dataEncoding, false, diag);
971  tagValue.assignData(get<0>(substr), get<1>(substr), TagDataType::Text, dataEncoding);
972 }
973 
983 void Id3v2Frame::makeString(std::unique_ptr<char[]> &buffer, uint32 &bufferSize, const std::string &value, TagTextEncoding encoding)
984 {
985  makeEncodingAndData(buffer, bufferSize, encoding, value.data(), value.size());
986 }
987 
999  std::unique_ptr<char[]> &buffer, uint32 &bufferSize, TagTextEncoding encoding, const char *data, std::size_t dataSize)
1000 {
1001  // calculate buffer size and allocate buffer
1002  if (!data) {
1003  dataSize = 0;
1004  }
1005  if (dataSize > numeric_limits<uint32>::max() - 5) {
1006  throw InvalidDataException();
1007  }
1008  char *bufferDataAddress;
1009  switch (encoding) {
1011  case TagTextEncoding::Utf8:
1012  case TagTextEncoding::Unspecified: // assumption
1013  // allocate buffer
1014  buffer = make_unique<char[]>(bufferSize = static_cast<uint32>(1 + dataSize + 1));
1015  buffer[0] = static_cast<char>(makeTextEncodingByte(encoding)); // set text encoding byte
1016  bufferDataAddress = buffer.get() + 1;
1017  break;
1020  // allocate buffer
1021  buffer = make_unique<char[]>(bufferSize = static_cast<uint32>(1 + 2 + dataSize + 2));
1022  buffer[0] = static_cast<char>(makeTextEncodingByte(encoding)); // set text encoding byte
1023  ConversionUtilities::LE::getBytes(
1024  encoding == TagTextEncoding::Utf16LittleEndian ? static_cast<uint16>(0xFEFF) : static_cast<uint16>(0xFFFE), buffer.get() + 1);
1025  bufferDataAddress = buffer.get() + 3;
1026  break;
1027  default:
1028  return;
1029  }
1030 
1031  // write string data
1032  if (dataSize) {
1033  copy(data, data + dataSize, bufferDataAddress);
1034  }
1035 }
1036 
1041 size_t Id3v2Frame::makeBom(char *buffer, TagTextEncoding encoding)
1042 {
1043  switch (encoding) {
1045  ConversionUtilities::LE::getBytes(static_cast<uint16>(0xFEFF), buffer);
1046  return 2;
1048  ConversionUtilities::BE::getBytes(static_cast<uint16>(0xFEFF), buffer);
1049  return 2;
1050  default:
1051  return 0;
1052  }
1053 }
1054 
1058 void Id3v2Frame::makeLegacyPicture(unique_ptr<char[]> &buffer, uint32 &bufferSize, const TagValue &picture, byte typeInfo)
1059 {
1060  // determine description
1061  TagTextEncoding descriptionEncoding = picture.descriptionEncoding();
1062  StringData convertedDescription;
1063  string::size_type descriptionSize = picture.description().find(
1064  "\0\0", 0, descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? 2 : 1);
1065  if (descriptionSize == string::npos) {
1066  descriptionSize = picture.description().size();
1067  }
1068  if (descriptionEncoding == TagTextEncoding::Utf8) {
1069  // UTF-8 is only supported by ID3v2.4, so convert back to UTF-16
1070  descriptionEncoding = TagTextEncoding::Utf16LittleEndian;
1071  convertedDescription = convertUtf8ToUtf16LE(picture.description().data(), descriptionSize);
1072  descriptionSize = convertedDescription.second;
1073  }
1074 
1075  // calculate needed buffer size and create buffer
1076  // note: encoding byte + image format + picture type byte + description size + 1 or 2 null bytes (depends on encoding) + data size
1077  const auto requiredBufferSize = 1 + 3 + 1 + descriptionSize
1078  + (descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? 4 : 1)
1079  + picture.dataSize();
1080  if (requiredBufferSize > numeric_limits<uint32>::max()) {
1081  throw InvalidDataException();
1082  }
1083  buffer = make_unique<char[]>(bufferSize = static_cast<uint32>(requiredBufferSize));
1084  char *offset = buffer.get();
1085 
1086  // write encoding byte
1087  *offset = static_cast<char>(makeTextEncodingByte(descriptionEncoding));
1088 
1089  // write mime type
1090  const char *imageFormat;
1091  if (picture.mimeType() == "image/jpeg") {
1092  imageFormat = "JPG";
1093  } else if (picture.mimeType() == "image/png") {
1094  imageFormat = "PNG";
1095  } else if (picture.mimeType() == "image/gif") {
1096  imageFormat = "GIF";
1097  } else if (picture.mimeType() == "-->") {
1098  imageFormat = picture.mimeType().data();
1099  } else {
1100  imageFormat = "UND";
1101  }
1102  strncpy(++offset, imageFormat, 3);
1103 
1104  // write picture type
1105  *(offset += 3) = static_cast<char>(typeInfo);
1106 
1107  // write description
1108  offset += makeBom(offset + 1, descriptionEncoding);
1109  if (convertedDescription.first) {
1110  copy(convertedDescription.first.get(), convertedDescription.first.get() + descriptionSize, ++offset);
1111  } else {
1112  picture.description().copy(++offset, descriptionSize);
1113  }
1114  *(offset += descriptionSize) = 0x00; // terminate description and increase data offset
1115  if (descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian) {
1116  *(++offset) = 0x00;
1117  }
1118 
1119  // write actual data
1120  copy(picture.dataPointer(), picture.dataPointer() + picture.dataSize(), ++offset);
1121 }
1122 
1126 void Id3v2Frame::makePicture(std::unique_ptr<char[]> &buffer, uint32 &bufferSize, const TagValue &picture, byte typeInfo, byte version)
1127 {
1128  if (version < 3) {
1129  makeLegacyPicture(buffer, bufferSize, picture, typeInfo);
1130  return;
1131  }
1132 
1133  // determine description
1134  TagTextEncoding descriptionEncoding = picture.descriptionEncoding();
1135  StringData convertedDescription;
1136  string::size_type descriptionSize = picture.description().find(
1137  "\0\0", 0, descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? 2 : 1);
1138  if (descriptionSize == string::npos) {
1139  descriptionSize = picture.description().size();
1140  }
1141  if (version < 4 && descriptionEncoding == TagTextEncoding::Utf8) {
1142  // UTF-8 is only supported by ID3v2.4, so convert back to UTF-16
1143  descriptionEncoding = TagTextEncoding::Utf16LittleEndian;
1144  convertedDescription = convertUtf8ToUtf16LE(picture.description().data(), descriptionSize);
1145  descriptionSize = convertedDescription.second;
1146  }
1147  // determine mime-type
1148  string::size_type mimeTypeSize = picture.mimeType().find('\0');
1149  if (mimeTypeSize == string::npos) {
1150  mimeTypeSize = picture.mimeType().length();
1151  }
1152  // calculate needed buffer size and create buffer
1153  const uint32 dataSize = picture.dataSize();
1154  buffer = make_unique<char[]>(bufferSize = 1 + mimeTypeSize + 1 + 1 + descriptionSize
1155  + (descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? 4 : 1)
1156  + dataSize);
1157  // note: encoding byte + mime type size + 0 byte + picture type byte + description size + 1 or 4 null bytes (depends on encoding) + data size
1158  char *offset = buffer.get();
1159  // write encoding byte
1160  *offset = static_cast<char>(makeTextEncodingByte(descriptionEncoding));
1161  // write mime type
1162  picture.mimeType().copy(++offset, mimeTypeSize);
1163  *(offset += mimeTypeSize) = 0x00; // terminate mime type
1164  // write picture type
1165  *(++offset) = static_cast<char>(typeInfo);
1166  // write description
1167  offset += makeBom(offset + 1, descriptionEncoding);
1168  if (convertedDescription.first) {
1169  copy(convertedDescription.first.get(), convertedDescription.first.get() + descriptionSize, ++offset);
1170  } else {
1171  picture.description().copy(++offset, descriptionSize);
1172  }
1173  *(offset += descriptionSize) = 0x00; // terminate description and increase data offset
1174  if (descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian) {
1175  *(++offset) = 0x00;
1176  }
1177  // write actual data
1178  copy(picture.dataPointer(), picture.dataPointer() + picture.dataSize(), ++offset);
1179 }
1180 
1184 void Id3v2Frame::makeComment(unique_ptr<char[]> &buffer, uint32 &bufferSize, const TagValue &comment, byte version, Diagnostics &diag)
1185 {
1186  static const string context("making comment frame");
1187  // check type and other values are valid
1188  TagTextEncoding encoding = comment.dataEncoding();
1189  if (!comment.description().empty() && encoding != comment.descriptionEncoding()) {
1190  diag.emplace_back(DiagLevel::Critical, "Data enoding and description encoding aren't equal.", context);
1191  throw InvalidDataException();
1192  }
1193  const string &lng = comment.language();
1194  if (lng.length() > 3) {
1195  diag.emplace_back(DiagLevel::Critical, "The language must be 3 bytes long (ISO-639-2).", context);
1196  throw InvalidDataException();
1197  }
1198  StringData convertedDescription;
1199  string::size_type descriptionSize = comment.description().find(
1200  "\0\0", 0, encoding == TagTextEncoding::Utf16BigEndian || encoding == TagTextEncoding::Utf16LittleEndian ? 2 : 1);
1201  if (descriptionSize == string::npos) {
1202  descriptionSize = comment.description().size();
1203  }
1204  if (version < 4 && encoding == TagTextEncoding::Utf8) {
1205  // UTF-8 is only supported by ID3v2.4, so convert back to UTF-16
1207  convertedDescription = convertUtf8ToUtf16LE(comment.description().data(), descriptionSize);
1208  descriptionSize = convertedDescription.second;
1209  }
1210  // calculate needed buffer size and create buffer
1211  const auto data = comment.toString(encoding);
1212  buffer = make_unique<char[]>(bufferSize = 1 + 3 + descriptionSize + data.size()
1213  + (encoding == TagTextEncoding::Utf16BigEndian || encoding == TagTextEncoding::Utf16LittleEndian ? 6 : 1) + data.size());
1214  // note: encoding byte + language + description size + actual data size + BOMs and termination
1215  char *offset = buffer.get();
1216  // write encoding
1217  *offset = static_cast<char>(makeTextEncodingByte(encoding));
1218  // write language
1219  for (unsigned int i = 0; i < 3; ++i) {
1220  *(++offset) = (lng.length() > i) ? lng[i] : 0x00;
1221  }
1222  // write description
1223  offset += makeBom(offset + 1, encoding);
1224  if (convertedDescription.first) {
1225  copy(convertedDescription.first.get(), convertedDescription.first.get() + descriptionSize, ++offset);
1226  } else {
1227  comment.description().copy(++offset, descriptionSize);
1228  }
1229  offset += descriptionSize;
1230  *offset = 0x00; // terminate description and increase data offset
1232  *(++offset) = 0x00;
1233  }
1234  // write actual data
1235  offset += makeBom(offset + 1, encoding);
1236  data.copy(++offset, data.size());
1237 }
1238 
1239 } // namespace TagParser
friend class Id3v2FrameMaker
Definition: id3v2frame.h:88
TagTextEncoding dataEncoding() const
Returns the data encoding.
Definition: tagvalue.h:538
uint16 flag() const
Returns the flags.
Definition: id3v2frame.h:204
The exception that is thrown when the data to be parsed is truncated and therefore can not be parsed ...
Definition: exceptions.h:39
void setTypeInfo(const TypeInfoType &typeInfo)
Sets the type info of the current TagField.
static 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).
static void makeComment(std::unique_ptr< char[]> &buffer, uint32 &bufferSize, const TagValue &comment, byte version, Diagnostics &diag)
Writes the specified comment to the specified buffer.
bool hasGroupInformation() const
Returns whether the frame contains group information.
Definition: id3v2frame.h:277
byte group() const
Returns the group.
Definition: id3v2frame.h:302
u16string wideStringFromSubstring(tuple< const char *, size_t, const char * > substr, TagTextEncoding encoding)
Returns an std::u16string instance for the substring parsed using parseSubstring().
Definition: id3v2frame.cpp:117
constexpr TAG_PARSER_EXPORT const char * version()
TagTextEncoding descriptionEncoding() const
Returns the description encoding.
Definition: tagvalue.h:548
const std::string & description() const
Returns the description.
Definition: tagvalue.h:448
void setMimeType(const std::string &mimeType)
Sets the MIME type.
Definition: tagvalue.h:482
static byte makeTextEncodingByte(TagTextEncoding textEncoding)
Returns a text encoding byte for the specified textEncoding.
Definition: id3v2frame.cpp:739
The exception that is thrown when an operation fails because the detected or specified version is not...
Definition: exceptions.h:53
TagTextEncoding parseTextEncodingByte(byte textEncodingByte, Diagnostics &diag)
Returns the text encoding for the specified textEncodingByte.
Definition: id3v2frame.cpp:718
void make(IoUtilities::BinaryWriter &writer, byte version, Diagnostics &diag)
Writes the frame to a stream using the specified writer and the specified ID3v2 version.
Definition: id3v2frame.cpp:386
std::u16string parseWideString(const char *buffer, std::size_t dataSize, TagTextEncoding &encoding, bool addWarnings, Diagnostics &diag)
Parses a substring from the specified buffer.
Definition: id3v2frame.cpp:854
constexpr bool isLongId(uint32 id)
Returns an indication whether the specified id is a long frame id.
Definition: id3v2frameids.h:71
static std::string formatList(const std::vector< std::string > &values)
Concatenates the specified string values to a list.
Definition: diagnostics.cpp:67
int toStandardGenreIndex() const
Converts the value of the current TagValue object to its equivalent standard genre index.
Definition: tagvalue.cpp:212
uint32 convertToLongId(uint32 id)
Converts the specified short frame ID to the equivalent long frame ID.
constexpr auto maxId3v2FrameDataSize(numeric_limits< uint32 >::max() - 15)
The maximum (supported) size of an ID3v2Frame.
constexpr bool isShortId(uint32 id)
Returns an indication whether the specified id is a short frame id.
Definition: id3v2frameids.h:79
void clearDataAndMetadata()
Wipes assigned data including meta data.
Definition: tagvalue.h:411
std::string parseString(const char *buffer, std::size_t maxSize, TagTextEncoding &encoding, bool addWarnings, Diagnostics &diag)
Parses a substring from the specified buffer.
Definition: id3v2frame.cpp:842
bool isCompressed() const
Returns whether the frame is compressed.
Definition: id3v2frame.h:260
static void makePicture(std::unique_ptr< char[]> &buffer, uint32 &bufferSize, const TagValue &picture, byte typeInfo, byte version)
Writes the specified picture to the specified buffer.
The Id3v2Frame class is used by Id3v2Tag to store the fields.
Definition: id3v2frame.h:86
bool isEmpty() const
Returns an indication whether an value is assigned.
Definition: tagvalue.h:389
const std::string & mimeType() const
Returns the MIME type.
Definition: tagvalue.h:472
void parseLegacyPicture(const char *buffer, std::size_t maxSize, TagValue &tagValue, byte &typeInfo, Diagnostics &diag)
Parses the ID3v2.2 picture from the specified buffer.
Definition: id3v2frame.cpp:892
std::size_t dataSize() const
Returns the size of the assigned value in bytes.
Definition: tagvalue.h:421
void parseBom(const char *buffer, std::size_t maxSize, TagTextEncoding &encoding, Diagnostics &diag)
Parses a byte order mark from the specified buffer.
Definition: id3v2frame.cpp:866
static 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:983
std::string idToString() const
constexpr TAG_PARSER_EXPORT const char * comment()
uint32 convertToShortId(uint32 id)
Converts the specified long frame ID to the equivalent short frame ID.
static 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:998
constexpr bool isTextFrame(uint32 id)
Returns an indication whether the specified id is a text frame id.
Definition: id3v2frameids.h:87
ChronoUtilities::TimeSpan toTimeSpan() const
Converts the value of the current TagValue object to its equivalent TimeSpan representation.
Definition: tagvalue.cpp:291
Contains utility classes helping to read and write streams.
bool isEncrypted() const
Returns whether the frame is encrypted.
Definition: id3v2frame.h:269
void assignPosition(PositionInSet value)
Assigns the given PositionInSet value.
Definition: tagvalue.h:313
PositionInSet toPositionInSet() const
Converts the value of the current TagValue object to its equivalent PositionInSet representation.
Definition: tagvalue.cpp:253
void parseComment(const char *buffer, std::size_t maxSize, TagValue &tagValue, Diagnostics &diag)
Parses the comment/unsynchronized lyrics from the specified buffer.
Definition: id3v2frame.cpp:952
void assignStandardGenreIndex(int index)
Assigns the given standard genre index to be assigned.
Definition: tagvalue.h:344
const TypeInfoType & typeInfo() const
Returns the type info of the current TagField.
The exception that is thrown when the data to be parsed holds no parsable information (e....
Definition: exceptions.h:18
The Id3v2FrameMaker class helps making ID3v2 frames.
Definition: id3v2frame.h:22
void parsePicture(const char *buffer, std::size_t maxSize, TagValue &tagValue, byte &typeInfo, Diagnostics &diag)
Parses the ID3v2.3 picture from the specified buffer.
Definition: id3v2frame.cpp:918
std::tuple< const char *, std::size_t, const char * > parseSubstring(const char *buffer, std::size_t maxSize, TagTextEncoding &encoding, bool addWarnings, Diagnostics &diag)
Parses a substring from the specified buffer.
Definition: id3v2frame.cpp:769
The TagField class is used by FieldMapBasedTag to store the fields.
int parseGenreIndex(const stringtype &denotation)
Helper function to parse the genre index.
Definition: id3v2frame.cpp:68
void setLanguage(const std::string &language)
Sets the language.
Definition: tagvalue.h:502
char * dataPointer()
Returns a pointer to the raw data assigned to the current instance.
Definition: tagvalue.h:432
typename TagFieldTraits< ImplementationType >::IdentifierType IdentifierType
const IdentifierType & id() const
Returns the id of the current TagField.
static std::size_t makeBom(char *buffer, TagTextEncoding encoding)
Writes the BOM for the specified encoding to the specified buffer.
TagDataType type() const
Returns the type of the assigned value.
Definition: tagvalue.h:353
void make(IoUtilities::BinaryWriter &writer)
Saves the frame (specified when constructing the object) using the specified writer.
Definition: id3v2frame.cpp:685
Id3v2Frame()
Constructs a new Id3v2Frame.
Definition: id3v2frame.cpp:40
The exception that is thrown when the data to be parsed or to be made seems invalid and therefore can...
Definition: exceptions.h:25
constexpr TAG_PARSER_EXPORT const char * duration()
void setDescription(const std::string &value, TagTextEncoding encoding=TagTextEncoding::Latin1)
Sets the description.
Definition: tagvalue.h:461
void assignData(const char *data, std::size_t length, TagDataType type=TagDataType::Binary, TagTextEncoding encoding=TagTextEncoding::Latin1)
static std::vector< std::string > toStrings(const ContainerType &values, TagTextEncoding encoding=TagTextEncoding::Utf8)
Converts the specified values to string using the specified encoding.
Definition: tagvalue.h:561
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:365
void assignTimeSpan(ChronoUtilities::TimeSpan value)
Assigns the given TimeSpan value.
Definition: tagvalue.h:326
constexpr int characterSize(TagTextEncoding encoding)
Returns the size of one character for the specified encoding in bytes.
Definition: tagvalue.h:36
The TagValue class wraps values of different types.
Definition: tagvalue.h:65
uint32 dataSize() const
Returns the size of the data stored in the frame in bytes.
Definition: id3v2frame.h:228
Id3v2FrameMaker prepareMaking(byte version, Diagnostics &diag)
Prepares making.
Definition: id3v2frame.cpp:373
TagTextEncoding
Specifies the text encoding.
Definition: tagvalue.h:24
void parse(IoUtilities::BinaryReader &reader, uint32 version, uint32 maximalSize, Diagnostics &diag)
Parses a frame from the stream read using the specified reader.
Definition: id3v2frame.cpp:134
Contains all classes and functions of the TagInfo library.
Definition: aaccodebook.h:9
void setId(const IdentifierType &id)
Sets the id of the current Tag Field.
static void ensureHostByteOrder(std::u16string &u16str, TagTextEncoding currentEncoding)
Ensures the byte-order of the specified UTF-16 string matches the byte-order of the machine.
Definition: tagvalue.cpp:726
The Diagnostics class is a container for DiagMessage.
Definition: diagnostics.h:156
string stringFromSubstring(tuple< const char *, size_t, const char * > substr)
Returns an std::string instance for the substring parsed using parseSubstring().
Definition: id3v2frame.cpp:109
TagValue & value()
Returns the value of the current TagField.