Tag Parser  9.4.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 
24 namespace Id3v2TextEncodingBytes {
25 enum Id3v2TextEncodingByte : std::uint8_t { Ascii, Utf16WithBom, Utf16BigEndianWithoutBom, Utf8 };
26 }
28 
30 constexpr auto maxId3v2FrameDataSize(numeric_limits<std::uint32_t>::max() - 15);
31 
40 Id3v2Frame::Id3v2Frame()
41  : m_parsedVersion(0)
42  , m_dataSize(0)
43  , m_totalSize(0)
44  , m_flag(0)
45  , m_group(0)
46  , m_padding(false)
47 {
48 }
49 
53 Id3v2Frame::Id3v2Frame(const IdentifierType &id, const TagValue &value, std::uint8_t group, std::uint16_t flag)
54  : TagField<Id3v2Frame>(id, value)
55  , m_parsedVersion(0)
56  , m_dataSize(0)
57  , m_totalSize(0)
58  , m_flag(flag)
59  , m_group(group)
60  , m_padding(false)
61 {
62 }
63 
68 template <class stringtype> int parseGenreIndex(const stringtype &denotation)
69 {
70  int index = -1;
71  for (auto c : denotation) {
72  if (index == -1) {
73  switch (c) {
74  case ' ':
75  break;
76  case '(':
77  index = 0;
78  break;
79  case '\0':
80  return -1;
81  default:
82  if (c >= '0' && c <= '9') {
83  index = c - '0';
84  } else {
85  return -1;
86  }
87  }
88  } else {
89  switch (c) {
90  case ')':
91  return index;
92  case '\0':
93  return index;
94  default:
95  if (c >= '0' && c <= '9') {
96  index = index * 10 + c - '0';
97  } else {
98  return -1;
99  }
100  }
101  }
102  }
103  return index;
104 }
105 
109 string stringFromSubstring(tuple<const char *, size_t, const char *> substr)
110 {
111  return string(get<0>(substr), get<1>(substr));
112 }
113 
117 u16string wideStringFromSubstring(tuple<const char *, size_t, const char *> substr, TagTextEncoding encoding)
118 {
119  u16string res(reinterpret_cast<u16string::const_pointer>(get<0>(substr)), get<1>(substr) / 2);
120  TagValue::ensureHostByteOrder(res, encoding);
121  return res;
122 }
123 
134 void Id3v2Frame::parse(BinaryReader &reader, std::uint32_t version, std::uint32_t maximalSize, Diagnostics &diag)
135 {
136  static const string defaultContext("parsing ID3v2 frame");
137  string context;
138 
139  // parse header
140  if (version < 3) {
141  // parse header for ID3v2.1 and ID3v2.2
142  // -> read ID
143  setId(reader.readUInt24BE());
144  if (id() & 0xFFFF0000u) {
145  m_padding = false;
146  } else {
147  // padding reached
148  m_padding = true;
149  throw NoDataFoundException();
150  }
151 
152  // -> update context
153  context = "parsing " % idToString() + " frame";
154 
155  // -> read size, check whether frame is truncated
156  m_dataSize = reader.readUInt24BE();
157  m_totalSize = m_dataSize + 6;
158  if (m_totalSize > maximalSize) {
159  diag.emplace_back(DiagLevel::Warning, "The frame is truncated and will be ignored.", context);
160  throw TruncatedDataException();
161  }
162 
163  // -> no flags/group in ID3v2.2
164  m_flag = 0;
165  m_group = 0;
166 
167  } else {
168  // parse header for ID3v2.3 and ID3v2.4
169  // -> read ID
170  setId(reader.readUInt32BE());
171  if (id() & 0xFF000000u) {
172  m_padding = false;
173  } else {
174  // padding reached
175  m_padding = true;
176  throw NoDataFoundException();
177  }
178 
179  // -> update context
180  context = "parsing " % idToString() + " frame";
181 
182  // -> read size, check whether frame is truncated
183  m_dataSize = version >= 4 ? reader.readSynchsafeUInt32BE() : reader.readUInt32BE();
184  m_totalSize = m_dataSize + 10;
185  if (m_totalSize > maximalSize) {
186  diag.emplace_back(DiagLevel::Warning, "The frame is truncated and will be ignored.", context);
187  throw TruncatedDataException();
188  }
189 
190  // -> read flags and group
191  m_flag = reader.readUInt16BE();
192  m_group = hasGroupInformation() ? reader.readByte() : 0;
193  if (isEncrypted()) {
194  // encryption is not implemented
195  diag.emplace_back(DiagLevel::Critical, "Encrypted frames aren't supported.", context);
197  }
198  }
199 
200  // add a warning if a frame appears in an ID3v2 tag known not to support it
202  diag.emplace_back(DiagLevel::Warning,
203  argsToString("The frame is only supported in ID3v2.4 and newer but the tag's version is ID3v2.", version, '.'), context);
204  } else if (version > 3 && Id3v2FrameIds::isPreId3v24Id(id())) {
205  diag.emplace_back(DiagLevel::Warning,
206  argsToString("The frame is only supported in ID3v2.3 and older but the tag's version is ID3v2.", version, '.'), context);
207  }
208 
209  // frame size mustn't be 0
210  if (m_dataSize <= 0) {
211  diag.emplace_back(DiagLevel::Warning, "The frame size is 0.", context);
212  throw InvalidDataException();
213  }
214 
215  // parse the data
216  unique_ptr<char[]> buffer;
217 
218  // -> decompress data if compressed; otherwise just read it
219  if (isCompressed()) {
220  uLongf decompressedSize = version >= 4 ? reader.readSynchsafeUInt32BE() : reader.readUInt32BE();
221  if (decompressedSize < m_dataSize) {
222  diag.emplace_back(DiagLevel::Critical, "The decompressed size is smaller than the compressed size.", context);
223  throw InvalidDataException();
224  }
225  const auto bufferCompressed = make_unique<char[]>(m_dataSize);
226  reader.read(bufferCompressed.get(), m_dataSize);
227  buffer = make_unique<char[]>(decompressedSize);
228  switch (
229  uncompress(reinterpret_cast<Bytef *>(buffer.get()), &decompressedSize, reinterpret_cast<Bytef *>(bufferCompressed.get()), m_dataSize)) {
230  case Z_MEM_ERROR:
231  diag.emplace_back(DiagLevel::Critical, "Decompressing failed. The source buffer was too small.", context);
232  throw InvalidDataException();
233  case Z_BUF_ERROR:
234  diag.emplace_back(DiagLevel::Critical, "Decompressing failed. The destination buffer was too small.", context);
235  throw InvalidDataException();
236  case Z_DATA_ERROR:
237  diag.emplace_back(DiagLevel::Critical, "Decompressing failed. The input data was corrupted or incomplete.", context);
238  throw InvalidDataException();
239  case Z_OK:
240  break;
241  default:
242  diag.emplace_back(DiagLevel::Critical, "Decompressing failed (unknown reason).", context);
243  throw InvalidDataException();
244  }
245  if (decompressedSize > maxId3v2FrameDataSize) {
246  diag.emplace_back(DiagLevel::Critical, "The decompressed data exceeds the maximum supported frame size.", context);
247  throw InvalidDataException();
248  }
249  m_dataSize = static_cast<std::uint32_t>(decompressedSize);
250  } else {
251  buffer = make_unique<char[]>(m_dataSize);
252  reader.read(buffer.get(), m_dataSize);
253  }
254 
255  // read tag value depending on frame ID/type
256  if (Id3v2FrameIds::isTextFrame(id())) {
257  // parse text encoding byte
258  TagTextEncoding dataEncoding = parseTextEncodingByte(static_cast<std::uint8_t>(*buffer.get()), diag);
259 
260  // parse string values (since ID3v2.4 a text frame may contain multiple strings)
261  const char *currentOffset = buffer.get() + 1;
262  for (size_t currentIndex = 1; currentIndex < m_dataSize;) {
263  // determine the next substring
264  const auto substr(parseSubstring(currentOffset, m_dataSize - currentIndex, dataEncoding, false, diag));
265 
266  // handle case when string is empty
267  if (!get<1>(substr)) {
268  if (currentIndex == 1) {
270  }
271  currentIndex = static_cast<size_t>(get<2>(substr) - buffer.get());
272  currentOffset = get<2>(substr);
273  continue;
274  }
275 
276  // determine the TagValue instance to store the value
277  TagValue *const value = [&] {
278  if (this->value().isEmpty()) {
279  return &this->value();
280  }
281  m_additionalValues.emplace_back();
282  return &m_additionalValues.back();
283  }();
284 
285  // apply further parsing for some text frame types (eg. convert track number to PositionInSet)
286  if ((version >= 3 && (id() == Id3v2FrameIds::lTrackPosition || id() == Id3v2FrameIds::lDiskPosition))
288  // parse the track number or the disk number frame
289  try {
290  if (characterSize(dataEncoding) > 1) {
292  } else {
294  }
295  } catch (const ConversionException &) {
296  diag.emplace_back(DiagLevel::Warning, "The value of track/disk position frame is not numeric and will be ignored.", context);
297  }
298 
299  } else if ((version >= 3 && id() == Id3v2FrameIds::lLength) || (version < 3 && id() == Id3v2FrameIds::sLength)) {
300  // parse frame contains length
301  try {
302  const auto milliseconds = [&] {
303  if (dataEncoding == TagTextEncoding::Utf16BigEndian || dataEncoding == TagTextEncoding::Utf16LittleEndian) {
304  const auto parsedStringRef = parseSubstring(buffer.get() + 1, m_dataSize - 1, dataEncoding, false, diag);
305  const auto convertedStringData = dataEncoding == TagTextEncoding::Utf16BigEndian
306  ? convertUtf16BEToUtf8(get<0>(parsedStringRef), get<1>(parsedStringRef))
307  : convertUtf16LEToUtf8(get<0>(parsedStringRef), get<1>(parsedStringRef));
308  return string(convertedStringData.first.get(), convertedStringData.second);
309  } else { // Latin-1 or UTF-8
310  return stringFromSubstring(substr);
311  }
312  }();
313  value->assignTimeSpan(TimeSpan::fromMilliseconds(stringToNumber<double>(milliseconds)));
314  } catch (const ConversionException &) {
315  diag.emplace_back(DiagLevel::Warning, "The value of the length frame is not numeric and will be ignored.", context);
316  }
317 
318  } else if ((version >= 3 && id() == Id3v2FrameIds::lGenre) || (version < 3 && id() == Id3v2FrameIds::sGenre)) {
319  // parse genre/content type
320  const auto genreIndex = [&] {
321  if (characterSize(dataEncoding) > 1) {
322  return parseGenreIndex(wideStringFromSubstring(substr, dataEncoding));
323  } else {
324  return parseGenreIndex(stringFromSubstring(substr));
325  }
326  }();
327  if (genreIndex != -1) {
328  // genre is specified as ID3 genre number
329  value->assignStandardGenreIndex(genreIndex);
330  } else {
331  // genre is specified as string
332  value->assignData(get<0>(substr), get<1>(substr), TagDataType::Text, dataEncoding);
333  }
334  } else {
335  // store any other text frames as-is
336  value->assignData(get<0>(substr), get<1>(substr), TagDataType::Text, dataEncoding);
337  }
338 
339  currentIndex = static_cast<size_t>(get<2>(substr) - buffer.get());
340  currentOffset = get<2>(substr);
341  }
342 
343  // add warning about additional values
344  if (version < 4 && !m_additionalValues.empty()) {
345  diag.emplace_back(
346  DiagLevel::Warning, "Multiple strings found though the tag is pre-ID3v2.4. " + ignoreAdditionalValuesDiagMsg(), context);
347  }
348 
349  } else if (version >= 3 && id() == Id3v2FrameIds::lCover) {
350  // parse picture frame
351  std::uint8_t type;
352  parsePicture(buffer.get(), m_dataSize, value(), type, diag);
353  setTypeInfo(type);
354 
355  } else if (version < 3 && id() == Id3v2FrameIds::sCover) {
356  // parse legacy picutre
357  std::uint8_t type;
358  parseLegacyPicture(buffer.get(), m_dataSize, value(), type, diag);
359  setTypeInfo(type);
360 
361  } else if (((version >= 3 && id() == Id3v2FrameIds::lComment) || (version < 3 && id() == Id3v2FrameIds::sComment))
363  // parse comment frame or unsynchronized lyrics frame (these two frame types have the same structure)
364  parseComment(buffer.get(), m_dataSize, value(), diag);
365 
366  } else {
367  // parse unknown/unsupported frame
368  value().assignData(buffer.get(), m_dataSize, TagDataType::Undefined);
369  }
370 }
371 
383 {
384  return Id3v2FrameMaker(*this, version, diag);
385 }
386 
395 void Id3v2Frame::make(BinaryWriter &writer, std::uint8_t version, Diagnostics &diag)
396 {
397  prepareMaking(version, diag).make(writer);
398 }
399 
403 void Id3v2Frame::reset()
404 {
405  m_flag = 0;
406  m_group = 0;
407  m_parsedVersion = 0;
408  m_dataSize = 0;
409  m_totalSize = 0;
410  m_padding = false;
411  m_additionalValues.clear();
412 }
413 
417 std::string Id3v2Frame::ignoreAdditionalValuesDiagMsg() const
418 {
419  if (m_additionalValues.size() == 1) {
420  return argsToString("Additional value \"", m_additionalValues.front().toString(TagTextEncoding::Utf8), "\" is supposed to be ignored.");
421  }
422  return argsToString("Additional values ", DiagMessage::formatList(TagValue::toStrings(m_additionalValues)), " are supposed to be ignored.");
423 }
424 
436 Id3v2FrameMaker::Id3v2FrameMaker(Id3v2Frame &frame, std::uint8_t version, Diagnostics &diag)
437  : m_frame(frame)
438  , m_frameId(m_frame.id())
439  , m_version(version)
440 {
441  const string context("making " % m_frame.idToString() + " frame");
442 
443  // validate frame's configuration
444  if (m_frame.isEncrypted()) {
445  diag.emplace_back(DiagLevel::Critical, "Cannot make an encrypted frame (isn't supported by this tagging library).", context);
446  throw InvalidDataException();
447  }
448  if (m_frame.hasPaddingReached()) {
449  diag.emplace_back(DiagLevel::Critical, "Cannot make a frame which is marked as padding.", context);
450  throw InvalidDataException();
451  }
452  if (version < 3 && m_frame.isCompressed()) {
453  diag.emplace_back(DiagLevel::Warning, "Compression is not supported by the version of ID3v2 and won't be applied.", context);
454  }
455  if (version < 3 && (m_frame.flag() || m_frame.group())) {
456  diag.emplace_back(DiagLevel::Warning,
457  "The existing flag and group information is not supported by the version of ID3v2 and will be ignored/discarted.", context);
458  }
459 
460  // get non-empty, assigned values
461  vector<const TagValue *> values;
462  values.reserve(1 + frame.additionalValues().size());
463  if (!frame.value().isEmpty()) {
464  values.emplace_back(&frame.value());
465  }
466  for (const auto &value : frame.additionalValues()) {
467  if (!value.isEmpty()) {
468  values.emplace_back(&value);
469  }
470  }
471 
472  // validate assigned values
473  if (values.empty()) {
474  throw NoDataProvidedException();
475  // note: This is not really an issue because in the case we're not provided with any value here just means that the field
476  // is supposed to be removed. So don't add any diagnostic messages here.
477  }
478  const bool isTextFrame = Id3v2FrameIds::isTextFrame(m_frameId);
479  if (values.size() != 1) {
480  if (!isTextFrame) {
481  diag.emplace_back(DiagLevel::Critical, "Multiple values are not supported for non-text-frames.", context);
482  throw InvalidDataException();
483  } else if (version < 4) {
484  diag.emplace_back(
485  DiagLevel::Warning, "Multiple strings assigned to pre-ID3v2.4 text frame. " + frame.ignoreAdditionalValuesDiagMsg(), context);
486  }
487  }
488 
489  // convert frame ID if necessary
490  if (version >= 3) {
491  if (Id3v2FrameIds::isShortId(m_frameId)) {
492  // try to convert the short frame ID to its long equivalent
493  if (!(m_frameId = Id3v2FrameIds::convertToLongId(m_frameId))) {
494  diag.emplace_back(DiagLevel::Critical,
495  "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.",
496  context);
497  throw InvalidDataException();
498  }
499  }
500  } else {
501  if (Id3v2FrameIds::isLongId(m_frameId)) {
502  // try to convert the long frame ID to its short equivalent
503  if (!(m_frameId = Id3v2FrameIds::convertToShortId(m_frameId))) {
504  diag.emplace_back(DiagLevel::Critical,
505  "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.",
506  context);
507  throw InvalidDataException();
508  }
509  }
510  }
511 
512  // add a warning if we're writing the frame for an ID3v2 tag known not to support it
513  if (version <= 3 && Id3v2FrameIds::isOnlyId3v24Id(version < 3 ? Id3v2FrameIds::convertToLongId(m_frameId) : m_frameId)) {
514  diag.emplace_back(DiagLevel::Warning,
515  argsToString("The frame is only supported in ID3v2.4 and newer but version of the tag being written is ID3v2.", version,
516  ". The frame is written nevertheless but other tools might not be able to deal with it."),
517  context);
518  } else if (version > 3 && Id3v2FrameIds::isPreId3v24Id(m_frameId)) {
519  diag.emplace_back(DiagLevel::Warning,
520  argsToString("The frame is only supported in ID3v2.3 and older but version of the tag being written is ID3v2.", version,
521  ". The frame is written nevertheless but other tools might not be able to deal with it."),
522  context);
523  }
524 
525  // make actual data depending on the frame ID
526  try {
527  if (isTextFrame) {
528  // make text frame
529  vector<string> substrings;
530  substrings.reserve(1 + frame.additionalValues().size());
532 
533  if ((version >= 3 && (m_frameId == Id3v2FrameIds::lTrackPosition || m_frameId == Id3v2FrameIds::lDiskPosition))
534  || (version < 3 && (m_frameId == Id3v2FrameIds::sTrackPosition || m_frameId == Id3v2FrameIds::sDiskPosition))) {
535  // make track number or disk number frame
536  encoding = version >= 4 ? TagTextEncoding::Utf8 : TagTextEncoding::Latin1;
537  for (const auto *const value : values) {
538  // convert the position to string
539  substrings.emplace_back(value->toString(encoding));
540  // warn if value is no valid position (although we just store a string after all)
542  continue;
543  }
544  try {
546  } catch (const ConversionException &) {
547  diag.emplace_back(DiagLevel::Warning,
548  argsToString("The track/disk number \"", substrings.back(), "\" is not of the expected form, eg. \"4/10\"."), context);
549  }
550  }
551 
552  } else if ((version >= 3 && m_frameId == Id3v2FrameIds::lLength) || (version < 3 && m_frameId == Id3v2FrameIds::sLength)) {
553  // make length frame
554  encoding = TagTextEncoding::Latin1;
555  for (const auto *const value : values) {
556  const auto duration(value->toTimeSpan());
557  if (duration.isNegative()) {
558  diag.emplace_back(DiagLevel::Critical, argsToString("Assigned duration \"", duration.toString(), "\" is negative."), context);
559  throw InvalidDataException();
560  }
561  substrings.emplace_back(numberToString(static_cast<std::uint64_t>(duration.totalMilliseconds())));
562  }
563 
564  } else {
565  // make standard genre index and other text frames
566  // -> find text encoding suitable for all assigned values
567  for (const auto *const value : values) {
568  switch (encoding) {
570  switch (value->type()) {
571  case TagDataType::StandardGenreIndex:
572  encoding = TagTextEncoding::Latin1;
573  break;
574  default:
575  encoding = value->dataEncoding();
576  }
577  break;
578  case TagTextEncoding::Latin1:
579  switch (value->dataEncoding()) {
580  case TagTextEncoding::Latin1:
581  break;
582  default:
583  encoding = value->dataEncoding();
584  }
585  break;
586  default:;
587  }
588  }
589  if (version <= 3 && encoding == TagTextEncoding::Utf8) {
590  encoding = TagTextEncoding::Utf16LittleEndian;
591  }
592  // -> format values
593  for (const auto *const value : values) {
594  if ((value->type() == TagDataType::StandardGenreIndex)
595  && ((version >= 3 && m_frameId == Id3v2FrameIds::lGenre) || (version < 3 && m_frameId == Id3v2FrameIds::sGenre))) {
596  // make standard genere index
597  substrings.emplace_back(numberToString(value->toStandardGenreIndex()));
598 
599  } else {
600  // make other text frame
601  substrings.emplace_back(value->toString(encoding));
602  }
603  }
604  }
605 
606  // concatenate substrings using encoding specific byte order mark and termination
607  const auto terminationLength = (encoding == TagTextEncoding::Utf16BigEndian || encoding == TagTextEncoding::Utf16LittleEndian) ? 2u : 1u;
608  const auto byteOrderMark = [&] {
609  switch (encoding) {
610  case TagTextEncoding::Utf16LittleEndian:
611  return string({ '\xFF', '\xFE' });
612  case TagTextEncoding::Utf16BigEndian:
613  return string({ '\xFE', '\xFF' });
614  default:
615  return string();
616  }
617  }();
618  const auto concatenatedSubstrings = joinStrings(substrings, string(), false, byteOrderMark, string(terminationLength, '\0'));
619 
620  // write text encoding byte and concatenated strings to data buffer
621  m_data = make_unique<char[]>(m_decompressedSize = static_cast<std::uint32_t>(1 + concatenatedSubstrings.size()));
622  m_data[0] = static_cast<char>(Id3v2Frame::makeTextEncodingByte(encoding));
623  concatenatedSubstrings.copy(&m_data[1], concatenatedSubstrings.size());
624 
625  } else if ((version >= 3 && m_frameId == Id3v2FrameIds::lCover) || (version < 3 && m_frameId == Id3v2FrameIds::sCover)) {
626  // make picture frame
627  m_frame.makePicture(m_data, m_decompressedSize, *values.front(), m_frame.isTypeInfoAssigned() ? m_frame.typeInfo() : 0, version, diag);
628 
629  } else if (((version >= 3 && m_frameId == Id3v2FrameIds::lComment) || (version < 3 && m_frameId == Id3v2FrameIds::sComment))
630  || ((version >= 3 && m_frameId == Id3v2FrameIds::lUnsynchronizedLyrics)
631  || (version < 3 && m_frameId == Id3v2FrameIds::sUnsynchronizedLyrics))) {
632  // make comment frame or the unsynchronized lyrics frame
633  m_frame.makeComment(m_data, m_decompressedSize, *values.front(), version, diag);
634 
635  } else {
636  // make unknown frame
637  const auto &value(*values.front());
639  diag.emplace_back(DiagLevel::Critical, "Assigned value exceeds maximum size.", context);
640  throw InvalidDataException();
641  }
642  m_data = make_unique<char[]>(m_decompressedSize = static_cast<std::uint32_t>(value.dataSize()));
643  copy(value.dataPointer(), value.dataPointer() + m_decompressedSize, m_data.get());
644  }
645  } catch (const ConversionException &) {
646  try {
647  const auto valuesAsString = TagValue::toStrings(values);
648  diag.emplace_back(DiagLevel::Critical,
649  argsToString("Assigned value(s) \"", DiagMessage::formatList(valuesAsString), "\" can not be converted appropriately."), context);
650  } catch (const ConversionException &) {
651  diag.emplace_back(DiagLevel::Critical, "Assigned value(s) can not be converted appropriately.", context);
652  }
653  throw InvalidDataException();
654  }
655 
656  // apply compression if frame should be compressed
657  if (version >= 3 && m_frame.isCompressed()) {
658  auto compressedSize = compressBound(m_decompressedSize);
659  auto compressedData = make_unique<char[]>(compressedSize);
660  switch (compress(reinterpret_cast<Bytef *>(compressedData.get()), reinterpret_cast<uLongf *>(&compressedSize),
661  reinterpret_cast<Bytef *>(m_data.get()), m_decompressedSize)) {
662  case Z_MEM_ERROR:
663  diag.emplace_back(DiagLevel::Critical, "Decompressing failed. The source buffer was too small.", context);
664  throw InvalidDataException();
665  case Z_BUF_ERROR:
666  diag.emplace_back(DiagLevel::Critical, "Decompressing failed. The destination buffer was too small.", context);
667  throw InvalidDataException();
668  case Z_OK:;
669  }
670  if (compressedSize > maxId3v2FrameDataSize) {
671  diag.emplace_back(DiagLevel::Critical, "Compressed size exceeds maximum data size.", context);
672  throw InvalidDataException();
673  }
674  m_data.swap(compressedData);
675  m_dataSize = static_cast<std::uint32_t>(compressedSize);
676  } else {
677  m_dataSize = m_decompressedSize;
678  }
679 
680  // calculate required size
681  // -> data size
682  m_requiredSize = m_dataSize;
683  if (version < 3) {
684  // -> header size
685  m_requiredSize += 6;
686  } else {
687  // -> header size
688  m_requiredSize += 10;
689  // -> group byte
690  if (m_frame.hasGroupInformation()) {
691  m_requiredSize += 1;
692  }
693  // -> decompressed size
694  if (version >= 3 && m_frame.isCompressed()) {
695  m_requiredSize += 4;
696  }
697  }
698 }
699 
707 void Id3v2FrameMaker::make(BinaryWriter &writer)
708 {
709  if (m_version < 3) {
710  writer.writeUInt24BE(m_frameId);
711  writer.writeUInt24BE(m_dataSize);
712  } else {
713  writer.writeUInt32BE(m_frameId);
714  if (m_version >= 4) {
715  writer.writeSynchsafeUInt32BE(m_dataSize);
716  } else {
717  writer.writeUInt32BE(m_dataSize);
718  }
719  writer.writeUInt16BE(m_frame.flag());
720  if (m_frame.hasGroupInformation()) {
721  writer.writeByte(m_frame.group());
722  }
723  if (m_version >= 3 && m_frame.isCompressed()) {
724  if (m_version >= 4) {
725  writer.writeSynchsafeUInt32BE(m_decompressedSize);
726  } else {
727  writer.writeUInt32BE(m_decompressedSize);
728  }
729  }
730  }
731  writer.write(m_data.get(), m_dataSize);
732 }
733 
740 TagTextEncoding Id3v2Frame::parseTextEncodingByte(std::uint8_t textEncodingByte, Diagnostics &diag)
741 {
742  switch (textEncodingByte) {
743  case Id3v2TextEncodingBytes::Ascii:
744  return TagTextEncoding::Latin1;
745  case Id3v2TextEncodingBytes::Utf16WithBom:
746  return TagTextEncoding::Utf16LittleEndian;
747  case Id3v2TextEncodingBytes::Utf16BigEndianWithoutBom:
748  return TagTextEncoding::Utf16BigEndian;
750  return TagTextEncoding::Utf8;
751  default:
752  diag.emplace_back(
753  DiagLevel::Warning, "The charset of the frame is invalid. Latin-1 will be used.", "parsing encoding of frame " + idToString());
754  return TagTextEncoding::Latin1;
755  }
756 }
757 
762 {
763  switch (textEncoding) {
764  case TagTextEncoding::Latin1:
765  return Id3v2TextEncodingBytes::Ascii;
768  case TagTextEncoding::Utf16LittleEndian:
769  return Id3v2TextEncodingBytes::Utf16WithBom;
770  case TagTextEncoding::Utf16BigEndian:
771  return Id3v2TextEncodingBytes::Utf16WithBom;
772  default:
773  return 0;
774  }
775 }
776 
791 tuple<const char *, size_t, const char *> Id3v2Frame::parseSubstring(
792  const char *buffer, std::size_t bufferSize, TagTextEncoding &encoding, bool addWarnings, Diagnostics &diag)
793 {
794  tuple<const char *, size_t, const char *> res(buffer, 0, buffer + bufferSize);
795  switch (encoding) {
797  case TagTextEncoding::Latin1:
798  case TagTextEncoding::Utf8: {
799  if ((bufferSize >= 3) && (BE::toUInt24(buffer) == 0x00EFBBBF)) {
800  if (encoding == TagTextEncoding::Latin1) {
801  diag.emplace_back(DiagLevel::Critical, "Denoted character set is Latin-1 but an UTF-8 BOM is present - assuming UTF-8.",
802  "parsing frame " + idToString());
803  encoding = TagTextEncoding::Utf8;
804  }
805  get<0>(res) += 3;
806  }
807  const char *pos = get<0>(res);
808  for (; *pos != 0x00; ++pos) {
809  if (pos < get<2>(res)) {
810  ++get<1>(res);
811  } else {
812  if (addWarnings) {
813  diag.emplace_back(
814  DiagLevel::Warning, "String in frame is not terminated properly.", "parsing termination of frame " + idToString());
815  }
816  break;
817  }
818  }
819  get<2>(res) = pos + 1;
820  break;
821  }
822  case TagTextEncoding::Utf16BigEndian:
823  case TagTextEncoding::Utf16LittleEndian: {
824  if (bufferSize >= 2) {
825  switch (LE::toUInt16(buffer)) {
826  case 0xFEFF:
827  if (encoding == TagTextEncoding::Utf16BigEndian) {
828  diag.emplace_back(DiagLevel::Critical,
829  "Denoted character set is UTF-16 Big Endian but UTF-16 Little Endian BOM is present - assuming UTF-16 LE.",
830  "parsing frame " + idToString());
831  encoding = TagTextEncoding::Utf16LittleEndian;
832  }
833  get<0>(res) += 2;
834  break;
835  case 0xFFFE:
836  encoding = TagTextEncoding::Utf16BigEndian;
837  get<0>(res) += 2;
838  }
839  }
840  const std::uint16_t *pos = reinterpret_cast<const std::uint16_t *>(get<0>(res));
841  for (; *pos != 0x0000; ++pos) {
842  if (pos < reinterpret_cast<const std::uint16_t *>(get<2>(res))) {
843  get<1>(res) += 2;
844  } else {
845  if (addWarnings) {
846  diag.emplace_back(
847  DiagLevel::Warning, "Wide string in frame is not terminated properly.", "parsing termination of frame " + idToString());
848  }
849  break;
850  }
851  }
852  get<2>(res) = reinterpret_cast<const char *>(pos + 1);
853  break;
854  }
855  }
856  return res;
857 }
858 
864 string Id3v2Frame::parseString(const char *buffer, size_t dataSize, TagTextEncoding &encoding, bool addWarnings, Diagnostics &diag)
865 {
866  return stringFromSubstring(parseSubstring(buffer, dataSize, encoding, addWarnings, diag));
867 }
868 
876 u16string Id3v2Frame::parseWideString(const char *buffer, size_t dataSize, TagTextEncoding &encoding, bool addWarnings, Diagnostics &diag)
877 {
878  return wideStringFromSubstring(parseSubstring(buffer, dataSize, encoding, addWarnings, diag), encoding);
879 }
880 
888 void Id3v2Frame::parseBom(const char *buffer, std::size_t maxSize, TagTextEncoding &encoding, Diagnostics &diag)
889 {
890  switch (encoding) {
891  case TagTextEncoding::Utf16BigEndian:
892  case TagTextEncoding::Utf16LittleEndian:
893  if ((maxSize >= 2) && (BE::toUInt16(buffer) == 0xFFFE)) {
894  encoding = TagTextEncoding::Utf16LittleEndian;
895  } else if ((maxSize >= 2) && (BE::toUInt16(buffer) == 0xFEFF)) {
896  encoding = TagTextEncoding::Utf16BigEndian;
897  }
898  break;
899  default:
900  if ((maxSize >= 3) && (BE::toUInt24(buffer) == 0x00EFBBBF)) {
901  encoding = TagTextEncoding::Utf8;
902  diag.emplace_back(DiagLevel::Warning, "UTF-8 byte order mark found in text frame.", "parsing byte oder mark of frame " + idToString());
903  }
904  }
905 }
906 
914 void Id3v2Frame::parseLegacyPicture(const char *buffer, std::size_t maxSize, TagValue &tagValue, std::uint8_t &typeInfo, Diagnostics &diag)
915 {
916  static const string context("parsing ID3v2.2 picture frame");
917  if (maxSize < 6) {
918  diag.emplace_back(DiagLevel::Critical, "Picture frame is incomplete.", context);
919  throw TruncatedDataException();
920  }
921  const char *end = buffer + maxSize;
922  auto dataEncoding = parseTextEncodingByte(static_cast<std::uint8_t>(*buffer), diag); // the first byte stores the encoding
923  typeInfo = static_cast<unsigned char>(*(buffer + 4));
924  auto substr = parseSubstring(buffer + 5, static_cast<size_t>(end - 5 - buffer), dataEncoding, true, diag);
925  tagValue.setDescription(string(get<0>(substr), get<1>(substr)), dataEncoding);
926  if (get<2>(substr) >= end) {
927  diag.emplace_back(DiagLevel::Critical, "Picture frame is incomplete (actual data is missing).", context);
928  throw TruncatedDataException();
929  }
930  tagValue.assignData(get<2>(substr), static_cast<size_t>(end - get<2>(substr)), TagDataType::Picture, dataEncoding);
931 }
932 
940 void Id3v2Frame::parsePicture(const char *buffer, std::size_t maxSize, TagValue &tagValue, std::uint8_t &typeInfo, Diagnostics &diag)
941 {
942  static const string context("parsing ID3v2.3 picture frame");
943  const char *end = buffer + maxSize;
944  auto dataEncoding = parseTextEncodingByte(static_cast<std::uint8_t>(*buffer), diag); // the first byte stores the encoding
945  auto mimeTypeEncoding = TagTextEncoding::Latin1;
946  auto substr = parseSubstring(buffer + 1, maxSize - 1, mimeTypeEncoding, true, diag);
947  if (get<1>(substr)) {
948  tagValue.setMimeType(string(get<0>(substr), get<1>(substr)));
949  }
950  if (get<2>(substr) >= end) {
951  diag.emplace_back(DiagLevel::Critical, "Picture frame is incomplete (type info, description and actual data are missing).", context);
952  throw TruncatedDataException();
953  }
954  typeInfo = static_cast<unsigned char>(*get<2>(substr));
955  if (++get<2>(substr) >= end) {
956  diag.emplace_back(DiagLevel::Critical, "Picture frame is incomplete (description and actual data are missing).", context);
957  throw TruncatedDataException();
958  }
959  substr = parseSubstring(get<2>(substr), static_cast<size_t>(end - get<2>(substr)), dataEncoding, true, diag);
960  tagValue.setDescription(string(get<0>(substr), get<1>(substr)), dataEncoding);
961  if (get<2>(substr) >= end) {
962  diag.emplace_back(DiagLevel::Critical, "Picture frame is incomplete (actual data is missing).", context);
963  throw TruncatedDataException();
964  }
965  tagValue.assignData(get<2>(substr), static_cast<size_t>(end - get<2>(substr)), TagDataType::Picture, dataEncoding);
966 }
967 
974 void Id3v2Frame::parseComment(const char *buffer, std::size_t dataSize, TagValue &tagValue, Diagnostics &diag)
975 {
976  static const string context("parsing comment/unsynchronized lyrics frame");
977  const char *end = buffer + dataSize;
978  if (dataSize < 5) {
979  diag.emplace_back(DiagLevel::Critical, "Comment frame is incomplete.", context);
980  throw TruncatedDataException();
981  }
982  TagTextEncoding dataEncoding = parseTextEncodingByte(static_cast<std::uint8_t>(*buffer), diag);
983  if (*(++buffer)) {
984  tagValue.setLanguage(string(buffer, 3));
985  }
986  auto substr = parseSubstring(buffer += 3, dataSize -= 4, dataEncoding, true, diag);
987  tagValue.setDescription(string(get<0>(substr), get<1>(substr)), dataEncoding);
988  if (get<2>(substr) > end) {
989  diag.emplace_back(DiagLevel::Critical, "Comment frame is incomplete (description not terminated?).", context);
990  throw TruncatedDataException();
991  }
992  substr = parseSubstring(get<2>(substr), static_cast<size_t>(end - get<2>(substr)), dataEncoding, false, diag);
993  tagValue.assignData(get<0>(substr), get<1>(substr), TagDataType::Text, dataEncoding);
994 }
995 
1000 size_t Id3v2Frame::makeBom(char *buffer, TagTextEncoding encoding)
1001 {
1002  switch (encoding) {
1003  case TagTextEncoding::Utf16LittleEndian:
1004  LE::getBytes(static_cast<std::uint16_t>(0xFEFF), buffer);
1005  return 2;
1006  case TagTextEncoding::Utf16BigEndian:
1007  BE::getBytes(static_cast<std::uint16_t>(0xFEFF), buffer);
1008  return 2;
1009  default:
1010  return 0;
1011  }
1012 }
1013 
1018  unique_ptr<char[]> &buffer, std::uint32_t &bufferSize, const TagValue &picture, std::uint8_t typeInfo, Diagnostics &diag)
1019 {
1020  // determine description
1021  TagTextEncoding descriptionEncoding = picture.descriptionEncoding();
1022  StringData convertedDescription;
1023  string::size_type descriptionSize = picture.description().find(
1024  "\0\0", 0, descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? 2 : 1);
1025  if (descriptionSize == string::npos) {
1026  descriptionSize = picture.description().size();
1027  }
1028  if (descriptionEncoding == TagTextEncoding::Utf8) {
1029  // UTF-8 is only supported by ID3v2.4, so convert back to UTF-16
1030  descriptionEncoding = TagTextEncoding::Utf16LittleEndian;
1031  convertedDescription = convertUtf8ToUtf16LE(picture.description().data(), descriptionSize);
1032  descriptionSize = convertedDescription.second;
1033  }
1034 
1035  // calculate needed buffer size and create buffer
1036  // note: encoding byte + image format + picture type byte + description size + 1 or 2 null bytes (depends on encoding) + data size
1037  const auto requiredBufferSize = 1 + 3 + 1 + descriptionSize
1038  + (descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? 4 : 1)
1039  + picture.dataSize();
1040  if (requiredBufferSize > numeric_limits<std::uint32_t>::max()) {
1041  diag.emplace_back(DiagLevel::Critical, "Required size exceeds maximum.", "making legacy picture frame");
1042  throw InvalidDataException();
1043  }
1044  buffer = make_unique<char[]>(bufferSize = static_cast<std::uint32_t>(requiredBufferSize));
1045  char *offset = buffer.get();
1046 
1047  // write encoding byte
1048  *offset = static_cast<char>(makeTextEncodingByte(descriptionEncoding));
1049 
1050  // write mime type
1051  const char *imageFormat;
1052  if (picture.mimeType() == "image/jpeg") {
1053  imageFormat = "JPG";
1054  } else if (picture.mimeType() == "image/png") {
1055  imageFormat = "PNG";
1056  } else if (picture.mimeType() == "image/gif") {
1057  imageFormat = "GIF";
1058  } else if (picture.mimeType() == "-->") {
1059  imageFormat = picture.mimeType().data();
1060  } else {
1061  imageFormat = "UND";
1062  }
1063  strncpy(++offset, imageFormat, 3);
1064 
1065  // write picture type
1066  *(offset += 3) = static_cast<char>(typeInfo);
1067 
1068  // write description
1069  offset += makeBom(offset + 1, descriptionEncoding);
1070  if (convertedDescription.first) {
1071  copy(convertedDescription.first.get(), convertedDescription.first.get() + descriptionSize, ++offset);
1072  } else {
1073  picture.description().copy(++offset, descriptionSize);
1074  }
1075  *(offset += descriptionSize) = 0x00; // terminate description and increase data offset
1076  if (descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian) {
1077  *(++offset) = 0x00;
1078  }
1079 
1080  // write actual data
1081  copy(picture.dataPointer(), picture.dataPointer() + picture.dataSize(), ++offset);
1082 }
1083 
1087 void Id3v2Frame::makePicture(std::unique_ptr<char[]> &buffer, std::uint32_t &bufferSize, const TagValue &picture, std::uint8_t typeInfo,
1088  std::uint8_t version, Diagnostics &diag)
1089 {
1090  if (version < 3) {
1091  makeLegacyPicture(buffer, bufferSize, picture, typeInfo, diag);
1092  return;
1093  }
1094 
1095  // determine description
1096  TagTextEncoding descriptionEncoding = picture.descriptionEncoding();
1097  StringData convertedDescription;
1098  string::size_type descriptionSize = picture.description().find(
1099  "\0\0", 0, descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? 2 : 1);
1100  if (descriptionSize == string::npos) {
1101  descriptionSize = picture.description().size();
1102  }
1103  if (version < 4 && descriptionEncoding == TagTextEncoding::Utf8) {
1104  // UTF-8 is only supported by ID3v2.4, so convert back to UTF-16
1105  descriptionEncoding = TagTextEncoding::Utf16LittleEndian;
1106  convertedDescription = convertUtf8ToUtf16LE(picture.description().data(), descriptionSize);
1107  descriptionSize = convertedDescription.second;
1108  }
1109  // determine mime-type
1110  string::size_type mimeTypeSize = picture.mimeType().find('\0');
1111  if (mimeTypeSize == string::npos) {
1112  mimeTypeSize = picture.mimeType().length();
1113  }
1114 
1115  // calculate needed buffer size and create buffer
1116  // note: encoding byte + mime type size + 0 byte + picture type byte + description size + 1 or 4 null bytes (depends on encoding) + data size
1117  const auto requiredBufferSize = 1 + mimeTypeSize + 1 + 1 + descriptionSize
1118  + (descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? 4 : 1)
1119  + picture.dataSize();
1120  if (requiredBufferSize > numeric_limits<uint32_t>::max()) {
1121  diag.emplace_back(DiagLevel::Critical, "Required size exceeds maximum.", "making picture frame");
1122  throw InvalidDataException();
1123  }
1124  buffer = make_unique<char[]>(bufferSize = static_cast<uint32_t>(requiredBufferSize));
1125  char *offset = buffer.get();
1126 
1127  // write encoding byte
1128  *offset = static_cast<char>(makeTextEncodingByte(descriptionEncoding));
1129 
1130  // write mime type
1131  picture.mimeType().copy(++offset, mimeTypeSize);
1132 
1133  *(offset += mimeTypeSize) = 0x00; // terminate mime type
1134  // write picture type
1135  *(++offset) = static_cast<char>(typeInfo);
1136 
1137  // write description
1138  offset += makeBom(offset + 1, descriptionEncoding);
1139  if (convertedDescription.first) {
1140  copy(convertedDescription.first.get(), convertedDescription.first.get() + descriptionSize, ++offset);
1141  } else {
1142  picture.description().copy(++offset, descriptionSize);
1143  }
1144  *(offset += descriptionSize) = 0x00; // terminate description and increase data offset
1145  if (descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian) {
1146  *(++offset) = 0x00;
1147  }
1148 
1149  // write actual data
1150  copy(picture.dataPointer(), picture.dataPointer() + picture.dataSize(), ++offset);
1151 }
1152 
1156 void Id3v2Frame::makeComment(unique_ptr<char[]> &buffer, std::uint32_t &bufferSize, const TagValue &comment, std::uint8_t version, Diagnostics &diag)
1157 {
1158  static const string context("making comment frame");
1159 
1160  // check whether type and other values are valid
1161  TagTextEncoding encoding = comment.dataEncoding();
1162  if (!comment.description().empty() && encoding != comment.descriptionEncoding()) {
1163  diag.emplace_back(DiagLevel::Critical, "Data encoding and description encoding aren't equal.", context);
1164  throw InvalidDataException();
1165  }
1166  const string &lng = comment.language();
1167  if (lng.length() > 3) {
1168  diag.emplace_back(DiagLevel::Critical, "The language must be 3 bytes long (ISO-639-2).", context);
1169  throw InvalidDataException();
1170  }
1171  StringData convertedDescription;
1172  string::size_type descriptionSize = comment.description().find(
1173  "\0\0", 0, encoding == TagTextEncoding::Utf16BigEndian || encoding == TagTextEncoding::Utf16LittleEndian ? 2 : 1);
1174  if (descriptionSize == string::npos) {
1175  descriptionSize = comment.description().size();
1176  }
1177  if (version < 4 && encoding == TagTextEncoding::Utf8) {
1178  // UTF-8 is only supported by ID3v2.4, so convert back to UTF-16
1179  encoding = TagTextEncoding::Utf16LittleEndian;
1180  convertedDescription = convertUtf8ToUtf16LE(comment.description().data(), descriptionSize);
1181  descriptionSize = convertedDescription.second;
1182  }
1183 
1184  // calculate needed buffer size and create buffer
1185  // note: encoding byte + language + description size + actual data size + BOMs and termination
1186  const auto data = comment.toString(encoding);
1187  const auto requiredBufferSize = 1 + 3 + descriptionSize + data.size()
1188  + (encoding == TagTextEncoding::Utf16BigEndian || encoding == TagTextEncoding::Utf16LittleEndian ? 6 : 1) + data.size();
1189  if (requiredBufferSize > numeric_limits<uint32_t>::max()) {
1190  diag.emplace_back(DiagLevel::Critical, "Required size exceeds maximum.", context);
1191  throw InvalidDataException();
1192  }
1193  buffer = make_unique<char[]>(bufferSize = static_cast<uint32_t>(requiredBufferSize));
1194  char *offset = buffer.get();
1195 
1196  // write encoding
1197  *offset = static_cast<char>(makeTextEncodingByte(encoding));
1198 
1199  // write language
1200  for (unsigned int i = 0; i < 3; ++i) {
1201  *(++offset) = (lng.length() > i) ? lng[i] : 0x00;
1202  }
1203 
1204  // write description
1205  offset += makeBom(offset + 1, encoding);
1206  if (convertedDescription.first) {
1207  copy(convertedDescription.first.get(), convertedDescription.first.get() + descriptionSize, ++offset);
1208  } else {
1209  comment.description().copy(++offset, descriptionSize);
1210  }
1211  offset += descriptionSize;
1212  *offset = 0x00; // terminate description and increase data offset
1213  if (encoding == TagTextEncoding::Utf16BigEndian || encoding == TagTextEncoding::Utf16LittleEndian) {
1214  *(++offset) = 0x00;
1215  }
1216 
1217  // write actual data
1218  offset += makeBom(offset + 1, encoding);
1219  data.copy(++offset, data.size());
1220 }
1221 
1222 } // 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:69
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:740
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:117
TagParser::VorbisCommentIds::version
constexpr TAG_PARSER_EXPORT const char * version()
Definition: vorbiscommentids.h:34
TagParser::RawDataType::Utf8
@ Utf8
Definition: mp4tagfield.h:21
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.
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::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::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:864
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
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:888
TagParser::Id3v2FrameIds::lComment
@ lComment
Definition: id3v2frameids.h:16
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:134
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:1156
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:761
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:876
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:974
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:30
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::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:914
id3genres.h
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::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:940
TagParser::Id3v2FrameIds::lUnsynchronizedLyrics
@ lUnsynchronizedLyrics
Definition: id3v2frameids.h:38
TagParser::Id3v2Frame::Id3v2Frame
Id3v2Frame()
Constructs a new Id3v2Frame.
Definition: id3v2frame.cpp:40
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:1000
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:68
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.
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:791
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:395
TagParser::Id3v2FrameIds::sGenre
@ sGenre
Definition: id3v2frameids.h:57
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::Id3v2Frame::prepareMaking
Id3v2FrameMaker prepareMaking(std::uint8_t version, Diagnostics &diag)
Prepares making.
Definition: id3v2frame.cpp:382
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:1017
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:109
TagParser::TagValue::setLanguage
void setLanguage(const std::string &language)
Sets the language.
Definition: tagvalue.h:582
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:1087
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:707