Tag Parser  9.1.2
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 <cstdint>
15 #include <cstring>
16 #include <limits>
17 #include <memory>
18 
19 using namespace std;
20 using namespace CppUtilities;
21 namespace TagParser {
22 
23 namespace Id3v2TextEncodingBytes {
25 }
26 
28 constexpr auto maxId3v2FrameDataSize(numeric_limits<std::uint32_t>::max() - 15);
29 
38 Id3v2Frame::Id3v2Frame()
39  : m_parsedVersion(0)
40  , m_dataSize(0)
41  , m_totalSize(0)
42  , m_flag(0)
43  , m_group(0)
44  , m_padding(false)
45 {
46 }
47 
51 Id3v2Frame::Id3v2Frame(const IdentifierType &id, const TagValue &value, std::uint8_t group, std::uint16_t flag)
52  : TagField<Id3v2Frame>(id, value)
53  , m_parsedVersion(0)
54  , m_dataSize(0)
55  , m_totalSize(0)
56  , m_flag(flag)
57  , m_group(group)
58  , m_padding(false)
59 {
60 }
61 
66 template <class stringtype> int parseGenreIndex(const stringtype &denotation)
67 {
68  int index = -1;
69  for (auto c : denotation) {
70  if (index == -1) {
71  switch (c) {
72  case ' ':
73  break;
74  case '(':
75  index = 0;
76  break;
77  case '\0':
78  return -1;
79  default:
80  if (c >= '0' && c <= '9') {
81  index = c - '0';
82  } else {
83  return -1;
84  }
85  }
86  } else {
87  switch (c) {
88  case ')':
89  return index;
90  case '\0':
91  return index;
92  default:
93  if (c >= '0' && c <= '9') {
94  index = index * 10 + c - '0';
95  } else {
96  return -1;
97  }
98  }
99  }
100  }
101  return index;
102 }
103 
107 string stringFromSubstring(tuple<const char *, size_t, const char *> substr)
108 {
109  return string(get<0>(substr), get<1>(substr));
110 }
111 
115 u16string wideStringFromSubstring(tuple<const char *, size_t, const char *> substr, TagTextEncoding encoding)
116 {
117  u16string res(reinterpret_cast<u16string::const_pointer>(get<0>(substr)), get<1>(substr) / 2);
118  TagValue::ensureHostByteOrder(res, encoding);
119  return res;
120 }
121 
132 void Id3v2Frame::parse(BinaryReader &reader, std::uint32_t version, std::uint32_t maximalSize, Diagnostics &diag)
133 {
134  static const string defaultContext("parsing ID3v2 frame");
135  string context;
136 
137  // parse header
138  if (version < 3) {
139  // parse header for ID3v2.1 and ID3v2.2
140  // -> read ID
141  setId(reader.readUInt24BE());
142  if (id() & 0xFFFF0000u) {
143  m_padding = false;
144  } else {
145  // padding reached
146  m_padding = true;
147  throw NoDataFoundException();
148  }
149 
150  // -> update context
151  context = "parsing " % idToString() + " frame";
152 
153  // -> read size, check whether frame is truncated
154  m_dataSize = reader.readUInt24BE();
155  m_totalSize = m_dataSize + 6;
156  if (m_totalSize > maximalSize) {
157  diag.emplace_back(DiagLevel::Warning, "The frame is truncated and will be ignored.", context);
158  throw TruncatedDataException();
159  }
160 
161  // -> no flags/group in ID3v2.2
162  m_flag = 0;
163  m_group = 0;
164 
165  } else {
166  // parse header for ID3v2.3 and ID3v2.4
167  // -> read ID
168  setId(reader.readUInt32BE());
169  if (id() & 0xFF000000u) {
170  m_padding = false;
171  } else {
172  // padding reached
173  m_padding = true;
174  throw NoDataFoundException();
175  }
176 
177  // -> update context
178  context = "parsing " % idToString() + " frame";
179 
180  // -> read size, check whether frame is truncated
181  m_dataSize = version >= 4 ? reader.readSynchsafeUInt32BE() : reader.readUInt32BE();
182  m_totalSize = m_dataSize + 10;
183  if (m_totalSize > maximalSize) {
184  diag.emplace_back(DiagLevel::Warning, "The frame is truncated and will be ignored.", context);
185  throw TruncatedDataException();
186  }
187 
188  // -> read flags and group
189  m_flag = reader.readUInt16BE();
190  m_group = hasGroupInformation() ? reader.readByte() : 0;
191  if (isEncrypted()) {
192  // encryption is not implemented
193  diag.emplace_back(DiagLevel::Critical, "Encrypted frames aren't supported.", context);
195  }
196  }
197 
198  // frame size mustn't be 0
199  if (m_dataSize <= 0) {
200  diag.emplace_back(DiagLevel::Warning, "The frame size is 0.", context);
201  throw InvalidDataException();
202  }
203 
204  // parse the data
205  unique_ptr<char[]> buffer;
206 
207  // -> decompress data if compressed; otherwise just read it
208  if (isCompressed()) {
209  uLongf decompressedSize = version >= 4 ? reader.readSynchsafeUInt32BE() : reader.readUInt32BE();
210  if (decompressedSize < m_dataSize) {
211  diag.emplace_back(DiagLevel::Critical, "The decompressed size is smaller than the compressed size.", context);
212  throw InvalidDataException();
213  }
214  const auto bufferCompressed = make_unique<char[]>(m_dataSize);
215  reader.read(bufferCompressed.get(), m_dataSize);
216  buffer = make_unique<char[]>(decompressedSize);
217  switch (
218  uncompress(reinterpret_cast<Bytef *>(buffer.get()), &decompressedSize, reinterpret_cast<Bytef *>(bufferCompressed.get()), m_dataSize)) {
219  case Z_MEM_ERROR:
220  diag.emplace_back(DiagLevel::Critical, "Decompressing failed. The source buffer was too small.", context);
221  throw InvalidDataException();
222  case Z_BUF_ERROR:
223  diag.emplace_back(DiagLevel::Critical, "Decompressing failed. The destination buffer was too small.", context);
224  throw InvalidDataException();
225  case Z_DATA_ERROR:
226  diag.emplace_back(DiagLevel::Critical, "Decompressing failed. The input data was corrupted or incomplete.", context);
227  throw InvalidDataException();
228  case Z_OK:
229  break;
230  default:
231  diag.emplace_back(DiagLevel::Critical, "Decompressing failed (unknown reason).", context);
232  throw InvalidDataException();
233  }
234  if (decompressedSize > maxId3v2FrameDataSize) {
235  diag.emplace_back(DiagLevel::Critical, "The decompressed data exceeds the maximum supported frame size.", context);
236  throw InvalidDataException();
237  }
238  m_dataSize = static_cast<std::uint32_t>(decompressedSize);
239  } else {
240  buffer = make_unique<char[]>(m_dataSize);
241  reader.read(buffer.get(), m_dataSize);
242  }
243 
244  // read tag value depending on frame ID/type
245  if (Id3v2FrameIds::isTextFrame(id())) {
246  // parse text encoding byte
247  TagTextEncoding dataEncoding = parseTextEncodingByte(static_cast<std::uint8_t>(*buffer.get()), diag);
248 
249  // parse string values (since ID3v2.4 a text frame may contain multiple strings)
250  const char *currentOffset = buffer.get() + 1;
251  for (size_t currentIndex = 1; currentIndex < m_dataSize;) {
252  // determine the next substring
253  const auto substr(parseSubstring(currentOffset, m_dataSize - currentIndex, dataEncoding, false, diag));
254 
255  // handle case when string is empty
256  if (!get<1>(substr)) {
257  if (currentIndex == 1) {
259  }
260  currentIndex = static_cast<size_t>(get<2>(substr) - buffer.get());
261  currentOffset = get<2>(substr);
262  continue;
263  }
264 
265  // determine the TagValue instance to store the value
266  TagValue *const value = [&] {
267  if (this->value().isEmpty()) {
268  return &this->value();
269  }
270  m_additionalValues.emplace_back();
271  return &m_additionalValues.back();
272  }();
273 
274  // apply further parsing for some text frame types (eg. convert track number to PositionInSet)
275  if ((version >= 3 && (id() == Id3v2FrameIds::lTrackPosition || id() == Id3v2FrameIds::lDiskPosition))
277  // parse the track number or the disk number frame
278  try {
279  if (characterSize(dataEncoding) > 1) {
281  } else {
283  }
284  } catch (const ConversionException &) {
285  diag.emplace_back(DiagLevel::Warning, "The value of track/disk position frame is not numeric and will be ignored.", context);
286  }
287 
288  } else if ((version >= 3 && id() == Id3v2FrameIds::lLength) || (version < 3 && id() == Id3v2FrameIds::sLength)) {
289  // parse frame contains length
290  try {
291  const auto milliseconds = [&] {
292  if (dataEncoding == TagTextEncoding::Utf16BigEndian || dataEncoding == TagTextEncoding::Utf16LittleEndian) {
293  const auto parsedStringRef = parseSubstring(buffer.get() + 1, m_dataSize - 1, dataEncoding, false, diag);
294  const auto convertedStringData = dataEncoding == TagTextEncoding::Utf16BigEndian
295  ? convertUtf16BEToUtf8(get<0>(parsedStringRef), get<1>(parsedStringRef))
296  : convertUtf16LEToUtf8(get<0>(parsedStringRef), get<1>(parsedStringRef));
297  return string(convertedStringData.first.get(), convertedStringData.second);
298  } else { // Latin-1 or UTF-8
299  return stringFromSubstring(substr);
300  }
301  }();
302  value->assignTimeSpan(TimeSpan::fromMilliseconds(stringToNumber<double>(milliseconds)));
303  } catch (const ConversionException &) {
304  diag.emplace_back(DiagLevel::Warning, "The value of the length frame is not numeric and will be ignored.", context);
305  }
306 
307  } else if ((version >= 3 && id() == Id3v2FrameIds::lGenre) || (version < 3 && id() == Id3v2FrameIds::sGenre)) {
308  // parse genre/content type
309  const auto genreIndex = [&] {
310  if (characterSize(dataEncoding) > 1) {
311  return parseGenreIndex(wideStringFromSubstring(substr, dataEncoding));
312  } else {
313  return parseGenreIndex(stringFromSubstring(substr));
314  }
315  }();
316  if (genreIndex != -1) {
317  // genre is specified as ID3 genre number
318  value->assignStandardGenreIndex(genreIndex);
319  } else {
320  // genre is specified as string
321  value->assignData(get<0>(substr), get<1>(substr), TagDataType::Text, dataEncoding);
322  }
323  } else {
324  // store any other text frames as-is
325  value->assignData(get<0>(substr), get<1>(substr), TagDataType::Text, dataEncoding);
326  }
327 
328  currentIndex = static_cast<size_t>(get<2>(substr) - buffer.get());
329  currentOffset = get<2>(substr);
330  }
331 
332  // add warning about additional values
333  if (version < 4 && !m_additionalValues.empty()) {
334  diag.emplace_back(
335  DiagLevel::Warning, "Multiple strings found though the tag is pre-ID3v2.4. " + ignoreAdditionalValuesDiagMsg(), context);
336  }
337 
338  } else if (version >= 3 && id() == Id3v2FrameIds::lCover) {
339  // parse picture frame
340  std::uint8_t type;
341  parsePicture(buffer.get(), m_dataSize, value(), type, diag);
342  setTypeInfo(type);
343 
344  } else if (version < 3 && id() == Id3v2FrameIds::sCover) {
345  // parse legacy picutre
346  std::uint8_t type;
347  parseLegacyPicture(buffer.get(), m_dataSize, value(), type, diag);
348  setTypeInfo(type);
349 
350  } else if (((version >= 3 && id() == Id3v2FrameIds::lComment) || (version < 3 && id() == Id3v2FrameIds::sComment))
352  // parse comment frame or unsynchronized lyrics frame (these two frame types have the same structure)
353  parseComment(buffer.get(), m_dataSize, value(), diag);
354 
355  } else {
356  // parse unknown/unsupported frame
357  value().assignData(buffer.get(), m_dataSize, TagDataType::Undefined);
358  }
359 }
360 
372 {
373  return Id3v2FrameMaker(*this, version, diag);
374 }
375 
384 void Id3v2Frame::make(BinaryWriter &writer, std::uint8_t version, Diagnostics &diag)
385 {
386  prepareMaking(version, diag).make(writer);
387 }
388 
392 void Id3v2Frame::reset()
393 {
394  m_flag = 0;
395  m_group = 0;
396  m_parsedVersion = 0;
397  m_dataSize = 0;
398  m_totalSize = 0;
399  m_padding = false;
400  m_additionalValues.clear();
401 }
402 
406 std::string Id3v2Frame::ignoreAdditionalValuesDiagMsg() const
407 {
408  if (m_additionalValues.size() == 1) {
409  return argsToString("Additional value \"", m_additionalValues.front().toString(TagTextEncoding::Utf8), "\" is supposed to be ignored.");
410  }
411  return argsToString("Additional values ", DiagMessage::formatList(TagValue::toStrings(m_additionalValues)), " are supposed to be ignored.");
412 }
413 
425 Id3v2FrameMaker::Id3v2FrameMaker(Id3v2Frame &frame, std::uint8_t version, Diagnostics &diag)
426  : m_frame(frame)
427  , m_frameId(m_frame.id())
428  , m_version(version)
429 {
430  const string context("making " % m_frame.idToString() + " frame");
431 
432  // validate frame's configuration
433  if (m_frame.isEncrypted()) {
434  diag.emplace_back(DiagLevel::Critical, "Cannot make an encrypted frame (isn't supported by this tagging library).", context);
435  throw InvalidDataException();
436  }
437  if (m_frame.hasPaddingReached()) {
438  diag.emplace_back(DiagLevel::Critical, "Cannot make a frame which is marked as padding.", context);
439  throw InvalidDataException();
440  }
441  if (version < 3 && m_frame.isCompressed()) {
442  diag.emplace_back(DiagLevel::Warning, "Compression is not supported by the version of ID3v2 and won't be applied.", context);
443  }
444  if (version < 3 && (m_frame.flag() || m_frame.group())) {
445  diag.emplace_back(DiagLevel::Warning,
446  "The existing flag and group information is not supported by the version of ID3v2 and will be ignored/discarted.", context);
447  }
448 
449  // get non-empty, assigned values
450  vector<const TagValue *> values;
451  values.reserve(1 + frame.additionalValues().size());
452  if (!frame.value().isEmpty()) {
453  values.emplace_back(&frame.value());
454  }
455  for (const auto &value : frame.additionalValues()) {
456  if (!value.isEmpty()) {
457  values.emplace_back(&value);
458  }
459  }
460 
461  // validate assigned values
462  if (values.empty()) {
463  throw NoDataProvidedException();
464  // note: This is not really an issue because in the case we're not provided with any value here just means that the field
465  // is supposed to be removed. So don't add any diagnostic messages here.
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(numberToString(static_cast<std::uint64_t>(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(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<std::uint32_t>(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, diag);
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<std::uint32_t>(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<std::uint32_t>(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 
716 TagTextEncoding Id3v2Frame::parseTextEncodingByte(std::uint8_t textEncodingByte, Diagnostics &diag)
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) && (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 (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 std::uint16_t *pos = reinterpret_cast<const std::uint16_t *>(get<0>(res));
817  for (; *pos != 0x0000; ++pos) {
818  if (pos < reinterpret_cast<const std::uint16_t *>(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, std::size_t maxSize, TagTextEncoding &encoding, Diagnostics &diag)
865 {
866  switch (encoding) {
869  if ((maxSize >= 2) && (BE::toUInt16(buffer) == 0xFFFE)) {
871  } else if ((maxSize >= 2) && (BE::toUInt16(buffer) == 0xFEFF)) {
873  }
874  break;
875  default:
876  if ((maxSize >= 3) && (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, std::uint8_t &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<std::uint8_t>(*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, std::uint8_t &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<std::uint8_t>(*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<std::uint8_t>(*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 
976 size_t Id3v2Frame::makeBom(char *buffer, TagTextEncoding encoding)
977 {
978  switch (encoding) {
980  LE::getBytes(static_cast<std::uint16_t>(0xFEFF), buffer);
981  return 2;
983  BE::getBytes(static_cast<std::uint16_t>(0xFEFF), buffer);
984  return 2;
985  default:
986  return 0;
987  }
988 }
989 
994  unique_ptr<char[]> &buffer, std::uint32_t &bufferSize, const TagValue &picture, std::uint8_t typeInfo, Diagnostics &diag)
995 {
996  // determine description
997  TagTextEncoding descriptionEncoding = picture.descriptionEncoding();
998  StringData convertedDescription;
999  string::size_type descriptionSize = picture.description().find(
1000  "\0\0", 0, descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? 2 : 1);
1001  if (descriptionSize == string::npos) {
1002  descriptionSize = picture.description().size();
1003  }
1004  if (descriptionEncoding == TagTextEncoding::Utf8) {
1005  // UTF-8 is only supported by ID3v2.4, so convert back to UTF-16
1006  descriptionEncoding = TagTextEncoding::Utf16LittleEndian;
1007  convertedDescription = convertUtf8ToUtf16LE(picture.description().data(), descriptionSize);
1008  descriptionSize = convertedDescription.second;
1009  }
1010 
1011  // calculate needed buffer size and create buffer
1012  // note: encoding byte + image format + picture type byte + description size + 1 or 2 null bytes (depends on encoding) + data size
1013  const auto requiredBufferSize = 1 + 3 + 1 + descriptionSize
1014  + (descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? 4 : 1)
1015  + picture.dataSize();
1016  if (requiredBufferSize > numeric_limits<std::uint32_t>::max()) {
1017  diag.emplace_back(DiagLevel::Critical, "Required size exceeds maximum.", "making legacy picture frame");
1018  throw InvalidDataException();
1019  }
1020  buffer = make_unique<char[]>(bufferSize = static_cast<std::uint32_t>(requiredBufferSize));
1021  char *offset = buffer.get();
1022 
1023  // write encoding byte
1024  *offset = static_cast<char>(makeTextEncodingByte(descriptionEncoding));
1025 
1026  // write mime type
1027  const char *imageFormat;
1028  if (picture.mimeType() == "image/jpeg") {
1029  imageFormat = "JPG";
1030  } else if (picture.mimeType() == "image/png") {
1031  imageFormat = "PNG";
1032  } else if (picture.mimeType() == "image/gif") {
1033  imageFormat = "GIF";
1034  } else if (picture.mimeType() == "-->") {
1035  imageFormat = picture.mimeType().data();
1036  } else {
1037  imageFormat = "UND";
1038  }
1039  strncpy(++offset, imageFormat, 3);
1040 
1041  // write picture type
1042  *(offset += 3) = static_cast<char>(typeInfo);
1043 
1044  // write description
1045  offset += makeBom(offset + 1, descriptionEncoding);
1046  if (convertedDescription.first) {
1047  copy(convertedDescription.first.get(), convertedDescription.first.get() + descriptionSize, ++offset);
1048  } else {
1049  picture.description().copy(++offset, descriptionSize);
1050  }
1051  *(offset += descriptionSize) = 0x00; // terminate description and increase data offset
1052  if (descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian) {
1053  *(++offset) = 0x00;
1054  }
1055 
1056  // write actual data
1057  copy(picture.dataPointer(), picture.dataPointer() + picture.dataSize(), ++offset);
1058 }
1059 
1063 void Id3v2Frame::makePicture(std::unique_ptr<char[]> &buffer, std::uint32_t &bufferSize, const TagValue &picture, std::uint8_t typeInfo,
1064  std::uint8_t version, Diagnostics &diag)
1065 {
1066  if (version < 3) {
1067  makeLegacyPicture(buffer, bufferSize, picture, typeInfo, diag);
1068  return;
1069  }
1070 
1071  // determine description
1072  TagTextEncoding descriptionEncoding = picture.descriptionEncoding();
1073  StringData convertedDescription;
1074  string::size_type descriptionSize = picture.description().find(
1075  "\0\0", 0, descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? 2 : 1);
1076  if (descriptionSize == string::npos) {
1077  descriptionSize = picture.description().size();
1078  }
1079  if (version < 4 && descriptionEncoding == TagTextEncoding::Utf8) {
1080  // UTF-8 is only supported by ID3v2.4, so convert back to UTF-16
1081  descriptionEncoding = TagTextEncoding::Utf16LittleEndian;
1082  convertedDescription = convertUtf8ToUtf16LE(picture.description().data(), descriptionSize);
1083  descriptionSize = convertedDescription.second;
1084  }
1085  // determine mime-type
1086  string::size_type mimeTypeSize = picture.mimeType().find('\0');
1087  if (mimeTypeSize == string::npos) {
1088  mimeTypeSize = picture.mimeType().length();
1089  }
1090 
1091  // calculate needed buffer size and create buffer
1092  // note: encoding byte + mime type size + 0 byte + picture type byte + description size + 1 or 4 null bytes (depends on encoding) + data size
1093  const auto requiredBufferSize = 1 + mimeTypeSize + 1 + 1 + descriptionSize
1094  + (descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? 4 : 1)
1095  + picture.dataSize();
1096  if (requiredBufferSize > numeric_limits<uint32_t>::max()) {
1097  diag.emplace_back(DiagLevel::Critical, "Required size exceeds maximum.", "making picture frame");
1098  throw InvalidDataException();
1099  }
1100  buffer = make_unique<char[]>(bufferSize = static_cast<uint32_t>(requiredBufferSize));
1101  char *offset = buffer.get();
1102 
1103  // write encoding byte
1104  *offset = static_cast<char>(makeTextEncodingByte(descriptionEncoding));
1105 
1106  // write mime type
1107  picture.mimeType().copy(++offset, mimeTypeSize);
1108 
1109  *(offset += mimeTypeSize) = 0x00; // terminate mime type
1110  // write picture type
1111  *(++offset) = static_cast<char>(typeInfo);
1112 
1113  // write description
1114  offset += makeBom(offset + 1, descriptionEncoding);
1115  if (convertedDescription.first) {
1116  copy(convertedDescription.first.get(), convertedDescription.first.get() + descriptionSize, ++offset);
1117  } else {
1118  picture.description().copy(++offset, descriptionSize);
1119  }
1120  *(offset += descriptionSize) = 0x00; // terminate description and increase data offset
1121  if (descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian) {
1122  *(++offset) = 0x00;
1123  }
1124 
1125  // write actual data
1126  copy(picture.dataPointer(), picture.dataPointer() + picture.dataSize(), ++offset);
1127 }
1128 
1132 void Id3v2Frame::makeComment(unique_ptr<char[]> &buffer, std::uint32_t &bufferSize, const TagValue &comment, std::uint8_t version, Diagnostics &diag)
1133 {
1134  static const string context("making comment frame");
1135 
1136  // check whether type and other values are valid
1137  TagTextEncoding encoding = comment.dataEncoding();
1138  if (!comment.description().empty() && encoding != comment.descriptionEncoding()) {
1139  diag.emplace_back(DiagLevel::Critical, "Data encoding and description encoding aren't equal.", context);
1140  throw InvalidDataException();
1141  }
1142  const string &lng = comment.language();
1143  if (lng.length() > 3) {
1144  diag.emplace_back(DiagLevel::Critical, "The language must be 3 bytes long (ISO-639-2).", context);
1145  throw InvalidDataException();
1146  }
1147  StringData convertedDescription;
1148  string::size_type descriptionSize = comment.description().find(
1149  "\0\0", 0, encoding == TagTextEncoding::Utf16BigEndian || encoding == TagTextEncoding::Utf16LittleEndian ? 2 : 1);
1150  if (descriptionSize == string::npos) {
1151  descriptionSize = comment.description().size();
1152  }
1153  if (version < 4 && encoding == TagTextEncoding::Utf8) {
1154  // UTF-8 is only supported by ID3v2.4, so convert back to UTF-16
1156  convertedDescription = convertUtf8ToUtf16LE(comment.description().data(), descriptionSize);
1157  descriptionSize = convertedDescription.second;
1158  }
1159 
1160  // calculate needed buffer size and create buffer
1161  // note: encoding byte + language + description size + actual data size + BOMs and termination
1162  const auto data = comment.toString(encoding);
1163  const auto requiredBufferSize = 1 + 3 + descriptionSize + data.size()
1164  + (encoding == TagTextEncoding::Utf16BigEndian || encoding == TagTextEncoding::Utf16LittleEndian ? 6 : 1) + data.size();
1165  if (requiredBufferSize > numeric_limits<uint32_t>::max()) {
1166  diag.emplace_back(DiagLevel::Critical, "Required size exceeds maximum.", context);
1167  throw InvalidDataException();
1168  }
1169  buffer = make_unique<char[]>(bufferSize = static_cast<uint32_t>(requiredBufferSize));
1170  char *offset = buffer.get();
1171 
1172  // write encoding
1173  *offset = static_cast<char>(makeTextEncodingByte(encoding));
1174 
1175  // write language
1176  for (unsigned int i = 0; i < 3; ++i) {
1177  *(++offset) = (lng.length() > i) ? lng[i] : 0x00;
1178  }
1179 
1180  // write description
1181  offset += makeBom(offset + 1, encoding);
1182  if (convertedDescription.first) {
1183  copy(convertedDescription.first.get(), convertedDescription.first.get() + descriptionSize, ++offset);
1184  } else {
1185  comment.description().copy(++offset, descriptionSize);
1186  }
1187  offset += descriptionSize;
1188  *offset = 0x00; // terminate description and increase data offset
1190  *(++offset) = 0x00;
1191  }
1192 
1193  // write actual data
1194  offset += makeBom(offset + 1, encoding);
1195  data.copy(++offset, data.size());
1196 }
1197 
1198 } // namespace TagParser
TagParser::TagValue::mimeType
const std::string & mimeType() const
Returns the MIME type.
Definition: tagvalue.h:537
TagParser::Id3v2FrameIds::sDiskPosition
@ sDiskPosition
Definition: id3v2frameids.h:46
TagParser::DiagMessage::formatList
static std::string formatList(const std::vector< std::string > &values)
Concatenates the specified string values to a list.
Definition: diagnostics.cpp:67
TagParser::TagValue::assignTimeSpan
void assignTimeSpan(CppUtilities::TimeSpan value)
Assigns the given TimeSpan value.
Definition: tagvalue.h:368
TagParser::Id3v2Frame::parseTextEncodingByte
TagTextEncoding parseTextEncodingByte(std::uint8_t textEncodingByte, Diagnostics &diag)
Returns the text encoding for the specified textEncodingByte.
Definition: id3v2frame.cpp:716
TagParser::TagField::typeInfo
const TypeInfoType & typeInfo() const
Returns the type info of the current TagField.
Definition: generictagfield.h:176
TagParser::Id3v2Frame::Id3v2FrameMaker
friend class Id3v2FrameMaker
Definition: id3v2frame.h:88
TagParser::wideStringFromSubstring
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:115
TagParser::TagTextEncoding::Utf8
@ Utf8
TagParser::Id3v2FrameIds::lCover
@ lCover
Definition: id3v2frameids.h:23
TagParser::Id3v2Frame
The Id3v2Frame class is used by Id3v2Tag to store the fields.
Definition: id3v2frame.h:86
TagParser::TagValue::clearDataAndMetadata
void clearDataAndMetadata()
Wipes assigned data including meta data.
Definition: tagvalue.h:471
TagParser::TagTextEncoding
TagTextEncoding
Specifies the text encoding.
Definition: tagvalue.h:25
TagParser::TagValue::toStandardGenreIndex
int toStandardGenreIndex() const
Converts the value of the current TagValue object to its equivalent standard genre index.
Definition: tagvalue.cpp:362
TagParser::TagValue::descriptionEncoding
TagTextEncoding descriptionEncoding() const
Returns the description encoding.
Definition: tagvalue.h:613
TagParser::Id3v2FrameMaker
The Id3v2FrameMaker class helps making ID3v2 frames. It allows to calculate the required size.
Definition: id3v2frame.h:22
TagParser::TagField::setTypeInfo
void setTypeInfo(const TypeInfoType &typeInfo)
Sets the type info of the current TagField.
Definition: generictagfield.h:184
TagParser::TagValue::assignPosition
void assignPosition(PositionInSet value)
Assigns the given PositionInSet value.
Definition: tagvalue.h:355
TagParser::TagTextEncoding::Utf16BigEndian
@ Utf16BigEndian
TagParser::TagValue::toPositionInSet
PositionInSet toPositionInSet() const
Converts the value of the current TagValue object to its equivalent PositionInSet representation.
Definition: tagvalue.cpp:403
id3v2frameids.h
TagParser::DiagLevel::Warning
@ Warning
TagParser::TagTextEncoding::Unspecified
@ Unspecified
TagParser::Id3v2Frame::hasGroupInformation
bool hasGroupInformation() const
Returns whether the frame contains group information.
Definition: id3v2frame.h:277
TagParser::TagValue::ensureHostByteOrder
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:905
TagParser::Id3v2FrameIds::isLongId
constexpr bool isLongId(std::uint32_t id)
Returns an indication whether the specified id is a long frame id.
Definition: id3v2frameids.h:71
TagParser::VorbisCommentIds::version
constexpr const TAG_PARSER_EXPORT char * version()
Definition: vorbiscommentids.h:33
TagParser::Id3v2Frame::parseString
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
TagParser::Diagnostics
The Diagnostics class is a container for DiagMessage.
Definition: diagnostics.h:156
TagParser::Id3v2FrameIds::sTrackPosition
@ sTrackPosition
Definition: id3v2frameids.h:45
TagParser::Id3v2FrameIds::sCover
@ sCover
Definition: id3v2frameids.h:49
TagParser::TagDataType::PositionInSet
@ PositionInSet
TagParser::TagDataType::StandardGenreIndex
@ StandardGenreIndex
TagParser::TagValue::dataPointer
char * dataPointer()
Returns a pointer to the raw data assigned to the current instance.
Definition: tagvalue.h:492
TagParser
Contains all classes and functions of the TagInfo library.
Definition: aaccodebook.h:10
TagParser::Id3v2Frame::parseBom
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
TagParser::Id3v2FrameIds::lComment
@ lComment
Definition: id3v2frameids.h:14
TagParser::Id3v2TextEncodingBytes::Utf8
@ Utf8
Definition: id3v2frame.cpp:24
TagParser::TagField::value
TagValue & value()
Returns the value of the current TagField.
Definition: generictagfield.h:144
TagParser::Id3v2Frame::parse
void parse(CppUtilities::BinaryReader &reader, std::uint32_t version, std::uint32_t maximalSize, Diagnostics &diag)
Parses a frame from the stream read using the specified reader.
Definition: id3v2frame.cpp:132
TagParser::Id3v2Frame::makeComment
static void makeComment(std::unique_ptr< char[]> &buffer, std::uint32_t &bufferSize, const TagValue &comment, std::uint8_t version, Diagnostics &diag)
Writes the specified comment to the specified buffer.
Definition: id3v2frame.cpp:1132
TagParser::Id3v2FrameIds::lTrackPosition
@ lTrackPosition
Definition: id3v2frameids.h:19
TagParser::Id3v2Frame::makeTextEncodingByte
static std::uint8_t makeTextEncodingByte(TagTextEncoding textEncoding)
Returns a text encoding byte for the specified textEncoding.
Definition: id3v2frame.cpp:737
TagParser::TagField::id
const IdentifierType & id() const
Returns the id of the current TagField.
Definition: generictagfield.h:115
TagParser::characterSize
constexpr int characterSize(TagTextEncoding encoding)
Returns the size of one character for the specified encoding in bytes.
Definition: tagvalue.h:37
TagParser::Id3v2Frame::parseWideString
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
TagParser::Id3v2Frame::isEncrypted
bool isEncrypted() const
Returns whether the frame is encrypted.
Definition: id3v2frame.h:269
TagParser::MatroskaTagIds::duration
constexpr const TAG_PARSER_EXPORT char * duration()
Definition: matroskatagid.h:342
TagParser::Id3v2FrameIds::isShortId
constexpr bool isShortId(std::uint32_t id)
Returns an indication whether the specified id is a short frame id.
Definition: id3v2frameids.h:79
TagParser::Id3v2Frame::parseComment
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
TagParser::TagValue::description
const std::string & description() const
Returns the description.
Definition: tagvalue.h:511
TagParser::Id3v2FrameIds::lLength
@ lLength
Definition: id3v2frameids.h:25
TagParser::TagValue::setMimeType
void setMimeType(const std::string &mimeType)
Sets the MIME type.
Definition: tagvalue.h:547
TagParser::Id3v2FrameIds::lGenre
@ lGenre
Definition: id3v2frameids.h:18
TagParser::TagField
The TagField class is used by FieldMapBasedTag to store the fields.
Definition: generictagfield.h:8
TagParser::TagValue::isEmpty
bool isEmpty() const
Returns whether an empty value is assigned.
Definition: tagvalue.h:449
TagParser::Id3v2Frame::isCompressed
bool isCompressed() const
Returns whether the frame is compressed.
Definition: id3v2frame.h:260
TagParser::TagValue::toStrings
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:626
TagParser::Id3v2FrameIds::isTextFrame
constexpr bool isTextFrame(std::uint32_t id)
Returns an indication whether the specified id is a text frame id.
Definition: id3v2frameids.h:87
TagParser::TagTextEncoding::Latin1
@ Latin1
TagParser::TagDataType::Undefined
@ Undefined
TagParser::Id3v2Frame::parseLegacyPicture
void parseLegacyPicture(const char *buffer, std::size_t maxSize, TagValue &tagValue, std::uint8_t &typeInfo, Diagnostics &diag)
Parses the ID3v2.2 picture from the specified buffer.
Definition: id3v2frame.cpp:890
id3genres.h
TagParser::DiagLevel::Critical
@ Critical
TagParser::maxId3v2FrameDataSize
constexpr auto maxId3v2FrameDataSize(numeric_limits< std::uint32_t >::max() - 15)
The maximum (supported) size of an ID3v2Frame.
TagParser::TagDataType::Text
@ Text
CppUtilities
Definition: abstractcontainer.h:15
TagParser::Id3v2FrameIds::convertToShortId
std::uint32_t convertToShortId(std::uint32_t id)
Converts the specified long frame ID to the equivalent short frame ID.
Definition: id3v2frameids.cpp:23
TagParser::Id3v2FrameIds::lDiskPosition
@ lDiskPosition
Definition: id3v2frameids.h:20
TagParser::Id3v2FrameIds::sLength
@ sLength
Definition: id3v2frameids.h:51
TagParser::Id3v2TextEncodingBytes::Utf16BigEndianWithoutBom
@ Utf16BigEndianWithoutBom
Definition: id3v2frame.cpp:24
TagParser::TagDataType::Picture
@ Picture
TagParser::Id3v2Frame::parsePicture
void parsePicture(const char *buffer, std::size_t maxSize, TagValue &tagValue, std::uint8_t &typeInfo, Diagnostics &diag)
Parses the ID3v2.3 picture from the specified buffer.
Definition: id3v2frame.cpp:916
TagParser::Id3v2FrameIds::lUnsynchronizedLyrics
@ lUnsynchronizedLyrics
Definition: id3v2frameids.h:28
TagParser::Id3v2Frame::Id3v2Frame
Id3v2Frame()
Constructs a new Id3v2Frame.
Definition: id3v2frame.cpp:38
TagParser::TagValue::assignStandardGenreIndex
void assignStandardGenreIndex(int index)
Assigns the given standard genre index to be assigned.
Definition: tagvalue.h:386
TagParser::Id3v2Frame::makeBom
static std::size_t makeBom(char *buffer, TagTextEncoding encoding)
Writes the BOM for the specified encoding to the specified buffer.
Definition: id3v2frame.cpp:976
TagParser::Id3v2Frame::dataSize
std::uint32_t dataSize() const
Returns the size of the data stored in the frame in bytes.
Definition: id3v2frame.h:228
TagParser::parseGenreIndex
int parseGenreIndex(const stringtype &denotation)
Helper function to parse the genre index.
Definition: id3v2frame.cpp:66
TagParser::VersionNotSupportedException
The exception that is thrown when an operation fails because the detected or specified version is not...
Definition: exceptions.h:53
TagParser::TagValue::toTimeSpan
CppUtilities::TimeSpan toTimeSpan() const
Converts the value of the current TagValue object to its equivalent TimeSpan representation.
Definition: tagvalue.cpp:442
TagParser::TagValue::dataSize
std::size_t dataSize() const
Returns the size of the assigned value in bytes.
Definition: tagvalue.h:481
TagParser::Id3v2FrameIds::sUnsynchronizedLyrics
@ sUnsynchronizedLyrics
Definition: id3v2frameids.h:54
TagParser::NoDataFoundException
The exception that is thrown when the data to be parsed holds no parsable information (e....
Definition: exceptions.h:18
TagParser::InvalidDataException
The exception that is thrown when the data to be parsed or to be made seems invalid and therefore can...
Definition: exceptions.h:25
TagParser::TruncatedDataException
The exception that is thrown when the data to be parsed is truncated and therefore can not be parsed ...
Definition: exceptions.h:39
TagParser::TagValue
The TagValue class wraps values of different types. It is meant to be assigned to a tag field.
Definition: tagvalue.h:75
TagParser::TagValue::setDescription
void setDescription(const std::string &value, TagTextEncoding encoding=TagTextEncoding::Latin1)
Sets the description.
Definition: tagvalue.h:526
TagParser::MatroskaTagIds::comment
constexpr const TAG_PARSER_EXPORT char * comment()
Definition: matroskatagid.h:309
TagParser::Id3v2Frame::parseSubstring
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
TagParser::Id3v2Frame::make
void make(CppUtilities::BinaryWriter &writer, std::uint8_t version, Diagnostics &diag)
Writes the frame to a stream using the specified writer and the specified ID3v2 version.
Definition: id3v2frame.cpp:384
TagParser::Id3v2FrameIds::sGenre
@ sGenre
Definition: id3v2frameids.h:44
TagParser::TagTextEncoding::Utf16LittleEndian
@ Utf16LittleEndian
TagParser::TagValue::toString
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:407
TagParser::PositionInSet
The PositionInSet class describes the position of an element in a set which consists of a certain num...
Definition: positioninset.h:21
TagParser::Id3v2TextEncodingBytes::Ascii
@ Ascii
Definition: id3v2frame.cpp:24
TagParser::Id3v2Frame::prepareMaking
Id3v2FrameMaker prepareMaking(std::uint8_t version, Diagnostics &diag)
Prepares making.
Definition: id3v2frame.cpp:371
TagParser::TagField::setId
void setId(const IdentifierType &id)
Sets the id of the current Tag Field.
Definition: generictagfield.h:128
TagParser::Id3v2Frame::flag
std::uint16_t flag() const
Returns the flags.
Definition: id3v2frame.h:204
TagParser::TagValue::type
TagDataType type() const
Returns the type of the assigned value.
Definition: tagvalue.h:395
TagParser::Id3v2FrameIds::sComment
@ sComment
Definition: id3v2frameids.h:40
id3v2frame.h
TagParser::TagValue::dataEncoding
TagTextEncoding dataEncoding() const
Returns the data encoding.
Definition: tagvalue.h:603
TagParser::TagValue::assignData
void assignData(const char *data, std::size_t length, TagDataType type=TagDataType::Binary, TagTextEncoding encoding=TagTextEncoding::Latin1)
TagParser::Id3v2FrameIds::convertToLongId
std::uint32_t convertToLongId(std::uint32_t id)
Converts the specified short frame ID to the equivalent long frame ID.
Definition: id3v2frameids.cpp:77
TagParser::Id3v2Frame::makeLegacyPicture
static void makeLegacyPicture(std::unique_ptr< char[]> &buffer, std::uint32_t &bufferSize, const TagValue &picture, std::uint8_t typeInfo, Diagnostics &diag)
Writes the specified picture to the specified buffer (ID3v2.2 compatible).
Definition: id3v2frame.cpp:993
TagParser::stringFromSubstring
string stringFromSubstring(tuple< const char *, size_t, const char * > substr)
Returns an std::string instance for the substring parsed using parseSubstring().
Definition: id3v2frame.cpp:107
TagParser::TagValue::setLanguage
void setLanguage(const std::string &language)
Sets the language.
Definition: tagvalue.h:567
TagParser::Id3v2TextEncodingBytes::Utf16WithBom
@ Utf16WithBom
Definition: id3v2frame.cpp:24
TagParser::Id3v2Frame::makePicture
static void makePicture(std::unique_ptr< char[]> &buffer, std::uint32_t &bufferSize, const TagValue &picture, std::uint8_t typeInfo, std::uint8_t version, Diagnostics &diag)
Writes the specified picture to the specified buffer.
Definition: id3v2frame.cpp:1063
TagParser::Id3v2TextEncodingBytes::Id3v2TextEncodingByte
Id3v2TextEncodingByte
Definition: id3v2frame.cpp:24
TagParser::TagField::idToString
std::string idToString() const
Definition: generictagfield.h:120
TagParser::TagField::IdentifierType
typename TagFieldTraits< ImplementationType >::IdentifierType IdentifierType
Definition: generictagfield.h:34
TagParser::Id3v2Frame::group
std::uint8_t group() const
Returns the group.
Definition: id3v2frame.h:302
TagParser::Id3v2FrameMaker::make
void make(CppUtilities::BinaryWriter &writer)
Saves the frame (specified when constructing the object) using the specified writer.
Definition: id3v2frame.cpp:683