Tag Parser  9.2.0
C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags
id3v2frame.cpp
Go to the documentation of this file.
1 #include "./id3v2frame.h"
2 #include "./id3genres.h"
3 #include "./id3v2frameids.h"
4 
5 #include "../diagnostics.h"
6 #include "../exceptions.h"
7 
8 #include <c++utilities/conversion/stringbuilder.h>
9 #include <c++utilities/conversion/stringconversion.h>
10 
11 #include <zlib.h>
12 
13 #include <algorithm>
14 #include <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  // add a warning if a frame appears in an ID3v2 tag known not to support it
200  diag.emplace_back(DiagLevel::Warning,
201  argsToString("The frame is only supported in ID3v2.4 and newer but the tag's version is ID3v2.", version, '.'), context);
202  } else if (version > 3 && Id3v2FrameIds::isPreId3v24Id(id())) {
203  diag.emplace_back(DiagLevel::Warning,
204  argsToString("The frame is only supported in ID3v2.3 and older but the tag's version is ID3v2.", version, '.'), context);
205  }
206 
207  // frame size mustn't be 0
208  if (m_dataSize <= 0) {
209  diag.emplace_back(DiagLevel::Warning, "The frame size is 0.", context);
210  throw InvalidDataException();
211  }
212 
213  // parse the data
214  unique_ptr<char[]> buffer;
215 
216  // -> decompress data if compressed; otherwise just read it
217  if (isCompressed()) {
218  uLongf decompressedSize = version >= 4 ? reader.readSynchsafeUInt32BE() : reader.readUInt32BE();
219  if (decompressedSize < m_dataSize) {
220  diag.emplace_back(DiagLevel::Critical, "The decompressed size is smaller than the compressed size.", context);
221  throw InvalidDataException();
222  }
223  const auto bufferCompressed = make_unique<char[]>(m_dataSize);
224  reader.read(bufferCompressed.get(), m_dataSize);
225  buffer = make_unique<char[]>(decompressedSize);
226  switch (
227  uncompress(reinterpret_cast<Bytef *>(buffer.get()), &decompressedSize, reinterpret_cast<Bytef *>(bufferCompressed.get()), m_dataSize)) {
228  case Z_MEM_ERROR:
229  diag.emplace_back(DiagLevel::Critical, "Decompressing failed. The source buffer was too small.", context);
230  throw InvalidDataException();
231  case Z_BUF_ERROR:
232  diag.emplace_back(DiagLevel::Critical, "Decompressing failed. The destination buffer was too small.", context);
233  throw InvalidDataException();
234  case Z_DATA_ERROR:
235  diag.emplace_back(DiagLevel::Critical, "Decompressing failed. The input data was corrupted or incomplete.", context);
236  throw InvalidDataException();
237  case Z_OK:
238  break;
239  default:
240  diag.emplace_back(DiagLevel::Critical, "Decompressing failed (unknown reason).", context);
241  throw InvalidDataException();
242  }
243  if (decompressedSize > maxId3v2FrameDataSize) {
244  diag.emplace_back(DiagLevel::Critical, "The decompressed data exceeds the maximum supported frame size.", context);
245  throw InvalidDataException();
246  }
247  m_dataSize = static_cast<std::uint32_t>(decompressedSize);
248  } else {
249  buffer = make_unique<char[]>(m_dataSize);
250  reader.read(buffer.get(), m_dataSize);
251  }
252 
253  // read tag value depending on frame ID/type
254  if (Id3v2FrameIds::isTextFrame(id())) {
255  // parse text encoding byte
256  TagTextEncoding dataEncoding = parseTextEncodingByte(static_cast<std::uint8_t>(*buffer.get()), diag);
257 
258  // parse string values (since ID3v2.4 a text frame may contain multiple strings)
259  const char *currentOffset = buffer.get() + 1;
260  for (size_t currentIndex = 1; currentIndex < m_dataSize;) {
261  // determine the next substring
262  const auto substr(parseSubstring(currentOffset, m_dataSize - currentIndex, dataEncoding, false, diag));
263 
264  // handle case when string is empty
265  if (!get<1>(substr)) {
266  if (currentIndex == 1) {
268  }
269  currentIndex = static_cast<size_t>(get<2>(substr) - buffer.get());
270  currentOffset = get<2>(substr);
271  continue;
272  }
273 
274  // determine the TagValue instance to store the value
275  TagValue *const value = [&] {
276  if (this->value().isEmpty()) {
277  return &this->value();
278  }
279  m_additionalValues.emplace_back();
280  return &m_additionalValues.back();
281  }();
282 
283  // apply further parsing for some text frame types (eg. convert track number to PositionInSet)
284  if ((version >= 3 && (id() == Id3v2FrameIds::lTrackPosition || id() == Id3v2FrameIds::lDiskPosition))
286  // parse the track number or the disk number frame
287  try {
288  if (characterSize(dataEncoding) > 1) {
290  } else {
292  }
293  } catch (const ConversionException &) {
294  diag.emplace_back(DiagLevel::Warning, "The value of track/disk position frame is not numeric and will be ignored.", context);
295  }
296 
297  } else if ((version >= 3 && id() == Id3v2FrameIds::lLength) || (version < 3 && id() == Id3v2FrameIds::sLength)) {
298  // parse frame contains length
299  try {
300  const auto milliseconds = [&] {
301  if (dataEncoding == TagTextEncoding::Utf16BigEndian || dataEncoding == TagTextEncoding::Utf16LittleEndian) {
302  const auto parsedStringRef = parseSubstring(buffer.get() + 1, m_dataSize - 1, dataEncoding, false, diag);
303  const auto convertedStringData = dataEncoding == TagTextEncoding::Utf16BigEndian
304  ? convertUtf16BEToUtf8(get<0>(parsedStringRef), get<1>(parsedStringRef))
305  : convertUtf16LEToUtf8(get<0>(parsedStringRef), get<1>(parsedStringRef));
306  return string(convertedStringData.first.get(), convertedStringData.second);
307  } else { // Latin-1 or UTF-8
308  return stringFromSubstring(substr);
309  }
310  }();
311  value->assignTimeSpan(TimeSpan::fromMilliseconds(stringToNumber<double>(milliseconds)));
312  } catch (const ConversionException &) {
313  diag.emplace_back(DiagLevel::Warning, "The value of the length frame is not numeric and will be ignored.", context);
314  }
315 
316  } else if ((version >= 3 && id() == Id3v2FrameIds::lGenre) || (version < 3 && id() == Id3v2FrameIds::sGenre)) {
317  // parse genre/content type
318  const auto genreIndex = [&] {
319  if (characterSize(dataEncoding) > 1) {
320  return parseGenreIndex(wideStringFromSubstring(substr, dataEncoding));
321  } else {
322  return parseGenreIndex(stringFromSubstring(substr));
323  }
324  }();
325  if (genreIndex != -1) {
326  // genre is specified as ID3 genre number
327  value->assignStandardGenreIndex(genreIndex);
328  } else {
329  // genre is specified as string
330  value->assignData(get<0>(substr), get<1>(substr), TagDataType::Text, dataEncoding);
331  }
332  } else {
333  // store any other text frames as-is
334  value->assignData(get<0>(substr), get<1>(substr), TagDataType::Text, dataEncoding);
335  }
336 
337  currentIndex = static_cast<size_t>(get<2>(substr) - buffer.get());
338  currentOffset = get<2>(substr);
339  }
340 
341  // add warning about additional values
342  if (version < 4 && !m_additionalValues.empty()) {
343  diag.emplace_back(
344  DiagLevel::Warning, "Multiple strings found though the tag is pre-ID3v2.4. " + ignoreAdditionalValuesDiagMsg(), context);
345  }
346 
347  } else if (version >= 3 && id() == Id3v2FrameIds::lCover) {
348  // parse picture frame
349  std::uint8_t type;
350  parsePicture(buffer.get(), m_dataSize, value(), type, diag);
351  setTypeInfo(type);
352 
353  } else if (version < 3 && id() == Id3v2FrameIds::sCover) {
354  // parse legacy picutre
355  std::uint8_t type;
356  parseLegacyPicture(buffer.get(), m_dataSize, value(), type, diag);
357  setTypeInfo(type);
358 
359  } else if (((version >= 3 && id() == Id3v2FrameIds::lComment) || (version < 3 && id() == Id3v2FrameIds::sComment))
361  // parse comment frame or unsynchronized lyrics frame (these two frame types have the same structure)
362  parseComment(buffer.get(), m_dataSize, value(), diag);
363 
364  } else {
365  // parse unknown/unsupported frame
366  value().assignData(buffer.get(), m_dataSize, TagDataType::Undefined);
367  }
368 }
369 
381 {
382  return Id3v2FrameMaker(*this, version, diag);
383 }
384 
393 void Id3v2Frame::make(BinaryWriter &writer, std::uint8_t version, Diagnostics &diag)
394 {
395  prepareMaking(version, diag).make(writer);
396 }
397 
401 void Id3v2Frame::reset()
402 {
403  m_flag = 0;
404  m_group = 0;
405  m_parsedVersion = 0;
406  m_dataSize = 0;
407  m_totalSize = 0;
408  m_padding = false;
409  m_additionalValues.clear();
410 }
411 
415 std::string Id3v2Frame::ignoreAdditionalValuesDiagMsg() const
416 {
417  if (m_additionalValues.size() == 1) {
418  return argsToString("Additional value \"", m_additionalValues.front().toString(TagTextEncoding::Utf8), "\" is supposed to be ignored.");
419  }
420  return argsToString("Additional values ", DiagMessage::formatList(TagValue::toStrings(m_additionalValues)), " are supposed to be ignored.");
421 }
422 
434 Id3v2FrameMaker::Id3v2FrameMaker(Id3v2Frame &frame, std::uint8_t version, Diagnostics &diag)
435  : m_frame(frame)
436  , m_frameId(m_frame.id())
437  , m_version(version)
438 {
439  const string context("making " % m_frame.idToString() + " frame");
440 
441  // validate frame's configuration
442  if (m_frame.isEncrypted()) {
443  diag.emplace_back(DiagLevel::Critical, "Cannot make an encrypted frame (isn't supported by this tagging library).", context);
444  throw InvalidDataException();
445  }
446  if (m_frame.hasPaddingReached()) {
447  diag.emplace_back(DiagLevel::Critical, "Cannot make a frame which is marked as padding.", context);
448  throw InvalidDataException();
449  }
450  if (version < 3 && m_frame.isCompressed()) {
451  diag.emplace_back(DiagLevel::Warning, "Compression is not supported by the version of ID3v2 and won't be applied.", context);
452  }
453  if (version < 3 && (m_frame.flag() || m_frame.group())) {
454  diag.emplace_back(DiagLevel::Warning,
455  "The existing flag and group information is not supported by the version of ID3v2 and will be ignored/discarted.", context);
456  }
457 
458  // get non-empty, assigned values
459  vector<const TagValue *> values;
460  values.reserve(1 + frame.additionalValues().size());
461  if (!frame.value().isEmpty()) {
462  values.emplace_back(&frame.value());
463  }
464  for (const auto &value : frame.additionalValues()) {
465  if (!value.isEmpty()) {
466  values.emplace_back(&value);
467  }
468  }
469 
470  // validate assigned values
471  if (values.empty()) {
472  throw NoDataProvidedException();
473  // note: This is not really an issue because in the case we're not provided with any value here just means that the field
474  // is supposed to be removed. So don't add any diagnostic messages here.
475  }
476  const bool isTextFrame = Id3v2FrameIds::isTextFrame(m_frameId);
477  if (values.size() != 1) {
478  if (!isTextFrame) {
479  diag.emplace_back(DiagLevel::Critical, "Multiple values are not supported for non-text-frames.", context);
480  throw InvalidDataException();
481  } else if (version < 4) {
482  diag.emplace_back(
483  DiagLevel::Warning, "Multiple strings assigned to pre-ID3v2.4 text frame. " + frame.ignoreAdditionalValuesDiagMsg(), context);
484  }
485  }
486 
487  // convert frame ID if necessary
488  if (version >= 3) {
489  if (Id3v2FrameIds::isShortId(m_frameId)) {
490  // try to convert the short frame ID to its long equivalent
491  if (!(m_frameId = Id3v2FrameIds::convertToLongId(m_frameId))) {
492  diag.emplace_back(DiagLevel::Critical,
493  "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.",
494  context);
495  throw InvalidDataException();
496  }
497  }
498  } else {
499  if (Id3v2FrameIds::isLongId(m_frameId)) {
500  // try to convert the long frame ID to its short equivalent
501  if (!(m_frameId = Id3v2FrameIds::convertToShortId(m_frameId))) {
502  diag.emplace_back(DiagLevel::Critical,
503  "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.",
504  context);
505  throw InvalidDataException();
506  }
507  }
508  }
509 
510  // add a warning if we're writing the frame for an ID3v2 tag known not to support it
511  if (version <= 3 && Id3v2FrameIds::isOnlyId3v24Id(version < 3 ? Id3v2FrameIds::convertToLongId(m_frameId) : m_frameId)) {
512  diag.emplace_back(DiagLevel::Warning,
513  argsToString("The frame is only supported in ID3v2.4 and newer but version of the tag being written is ID3v2.", version,
514  ". The frame is written nevertheless but other tools might not be able to deal with it."),
515  context);
516  } else if (version > 3 && Id3v2FrameIds::isPreId3v24Id(m_frameId)) {
517  diag.emplace_back(DiagLevel::Warning,
518  argsToString("The frame is only supported in ID3v2.3 and older but version of the tag being written is ID3v2.", version,
519  ". The frame is written nevertheless but other tools might not be able to deal with it."),
520  context);
521  }
522 
523  // make actual data depending on the frame ID
524  try {
525  if (isTextFrame) {
526  // make text frame
527  vector<string> substrings;
528  substrings.reserve(1 + frame.additionalValues().size());
530 
531  if ((version >= 3 && (m_frameId == Id3v2FrameIds::lTrackPosition || m_frameId == Id3v2FrameIds::lDiskPosition))
532  || (version < 3 && (m_frameId == Id3v2FrameIds::sTrackPosition || m_frameId == Id3v2FrameIds::sDiskPosition))) {
533  // make track number or disk number frame
535  for (const auto *const value : values) {
536  // convert the position to string
537  substrings.emplace_back(value->toString(encoding));
538  // warn if value is no valid position (although we just store a string after all)
540  continue;
541  }
542  try {
544  } catch (const ConversionException &) {
545  diag.emplace_back(DiagLevel::Warning,
546  argsToString("The track/disk number \"", substrings.back(), "\" is not of the expected form, eg. \"4/10\"."), context);
547  }
548  }
549 
550  } else if ((version >= 3 && m_frameId == Id3v2FrameIds::lLength) || (version < 3 && m_frameId == Id3v2FrameIds::sLength)) {
551  // make length frame
552  encoding = TagTextEncoding::Latin1;
553  for (const auto *const value : values) {
554  const auto duration(value->toTimeSpan());
555  if (duration.isNegative()) {
556  diag.emplace_back(DiagLevel::Critical, argsToString("Assigned duration \"", duration.toString(), "\" is negative."), context);
557  throw InvalidDataException();
558  }
559  substrings.emplace_back(numberToString(static_cast<std::uint64_t>(duration.totalMilliseconds())));
560  }
561 
562  } else {
563  // make standard genre index and other text frames
564  // -> find text encoding suitable for all assigned values
565  for (const auto *const value : values) {
566  switch (encoding) {
568  switch (value->type()) {
570  encoding = TagTextEncoding::Latin1;
571  break;
572  default:
573  encoding = value->dataEncoding();
574  }
575  break;
577  switch (value->dataEncoding()) {
579  break;
580  default:
581  encoding = value->dataEncoding();
582  }
583  break;
584  default:;
585  }
586  }
587  if (version <= 3 && encoding == TagTextEncoding::Utf8) {
589  }
590  // -> format values
591  for (const auto *const value : values) {
593  && ((version >= 3 && m_frameId == Id3v2FrameIds::lGenre) || (version < 3 && m_frameId == Id3v2FrameIds::sGenre))) {
594  // make standard genere index
595  substrings.emplace_back(numberToString(value->toStandardGenreIndex()));
596 
597  } else {
598  // make other text frame
599  substrings.emplace_back(value->toString(encoding));
600  }
601  }
602  }
603 
604  // concatenate substrings using encoding specific byte order mark and termination
605  const auto terminationLength = (encoding == TagTextEncoding::Utf16BigEndian || encoding == TagTextEncoding::Utf16LittleEndian) ? 2u : 1u;
606  const auto byteOrderMark = [&] {
607  switch (encoding) {
609  return string({ '\xFF', '\xFE' });
611  return string({ '\xFE', '\xFF' });
612  default:
613  return string();
614  }
615  }();
616  const auto concatenatedSubstrings = joinStrings(substrings, string(), false, byteOrderMark, string(terminationLength, '\0'));
617 
618  // write text encoding byte and concatenated strings to data buffer
619  m_data = make_unique<char[]>(m_decompressedSize = static_cast<std::uint32_t>(1 + concatenatedSubstrings.size()));
620  m_data[0] = static_cast<char>(Id3v2Frame::makeTextEncodingByte(encoding));
621  concatenatedSubstrings.copy(&m_data[1], concatenatedSubstrings.size());
622 
623  } else if ((version >= 3 && m_frameId == Id3v2FrameIds::lCover) || (version < 3 && m_frameId == Id3v2FrameIds::sCover)) {
624  // make picture frame
625  m_frame.makePicture(m_data, m_decompressedSize, *values.front(), m_frame.isTypeInfoAssigned() ? m_frame.typeInfo() : 0, version, diag);
626 
627  } else if (((version >= 3 && m_frameId == Id3v2FrameIds::lComment) || (version < 3 && m_frameId == Id3v2FrameIds::sComment))
628  || ((version >= 3 && m_frameId == Id3v2FrameIds::lUnsynchronizedLyrics)
629  || (version < 3 && m_frameId == Id3v2FrameIds::sUnsynchronizedLyrics))) {
630  // make comment frame or the unsynchronized lyrics frame
631  m_frame.makeComment(m_data, m_decompressedSize, *values.front(), version, diag);
632 
633  } else {
634  // make unknown frame
635  const auto &value(*values.front());
637  diag.emplace_back(DiagLevel::Critical, "Assigned value exceeds maximum size.", context);
638  throw InvalidDataException();
639  }
640  m_data = make_unique<char[]>(m_decompressedSize = static_cast<std::uint32_t>(value.dataSize()));
641  copy(value.dataPointer(), value.dataPointer() + m_decompressedSize, m_data.get());
642  }
643  } catch (const ConversionException &) {
644  try {
645  const auto valuesAsString = TagValue::toStrings(values);
646  diag.emplace_back(DiagLevel::Critical,
647  argsToString("Assigned value(s) \"", DiagMessage::formatList(valuesAsString), "\" can not be converted appropriately."), context);
648  } catch (const ConversionException &) {
649  diag.emplace_back(DiagLevel::Critical, "Assigned value(s) can not be converted appropriately.", context);
650  }
651  throw InvalidDataException();
652  }
653 
654  // apply compression if frame should be compressed
655  if (version >= 3 && m_frame.isCompressed()) {
656  auto compressedSize = compressBound(m_decompressedSize);
657  auto compressedData = make_unique<char[]>(compressedSize);
658  switch (compress(reinterpret_cast<Bytef *>(compressedData.get()), reinterpret_cast<uLongf *>(&compressedSize),
659  reinterpret_cast<Bytef *>(m_data.get()), m_decompressedSize)) {
660  case Z_MEM_ERROR:
661  diag.emplace_back(DiagLevel::Critical, "Decompressing failed. The source buffer was too small.", context);
662  throw InvalidDataException();
663  case Z_BUF_ERROR:
664  diag.emplace_back(DiagLevel::Critical, "Decompressing failed. The destination buffer was too small.", context);
665  throw InvalidDataException();
666  case Z_OK:;
667  }
668  if (compressedSize > maxId3v2FrameDataSize) {
669  diag.emplace_back(DiagLevel::Critical, "Compressed size exceeds maximum data size.", context);
670  throw InvalidDataException();
671  }
672  m_data.swap(compressedData);
673  m_dataSize = static_cast<std::uint32_t>(compressedSize);
674  } else {
675  m_dataSize = m_decompressedSize;
676  }
677 
678  // calculate required size
679  // -> data size
680  m_requiredSize = m_dataSize;
681  if (version < 3) {
682  // -> header size
683  m_requiredSize += 6;
684  } else {
685  // -> header size
686  m_requiredSize += 10;
687  // -> group byte
688  if (m_frame.hasGroupInformation()) {
689  m_requiredSize += 1;
690  }
691  // -> decompressed size
692  if (version >= 3 && m_frame.isCompressed()) {
693  m_requiredSize += 4;
694  }
695  }
696 }
697 
705 void Id3v2FrameMaker::make(BinaryWriter &writer)
706 {
707  if (m_version < 3) {
708  writer.writeUInt24BE(m_frameId);
709  writer.writeUInt24BE(m_dataSize);
710  } else {
711  writer.writeUInt32BE(m_frameId);
712  if (m_version >= 4) {
713  writer.writeSynchsafeUInt32BE(m_dataSize);
714  } else {
715  writer.writeUInt32BE(m_dataSize);
716  }
717  writer.writeUInt16BE(m_frame.flag());
718  if (m_frame.hasGroupInformation()) {
719  writer.writeByte(m_frame.group());
720  }
721  if (m_version >= 3 && m_frame.isCompressed()) {
722  if (m_version >= 4) {
723  writer.writeSynchsafeUInt32BE(m_decompressedSize);
724  } else {
725  writer.writeUInt32BE(m_decompressedSize);
726  }
727  }
728  }
729  writer.write(m_data.get(), m_dataSize);
730 }
731 
738 TagTextEncoding Id3v2Frame::parseTextEncodingByte(std::uint8_t textEncodingByte, Diagnostics &diag)
739 {
740  switch (textEncodingByte) {
748  return TagTextEncoding::Utf8;
749  default:
750  diag.emplace_back(
751  DiagLevel::Warning, "The charset of the frame is invalid. Latin-1 will be used.", "parsing encoding of frame " + idToString());
753  }
754 }
755 
760 {
761  switch (textEncoding) {
770  default:
771  return 0;
772  }
773 }
774 
789 tuple<const char *, size_t, const char *> Id3v2Frame::parseSubstring(
790  const char *buffer, std::size_t bufferSize, TagTextEncoding &encoding, bool addWarnings, Diagnostics &diag)
791 {
792  tuple<const char *, size_t, const char *> res(buffer, 0, buffer + bufferSize);
793  switch (encoding) {
796  case TagTextEncoding::Utf8: {
797  if ((bufferSize >= 3) && (BE::toUInt24(buffer) == 0x00EFBBBF)) {
798  if (encoding == TagTextEncoding::Latin1) {
799  diag.emplace_back(DiagLevel::Critical, "Denoted character set is Latin-1 but an UTF-8 BOM is present - assuming UTF-8.",
800  "parsing frame " + idToString());
801  encoding = TagTextEncoding::Utf8;
802  }
803  get<0>(res) += 3;
804  }
805  const char *pos = get<0>(res);
806  for (; *pos != 0x00; ++pos) {
807  if (pos < get<2>(res)) {
808  ++get<1>(res);
809  } else {
810  if (addWarnings) {
811  diag.emplace_back(
812  DiagLevel::Warning, "String in frame is not terminated properly.", "parsing termination of frame " + idToString());
813  }
814  break;
815  }
816  }
817  get<2>(res) = pos + 1;
818  break;
819  }
822  if (bufferSize >= 2) {
823  switch (LE::toUInt16(buffer)) {
824  case 0xFEFF:
825  if (encoding == TagTextEncoding::Utf16BigEndian) {
826  diag.emplace_back(DiagLevel::Critical,
827  "Denoted character set is UTF-16 Big Endian but UTF-16 Little Endian BOM is present - assuming UTF-16 LE.",
828  "parsing frame " + idToString());
830  }
831  get<0>(res) += 2;
832  break;
833  case 0xFFFE:
835  get<0>(res) += 2;
836  }
837  }
838  const std::uint16_t *pos = reinterpret_cast<const std::uint16_t *>(get<0>(res));
839  for (; *pos != 0x0000; ++pos) {
840  if (pos < reinterpret_cast<const std::uint16_t *>(get<2>(res))) {
841  get<1>(res) += 2;
842  } else {
843  if (addWarnings) {
844  diag.emplace_back(
845  DiagLevel::Warning, "Wide string in frame is not terminated properly.", "parsing termination of frame " + idToString());
846  }
847  break;
848  }
849  }
850  get<2>(res) = reinterpret_cast<const char *>(pos + 1);
851  break;
852  }
853  }
854  return res;
855 }
856 
862 string Id3v2Frame::parseString(const char *buffer, size_t dataSize, TagTextEncoding &encoding, bool addWarnings, Diagnostics &diag)
863 {
864  return stringFromSubstring(parseSubstring(buffer, dataSize, encoding, addWarnings, diag));
865 }
866 
874 u16string Id3v2Frame::parseWideString(const char *buffer, size_t dataSize, TagTextEncoding &encoding, bool addWarnings, Diagnostics &diag)
875 {
876  return wideStringFromSubstring(parseSubstring(buffer, dataSize, encoding, addWarnings, diag), encoding);
877 }
878 
886 void Id3v2Frame::parseBom(const char *buffer, std::size_t maxSize, TagTextEncoding &encoding, Diagnostics &diag)
887 {
888  switch (encoding) {
891  if ((maxSize >= 2) && (BE::toUInt16(buffer) == 0xFFFE)) {
893  } else if ((maxSize >= 2) && (BE::toUInt16(buffer) == 0xFEFF)) {
895  }
896  break;
897  default:
898  if ((maxSize >= 3) && (BE::toUInt24(buffer) == 0x00EFBBBF)) {
899  encoding = TagTextEncoding::Utf8;
900  diag.emplace_back(DiagLevel::Warning, "UTF-8 byte order mark found in text frame.", "parsing byte oder mark of frame " + idToString());
901  }
902  }
903 }
904 
912 void Id3v2Frame::parseLegacyPicture(const char *buffer, std::size_t maxSize, TagValue &tagValue, std::uint8_t &typeInfo, Diagnostics &diag)
913 {
914  static const string context("parsing ID3v2.2 picture frame");
915  if (maxSize < 6) {
916  diag.emplace_back(DiagLevel::Critical, "Picture frame is incomplete.", context);
917  throw TruncatedDataException();
918  }
919  const char *end = buffer + maxSize;
920  auto dataEncoding = parseTextEncodingByte(static_cast<std::uint8_t>(*buffer), diag); // the first byte stores the encoding
921  typeInfo = static_cast<unsigned char>(*(buffer + 4));
922  auto substr = parseSubstring(buffer + 5, static_cast<size_t>(end - 5 - buffer), dataEncoding, true, diag);
923  tagValue.setDescription(string(get<0>(substr), get<1>(substr)), dataEncoding);
924  if (get<2>(substr) >= end) {
925  diag.emplace_back(DiagLevel::Critical, "Picture frame is incomplete (actual data is missing).", context);
926  throw TruncatedDataException();
927  }
928  tagValue.assignData(get<2>(substr), static_cast<size_t>(end - get<2>(substr)), TagDataType::Picture, dataEncoding);
929 }
930 
938 void Id3v2Frame::parsePicture(const char *buffer, std::size_t maxSize, TagValue &tagValue, std::uint8_t &typeInfo, Diagnostics &diag)
939 {
940  static const string context("parsing ID3v2.3 picture frame");
941  const char *end = buffer + maxSize;
942  auto dataEncoding = parseTextEncodingByte(static_cast<std::uint8_t>(*buffer), diag); // the first byte stores the encoding
943  auto mimeTypeEncoding = TagTextEncoding::Latin1;
944  auto substr = parseSubstring(buffer + 1, maxSize - 1, mimeTypeEncoding, true, diag);
945  if (get<1>(substr)) {
946  tagValue.setMimeType(string(get<0>(substr), get<1>(substr)));
947  }
948  if (get<2>(substr) >= end) {
949  diag.emplace_back(DiagLevel::Critical, "Picture frame is incomplete (type info, description and actual data are missing).", context);
950  throw TruncatedDataException();
951  }
952  typeInfo = static_cast<unsigned char>(*get<2>(substr));
953  if (++get<2>(substr) >= end) {
954  diag.emplace_back(DiagLevel::Critical, "Picture frame is incomplete (description and actual data are missing).", context);
955  throw TruncatedDataException();
956  }
957  substr = parseSubstring(get<2>(substr), static_cast<size_t>(end - get<2>(substr)), dataEncoding, true, diag);
958  tagValue.setDescription(string(get<0>(substr), get<1>(substr)), dataEncoding);
959  if (get<2>(substr) >= end) {
960  diag.emplace_back(DiagLevel::Critical, "Picture frame is incomplete (actual data is missing).", context);
961  throw TruncatedDataException();
962  }
963  tagValue.assignData(get<2>(substr), static_cast<size_t>(end - get<2>(substr)), TagDataType::Picture, dataEncoding);
964 }
965 
972 void Id3v2Frame::parseComment(const char *buffer, std::size_t dataSize, TagValue &tagValue, Diagnostics &diag)
973 {
974  static const string context("parsing comment/unsynchronized lyrics frame");
975  const char *end = buffer + dataSize;
976  if (dataSize < 5) {
977  diag.emplace_back(DiagLevel::Critical, "Comment frame is incomplete.", context);
978  throw TruncatedDataException();
979  }
980  TagTextEncoding dataEncoding = parseTextEncodingByte(static_cast<std::uint8_t>(*buffer), diag);
981  if (*(++buffer)) {
982  tagValue.setLanguage(string(buffer, 3));
983  }
984  auto substr = parseSubstring(buffer += 3, dataSize -= 4, dataEncoding, true, diag);
985  tagValue.setDescription(string(get<0>(substr), get<1>(substr)), dataEncoding);
986  if (get<2>(substr) > end) {
987  diag.emplace_back(DiagLevel::Critical, "Comment frame is incomplete (description not terminated?).", context);
988  throw TruncatedDataException();
989  }
990  substr = parseSubstring(get<2>(substr), static_cast<size_t>(end - get<2>(substr)), dataEncoding, false, diag);
991  tagValue.assignData(get<0>(substr), get<1>(substr), TagDataType::Text, dataEncoding);
992 }
993 
998 size_t Id3v2Frame::makeBom(char *buffer, TagTextEncoding encoding)
999 {
1000  switch (encoding) {
1002  LE::getBytes(static_cast<std::uint16_t>(0xFEFF), buffer);
1003  return 2;
1005  BE::getBytes(static_cast<std::uint16_t>(0xFEFF), buffer);
1006  return 2;
1007  default:
1008  return 0;
1009  }
1010 }
1011 
1016  unique_ptr<char[]> &buffer, std::uint32_t &bufferSize, const TagValue &picture, std::uint8_t typeInfo, Diagnostics &diag)
1017 {
1018  // determine description
1019  TagTextEncoding descriptionEncoding = picture.descriptionEncoding();
1020  StringData convertedDescription;
1021  string::size_type descriptionSize = picture.description().find(
1022  "\0\0", 0, descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? 2 : 1);
1023  if (descriptionSize == string::npos) {
1024  descriptionSize = picture.description().size();
1025  }
1026  if (descriptionEncoding == TagTextEncoding::Utf8) {
1027  // UTF-8 is only supported by ID3v2.4, so convert back to UTF-16
1028  descriptionEncoding = TagTextEncoding::Utf16LittleEndian;
1029  convertedDescription = convertUtf8ToUtf16LE(picture.description().data(), descriptionSize);
1030  descriptionSize = convertedDescription.second;
1031  }
1032 
1033  // calculate needed buffer size and create buffer
1034  // note: encoding byte + image format + picture type byte + description size + 1 or 2 null bytes (depends on encoding) + data size
1035  const auto requiredBufferSize = 1 + 3 + 1 + descriptionSize
1036  + (descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? 4 : 1)
1037  + picture.dataSize();
1038  if (requiredBufferSize > numeric_limits<std::uint32_t>::max()) {
1039  diag.emplace_back(DiagLevel::Critical, "Required size exceeds maximum.", "making legacy picture frame");
1040  throw InvalidDataException();
1041  }
1042  buffer = make_unique<char[]>(bufferSize = static_cast<std::uint32_t>(requiredBufferSize));
1043  char *offset = buffer.get();
1044 
1045  // write encoding byte
1046  *offset = static_cast<char>(makeTextEncodingByte(descriptionEncoding));
1047 
1048  // write mime type
1049  const char *imageFormat;
1050  if (picture.mimeType() == "image/jpeg") {
1051  imageFormat = "JPG";
1052  } else if (picture.mimeType() == "image/png") {
1053  imageFormat = "PNG";
1054  } else if (picture.mimeType() == "image/gif") {
1055  imageFormat = "GIF";
1056  } else if (picture.mimeType() == "-->") {
1057  imageFormat = picture.mimeType().data();
1058  } else {
1059  imageFormat = "UND";
1060  }
1061  strncpy(++offset, imageFormat, 3);
1062 
1063  // write picture type
1064  *(offset += 3) = static_cast<char>(typeInfo);
1065 
1066  // write description
1067  offset += makeBom(offset + 1, descriptionEncoding);
1068  if (convertedDescription.first) {
1069  copy(convertedDescription.first.get(), convertedDescription.first.get() + descriptionSize, ++offset);
1070  } else {
1071  picture.description().copy(++offset, descriptionSize);
1072  }
1073  *(offset += descriptionSize) = 0x00; // terminate description and increase data offset
1074  if (descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian) {
1075  *(++offset) = 0x00;
1076  }
1077 
1078  // write actual data
1079  copy(picture.dataPointer(), picture.dataPointer() + picture.dataSize(), ++offset);
1080 }
1081 
1085 void Id3v2Frame::makePicture(std::unique_ptr<char[]> &buffer, std::uint32_t &bufferSize, const TagValue &picture, std::uint8_t typeInfo,
1086  std::uint8_t version, Diagnostics &diag)
1087 {
1088  if (version < 3) {
1089  makeLegacyPicture(buffer, bufferSize, picture, typeInfo, diag);
1090  return;
1091  }
1092 
1093  // determine description
1094  TagTextEncoding descriptionEncoding = picture.descriptionEncoding();
1095  StringData convertedDescription;
1096  string::size_type descriptionSize = picture.description().find(
1097  "\0\0", 0, descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? 2 : 1);
1098  if (descriptionSize == string::npos) {
1099  descriptionSize = picture.description().size();
1100  }
1101  if (version < 4 && descriptionEncoding == TagTextEncoding::Utf8) {
1102  // UTF-8 is only supported by ID3v2.4, so convert back to UTF-16
1103  descriptionEncoding = TagTextEncoding::Utf16LittleEndian;
1104  convertedDescription = convertUtf8ToUtf16LE(picture.description().data(), descriptionSize);
1105  descriptionSize = convertedDescription.second;
1106  }
1107  // determine mime-type
1108  string::size_type mimeTypeSize = picture.mimeType().find('\0');
1109  if (mimeTypeSize == string::npos) {
1110  mimeTypeSize = picture.mimeType().length();
1111  }
1112 
1113  // calculate needed buffer size and create buffer
1114  // note: encoding byte + mime type size + 0 byte + picture type byte + description size + 1 or 4 null bytes (depends on encoding) + data size
1115  const auto requiredBufferSize = 1 + mimeTypeSize + 1 + 1 + descriptionSize
1116  + (descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? 4 : 1)
1117  + picture.dataSize();
1118  if (requiredBufferSize > numeric_limits<uint32_t>::max()) {
1119  diag.emplace_back(DiagLevel::Critical, "Required size exceeds maximum.", "making picture frame");
1120  throw InvalidDataException();
1121  }
1122  buffer = make_unique<char[]>(bufferSize = static_cast<uint32_t>(requiredBufferSize));
1123  char *offset = buffer.get();
1124 
1125  // write encoding byte
1126  *offset = static_cast<char>(makeTextEncodingByte(descriptionEncoding));
1127 
1128  // write mime type
1129  picture.mimeType().copy(++offset, mimeTypeSize);
1130 
1131  *(offset += mimeTypeSize) = 0x00; // terminate mime type
1132  // write picture type
1133  *(++offset) = static_cast<char>(typeInfo);
1134 
1135  // write description
1136  offset += makeBom(offset + 1, descriptionEncoding);
1137  if (convertedDescription.first) {
1138  copy(convertedDescription.first.get(), convertedDescription.first.get() + descriptionSize, ++offset);
1139  } else {
1140  picture.description().copy(++offset, descriptionSize);
1141  }
1142  *(offset += descriptionSize) = 0x00; // terminate description and increase data offset
1143  if (descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian) {
1144  *(++offset) = 0x00;
1145  }
1146 
1147  // write actual data
1148  copy(picture.dataPointer(), picture.dataPointer() + picture.dataSize(), ++offset);
1149 }
1150 
1154 void Id3v2Frame::makeComment(unique_ptr<char[]> &buffer, std::uint32_t &bufferSize, const TagValue &comment, std::uint8_t version, Diagnostics &diag)
1155 {
1156  static const string context("making comment frame");
1157 
1158  // check whether type and other values are valid
1159  TagTextEncoding encoding = comment.dataEncoding();
1160  if (!comment.description().empty() && encoding != comment.descriptionEncoding()) {
1161  diag.emplace_back(DiagLevel::Critical, "Data encoding and description encoding aren't equal.", context);
1162  throw InvalidDataException();
1163  }
1164  const string &lng = comment.language();
1165  if (lng.length() > 3) {
1166  diag.emplace_back(DiagLevel::Critical, "The language must be 3 bytes long (ISO-639-2).", context);
1167  throw InvalidDataException();
1168  }
1169  StringData convertedDescription;
1170  string::size_type descriptionSize = comment.description().find(
1171  "\0\0", 0, encoding == TagTextEncoding::Utf16BigEndian || encoding == TagTextEncoding::Utf16LittleEndian ? 2 : 1);
1172  if (descriptionSize == string::npos) {
1173  descriptionSize = comment.description().size();
1174  }
1175  if (version < 4 && encoding == TagTextEncoding::Utf8) {
1176  // UTF-8 is only supported by ID3v2.4, so convert back to UTF-16
1178  convertedDescription = convertUtf8ToUtf16LE(comment.description().data(), descriptionSize);
1179  descriptionSize = convertedDescription.second;
1180  }
1181 
1182  // calculate needed buffer size and create buffer
1183  // note: encoding byte + language + description size + actual data size + BOMs and termination
1184  const auto data = comment.toString(encoding);
1185  const auto requiredBufferSize = 1 + 3 + descriptionSize + data.size()
1186  + (encoding == TagTextEncoding::Utf16BigEndian || encoding == TagTextEncoding::Utf16LittleEndian ? 6 : 1) + data.size();
1187  if (requiredBufferSize > numeric_limits<uint32_t>::max()) {
1188  diag.emplace_back(DiagLevel::Critical, "Required size exceeds maximum.", context);
1189  throw InvalidDataException();
1190  }
1191  buffer = make_unique<char[]>(bufferSize = static_cast<uint32_t>(requiredBufferSize));
1192  char *offset = buffer.get();
1193 
1194  // write encoding
1195  *offset = static_cast<char>(makeTextEncodingByte(encoding));
1196 
1197  // write language
1198  for (unsigned int i = 0; i < 3; ++i) {
1199  *(++offset) = (lng.length() > i) ? lng[i] : 0x00;
1200  }
1201 
1202  // write description
1203  offset += makeBom(offset + 1, encoding);
1204  if (convertedDescription.first) {
1205  copy(convertedDescription.first.get(), convertedDescription.first.get() + descriptionSize, ++offset);
1206  } else {
1207  comment.description().copy(++offset, descriptionSize);
1208  }
1209  offset += descriptionSize;
1210  *offset = 0x00; // terminate description and increase data offset
1212  *(++offset) = 0x00;
1213  }
1214 
1215  // write actual data
1216  offset += makeBom(offset + 1, encoding);
1217  data.copy(++offset, data.size());
1218 }
1219 
1220 } // namespace TagParser
TagParser::TagValue::mimeType
const std::string & mimeType() const
Returns the MIME type.
Definition: tagvalue.h:552
TagParser::Id3v2FrameIds::sDiskPosition
@ sDiskPosition
Definition: id3v2frameids.h:59
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:378
TagParser::Id3v2Frame::parseTextEncodingByte
TagTextEncoding parseTextEncodingByte(std::uint8_t textEncodingByte, Diagnostics &diag)
Returns the text encoding for the specified textEncodingByte.
Definition: id3v2frame.cpp:738
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::VorbisCommentIds::version
constexpr TAG_PARSER_EXPORT const char * version()
Definition: vorbiscommentids.h:34
TagParser::Id3v2FrameIds::lCover
@ lCover
Definition: id3v2frameids.h:33
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:486
TagParser::Id3v2FrameIds::isOnlyId3v24Id
TAG_PARSER_EXPORT bool isOnlyId3v24Id(std::uint32_t id)
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:628
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:365
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:911
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:86
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:862
TagParser::Diagnostics
The Diagnostics class is a container for DiagMessage.
Definition: diagnostics.h:156
TagParser::Id3v2FrameIds::sTrackPosition
@ sTrackPosition
Definition: id3v2frameids.h:58
TagParser::Id3v2FrameIds::sCover
@ sCover
Definition: id3v2frameids.h:62
TagParser::TagDataType::PositionInSet
@ PositionInSet
TagParser::TagDataType::StandardGenreIndex
@ StandardGenreIndex
TagParser
Contains all classes and functions of the TagInfo library.
Definition: aaccodebook.h:10
TagParser::Id3v2FrameIds::isPreId3v24Id
TAG_PARSER_EXPORT bool isPreId3v24Id(std::uint32_t id)
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:886
TagParser::Id3v2FrameIds::lComment
@ lComment
Definition: id3v2frameids.h:16
TagParser::Id3v2TextEncodingBytes::Utf8
@ Utf8
Definition: id3v2frame.cpp:24
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:1154
TagParser::Id3v2FrameIds::lTrackPosition
@ lTrackPosition
Definition: id3v2frameids.h:29
TagParser::Id3v2Frame::makeTextEncodingByte
static std::uint8_t makeTextEncodingByte(TagTextEncoding textEncoding)
Returns a text encoding byte for the specified textEncoding.
Definition: id3v2frame.cpp:759
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:874
TagParser::Id3v2Frame::isEncrypted
bool isEncrypted() const
Returns whether the frame is encrypted.
Definition: id3v2frame.h:269
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:94
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:972
TagParser::TagValue::description
const std::string & description() const
Returns the description.
Definition: tagvalue.h:526
TagParser::Id3v2FrameIds::lLength
@ lLength
Definition: id3v2frameids.h:35
TagParser::TagValue::setMimeType
void setMimeType(const std::string &mimeType)
Sets the MIME type.
Definition: tagvalue.h:562
TagParser::Id3v2FrameIds::lGenre
@ lGenre
Definition: id3v2frameids.h:28
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 no or an empty value is assigned.
Definition: tagvalue.h:464
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:641
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:102
TagParser::TagField::value
TagValue & value()
Returns the value of the current TagField.
Definition: generictagfield.h:144
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:912
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
TAG_PARSER_EXPORT std::uint32_t convertToShortId(std::uint32_t id)
Converts the specified long frame ID to the equivalent short frame ID.
Definition: id3v2frameids.cpp:27
TagParser::Id3v2FrameIds::lDiskPosition
@ lDiskPosition
Definition: id3v2frameids.h:30
TagParser::Id3v2FrameIds::sLength
@ sLength
Definition: id3v2frameids.h:64
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:938
TagParser::Id3v2FrameIds::lUnsynchronizedLyrics
@ lUnsynchronizedLyrics
Definition: id3v2frameids.h:38
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:396
TagParser::TagValue::dataPointer
char * dataPointer()
Returns a pointer to the raw data assigned to the current instance.
Definition: tagvalue.h:507
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:998
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::MatroskaTagIds::comment
constexpr TAG_PARSER_EXPORT const char * comment()
Definition: matroskatagid.h:306
TagParser::TagValue::dataSize
std::size_t dataSize() const
Returns the size of the assigned value in bytes.
Definition: tagvalue.h:496
TagParser::Id3v2FrameIds::sUnsynchronizedLyrics
@ sUnsynchronizedLyrics
Definition: id3v2frameids.h:67
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:541
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:789
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:393
TagParser::Id3v2FrameIds::sGenre
@ sGenre
Definition: id3v2frameids.h:57
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:422
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:380
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:405
TagParser::Id3v2FrameIds::sComment
@ sComment
Definition: id3v2frameids.h:50
id3v2frame.h
TagParser::TagValue::dataEncoding
TagTextEncoding dataEncoding() const
Returns the data encoding.
Definition: tagvalue.h:618
TagParser::TagValue::assignData
void assignData(const char *data, std::size_t length, TagDataType type=TagDataType::Binary, TagTextEncoding encoding=TagTextEncoding::Latin1)
TagParser::Id3v2FrameIds::convertToLongId
TAG_PARSER_EXPORT std::uint32_t convertToLongId(std::uint32_t id)
Converts the specified short frame ID to the equivalent long frame ID.
Definition: id3v2frameids.cpp:87
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:1015
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:582
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:1085
TagParser::Id3v2TextEncodingBytes::Id3v2TextEncodingByte
Id3v2TextEncodingByte
Definition: id3v2frame.cpp:24
TagParser::TagField::idToString
std::string idToString() const
Definition: generictagfield.h:120
TagParser::MatroskaTagIds::duration
constexpr TAG_PARSER_EXPORT const char * duration()
Definition: matroskatagid.h:339
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:705