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