Tag Parser  10.0.1
C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags
id3v2frame.cpp
Go to the documentation of this file.
1 #include "./id3v2frame.h"
2 #include "./id3genres.h"
3 #include "./id3v2frameids.h"
4 
5 #include "../diagnostics.h"
6 #include "../exceptions.h"
7 
8 #include <c++utilities/conversion/stringbuilder.h>
9 #include <c++utilities/conversion/stringconversion.h>
10 
11 #include <zlib.h>
12 
13 #include <algorithm>
14 #include <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::internallyClearValue()
404 {
406  m_additionalValues.clear();
407 }
408 
412 void Id3v2Frame::internallyClearFurtherData()
413 {
414  m_flag = 0;
415  m_group = 0;
416  m_parsedVersion = 0;
417  m_dataSize = 0;
418  m_totalSize = 0;
419  m_padding = false;
420 }
421 
425 std::string Id3v2Frame::ignoreAdditionalValuesDiagMsg() const
426 {
427  if (m_additionalValues.size() == 1) {
428  return argsToString("Additional value \"", m_additionalValues.front().toString(TagTextEncoding::Utf8), "\" is supposed to be ignored.");
429  }
430  return argsToString("Additional values ", DiagMessage::formatList(TagValue::toStrings(m_additionalValues)), " are supposed to be ignored.");
431 }
432 
444 Id3v2FrameMaker::Id3v2FrameMaker(Id3v2Frame &frame, std::uint8_t version, Diagnostics &diag)
445  : m_frame(frame)
446  , m_frameId(m_frame.id())
447  , m_version(version)
448 {
449  const string context("making " % m_frame.idToString() + " frame");
450 
451  // validate frame's configuration
452  if (m_frame.isEncrypted()) {
453  diag.emplace_back(DiagLevel::Critical, "Cannot make an encrypted frame (isn't supported by this tagging library).", context);
454  throw InvalidDataException();
455  }
456  if (m_frame.hasPaddingReached()) {
457  diag.emplace_back(DiagLevel::Critical, "Cannot make a frame which is marked as padding.", context);
458  throw InvalidDataException();
459  }
460  if (version < 3 && m_frame.isCompressed()) {
461  diag.emplace_back(DiagLevel::Warning, "Compression is not supported by the version of ID3v2 and won't be applied.", context);
462  }
463  if (version < 3 && (m_frame.flag() || m_frame.group())) {
464  diag.emplace_back(DiagLevel::Warning,
465  "The existing flag and group information is not supported by the version of ID3v2 and will be ignored/discarted.", context);
466  }
467 
468  // get non-empty, assigned values
469  vector<const TagValue *> values;
470  values.reserve(1 + frame.additionalValues().size());
471  if (!frame.value().isEmpty()) {
472  values.emplace_back(&frame.value());
473  }
474  for (const auto &value : frame.additionalValues()) {
475  if (!value.isEmpty()) {
476  values.emplace_back(&value);
477  }
478  }
479 
480  // validate assigned values
481  if (values.empty()) {
482  throw NoDataProvidedException();
483  // note: This is not really an issue because in the case we're not provided with any value here just means that the field
484  // is supposed to be removed. So don't add any diagnostic messages here.
485  }
486  const bool isTextFrame = Id3v2FrameIds::isTextFrame(m_frameId);
487  if (values.size() != 1) {
488  if (!isTextFrame) {
489  diag.emplace_back(DiagLevel::Critical, "Multiple values are not supported for non-text-frames.", context);
490  throw InvalidDataException();
491  } else if (version < 4) {
492  diag.emplace_back(
493  DiagLevel::Warning, "Multiple strings assigned to pre-ID3v2.4 text frame. " + frame.ignoreAdditionalValuesDiagMsg(), context);
494  }
495  }
496 
497  // convert frame ID if necessary
498  if (version >= 3) {
499  if (Id3v2FrameIds::isShortId(m_frameId)) {
500  // try to convert the short frame ID to its long equivalent
501  if (!(m_frameId = Id3v2FrameIds::convertToLongId(m_frameId))) {
502  diag.emplace_back(DiagLevel::Critical,
503  "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.",
504  context);
505  throw InvalidDataException();
506  }
507  }
508  } else {
509  if (Id3v2FrameIds::isLongId(m_frameId)) {
510  // try to convert the long frame ID to its short equivalent
511  if (!(m_frameId = Id3v2FrameIds::convertToShortId(m_frameId))) {
512  diag.emplace_back(DiagLevel::Critical,
513  "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.",
514  context);
515  throw InvalidDataException();
516  }
517  }
518  }
519 
520  // add a warning if we're writing the frame for an ID3v2 tag known not to support it
521  if (version <= 3 && Id3v2FrameIds::isOnlyId3v24Id(version < 3 ? Id3v2FrameIds::convertToLongId(m_frameId) : m_frameId)) {
522  diag.emplace_back(DiagLevel::Warning,
523  argsToString("The frame is only supported in ID3v2.4 and newer but version of the tag being written is ID3v2.", version,
524  ". The frame is written nevertheless but other tools might not be able to deal with it."),
525  context);
526  } else if (version > 3 && Id3v2FrameIds::isPreId3v24Id(m_frameId)) {
527  diag.emplace_back(DiagLevel::Warning,
528  argsToString("The frame is only supported in ID3v2.3 and older but version of the tag being written is ID3v2.", version,
529  ". The frame is written nevertheless but other tools might not be able to deal with it."),
530  context);
531  }
532 
533  // make actual data depending on the frame ID
534  try {
535  if (isTextFrame) {
536  // make text frame
537  vector<string> substrings;
538  substrings.reserve(1 + frame.additionalValues().size());
540 
541  if ((version >= 3 && (m_frameId == Id3v2FrameIds::lTrackPosition || m_frameId == Id3v2FrameIds::lDiskPosition))
542  || (version < 3 && (m_frameId == Id3v2FrameIds::sTrackPosition || m_frameId == Id3v2FrameIds::sDiskPosition))) {
543  // make track number or disk number frame
545  for (const auto *const value : values) {
546  // convert the position to string
547  substrings.emplace_back(value->toString(encoding));
548  // warn if value is no valid position (although we just store a string after all)
550  continue;
551  }
552  try {
554  } catch (const ConversionException &) {
555  diag.emplace_back(DiagLevel::Warning,
556  argsToString("The track/disk number \"", substrings.back(), "\" is not of the expected form, eg. \"4/10\"."), context);
557  }
558  }
559 
560  } else if ((version >= 3 && m_frameId == Id3v2FrameIds::lLength) || (version < 3 && m_frameId == Id3v2FrameIds::sLength)) {
561  // make length frame
562  encoding = TagTextEncoding::Latin1;
563  for (const auto *const value : values) {
564  const auto duration(value->toTimeSpan());
565  if (duration.isNegative()) {
566  diag.emplace_back(DiagLevel::Critical, argsToString("Assigned duration \"", duration.toString(), "\" is negative."), context);
567  throw InvalidDataException();
568  }
569  substrings.emplace_back(numberToString(static_cast<std::uint64_t>(duration.totalMilliseconds())));
570  }
571 
572  } else {
573  // make standard genre index and other text frames
574  // -> find text encoding suitable for all assigned values
575  for (const auto *const value : values) {
576  switch (encoding) {
578  switch (value->type()) {
580  encoding = TagTextEncoding::Latin1;
581  break;
582  default:
583  encoding = value->dataEncoding();
584  }
585  break;
587  switch (value->dataEncoding()) {
589  break;
590  default:
591  encoding = value->dataEncoding();
592  }
593  break;
594  default:;
595  }
596  }
597  if (version <= 3 && encoding == TagTextEncoding::Utf8) {
599  }
600  // -> format values
601  for (const auto *const value : values) {
603  && ((version >= 3 && m_frameId == Id3v2FrameIds::lGenre) || (version < 3 && m_frameId == Id3v2FrameIds::sGenre))) {
604  // make standard genere index
605  substrings.emplace_back(numberToString(value->toStandardGenreIndex()));
606 
607  } else {
608  // make other text frame
609  substrings.emplace_back(value->toString(encoding));
610  }
611  }
612  }
613 
614  // concatenate substrings using encoding specific byte order mark and termination
615  const auto terminationLength = (encoding == TagTextEncoding::Utf16BigEndian || encoding == TagTextEncoding::Utf16LittleEndian) ? 2u : 1u;
616  const auto byteOrderMark = [&] {
617  switch (encoding) {
619  return string({ '\xFF', '\xFE' });
621  return string({ '\xFE', '\xFF' });
622  default:
623  return string();
624  }
625  }();
626  const auto concatenatedSubstrings = joinStrings(substrings, string(), false, byteOrderMark, string(terminationLength, '\0'));
627 
628  // write text encoding byte and concatenated strings to data buffer
629  m_data = make_unique<char[]>(m_decompressedSize = static_cast<std::uint32_t>(1 + concatenatedSubstrings.size()));
630  m_data[0] = static_cast<char>(Id3v2Frame::makeTextEncodingByte(encoding));
631  concatenatedSubstrings.copy(&m_data[1], concatenatedSubstrings.size());
632 
633  } else if ((version >= 3 && m_frameId == Id3v2FrameIds::lCover) || (version < 3 && m_frameId == Id3v2FrameIds::sCover)) {
634  // make picture frame
635  m_frame.makePicture(m_data, m_decompressedSize, *values.front(), m_frame.isTypeInfoAssigned() ? m_frame.typeInfo() : 0, version, diag);
636 
637  } else if (((version >= 3 && m_frameId == Id3v2FrameIds::lComment) || (version < 3 && m_frameId == Id3v2FrameIds::sComment))
638  || ((version >= 3 && m_frameId == Id3v2FrameIds::lUnsynchronizedLyrics)
639  || (version < 3 && m_frameId == Id3v2FrameIds::sUnsynchronizedLyrics))) {
640  // make comment frame or the unsynchronized lyrics frame
641  m_frame.makeComment(m_data, m_decompressedSize, *values.front(), version, diag);
642 
643  } else {
644  // make unknown frame
645  const auto &value(*values.front());
647  diag.emplace_back(DiagLevel::Critical, "Assigned value exceeds maximum size.", context);
648  throw InvalidDataException();
649  }
650  m_data = make_unique<char[]>(m_decompressedSize = static_cast<std::uint32_t>(value.dataSize()));
651  copy(value.dataPointer(), value.dataPointer() + m_decompressedSize, m_data.get());
652  }
653  } catch (const ConversionException &) {
654  try {
655  const auto valuesAsString = TagValue::toStrings(values);
656  diag.emplace_back(DiagLevel::Critical,
657  argsToString("Assigned value(s) \"", DiagMessage::formatList(valuesAsString), "\" can not be converted appropriately."), context);
658  } catch (const ConversionException &) {
659  diag.emplace_back(DiagLevel::Critical, "Assigned value(s) can not be converted appropriately.", context);
660  }
661  throw InvalidDataException();
662  }
663 
664  // apply compression if frame should be compressed
665  if (version >= 3 && m_frame.isCompressed()) {
666  auto compressedSize = compressBound(m_decompressedSize);
667  auto compressedData = make_unique<char[]>(compressedSize);
668  switch (compress(reinterpret_cast<Bytef *>(compressedData.get()), reinterpret_cast<uLongf *>(&compressedSize),
669  reinterpret_cast<Bytef *>(m_data.get()), m_decompressedSize)) {
670  case Z_MEM_ERROR:
671  diag.emplace_back(DiagLevel::Critical, "Decompressing failed. The source buffer was too small.", context);
672  throw InvalidDataException();
673  case Z_BUF_ERROR:
674  diag.emplace_back(DiagLevel::Critical, "Decompressing failed. The destination buffer was too small.", context);
675  throw InvalidDataException();
676  case Z_OK:;
677  }
678  if (compressedSize > maxId3v2FrameDataSize) {
679  diag.emplace_back(DiagLevel::Critical, "Compressed size exceeds maximum data size.", context);
680  throw InvalidDataException();
681  }
682  m_data.swap(compressedData);
683  m_dataSize = static_cast<std::uint32_t>(compressedSize);
684  } else {
685  m_dataSize = m_decompressedSize;
686  }
687 
688  // calculate required size
689  // -> data size
690  m_requiredSize = m_dataSize;
691  if (version < 3) {
692  // -> header size
693  m_requiredSize += 6;
694  } else {
695  // -> header size
696  m_requiredSize += 10;
697  // -> group byte
698  if (m_frame.hasGroupInformation()) {
699  m_requiredSize += 1;
700  }
701  // -> decompressed size
702  if (version >= 3 && m_frame.isCompressed()) {
703  m_requiredSize += 4;
704  }
705  }
706 }
707 
715 void Id3v2FrameMaker::make(BinaryWriter &writer)
716 {
717  if (m_version < 3) {
718  writer.writeUInt24BE(m_frameId);
719  writer.writeUInt24BE(m_dataSize);
720  } else {
721  writer.writeUInt32BE(m_frameId);
722  if (m_version >= 4) {
723  writer.writeSynchsafeUInt32BE(m_dataSize);
724  } else {
725  writer.writeUInt32BE(m_dataSize);
726  }
727  writer.writeUInt16BE(m_frame.flag());
728  if (m_frame.hasGroupInformation()) {
729  writer.writeByte(m_frame.group());
730  }
731  if (m_version >= 3 && m_frame.isCompressed()) {
732  if (m_version >= 4) {
733  writer.writeSynchsafeUInt32BE(m_decompressedSize);
734  } else {
735  writer.writeUInt32BE(m_decompressedSize);
736  }
737  }
738  }
739  writer.write(m_data.get(), m_dataSize);
740 }
741 
748 TagTextEncoding Id3v2Frame::parseTextEncodingByte(std::uint8_t textEncodingByte, Diagnostics &diag)
749 {
750  switch (textEncodingByte) {
751  case Id3v2TextEncodingBytes::Ascii:
753  case Id3v2TextEncodingBytes::Utf16WithBom:
755  case Id3v2TextEncodingBytes::Utf16BigEndianWithoutBom:
758  return TagTextEncoding::Utf8;
759  default:
760  diag.emplace_back(
761  DiagLevel::Warning, "The charset of the frame is invalid. Latin-1 will be used.", "parsing encoding of frame " + idToString());
763  }
764 }
765 
770 {
771  switch (textEncoding) {
773  return Id3v2TextEncodingBytes::Ascii;
777  return Id3v2TextEncodingBytes::Utf16WithBom;
779  return Id3v2TextEncodingBytes::Utf16WithBom;
780  default:
781  return 0;
782  }
783 }
784 
799 tuple<const char *, size_t, const char *> Id3v2Frame::parseSubstring(
800  const char *buffer, std::size_t bufferSize, TagTextEncoding &encoding, bool addWarnings, Diagnostics &diag)
801 {
802  tuple<const char *, size_t, const char *> res(buffer, 0, buffer + bufferSize);
803  switch (encoding) {
806  case TagTextEncoding::Utf8: {
807  if ((bufferSize >= 3) && (BE::toUInt24(buffer) == 0x00EFBBBF)) {
808  if (encoding == TagTextEncoding::Latin1) {
809  diag.emplace_back(DiagLevel::Critical, "Denoted character set is Latin-1 but an UTF-8 BOM is present - assuming UTF-8.",
810  "parsing frame " + idToString());
811  encoding = TagTextEncoding::Utf8;
812  }
813  get<0>(res) += 3;
814  }
815  const char *pos = get<0>(res);
816  for (; *pos != 0x00; ++pos) {
817  if (pos < get<2>(res)) {
818  ++get<1>(res);
819  } else {
820  if (addWarnings) {
821  diag.emplace_back(
822  DiagLevel::Warning, "String in frame is not terminated properly.", "parsing termination of frame " + idToString());
823  }
824  break;
825  }
826  }
827  get<2>(res) = pos + 1;
828  break;
829  }
832  if (bufferSize >= 2) {
833  switch (LE::toUInt16(buffer)) {
834  case 0xFEFF:
835  if (encoding == TagTextEncoding::Utf16BigEndian) {
836  diag.emplace_back(DiagLevel::Critical,
837  "Denoted character set is UTF-16 Big Endian but UTF-16 Little Endian BOM is present - assuming UTF-16 LE.",
838  "parsing frame " + idToString());
840  }
841  get<0>(res) += 2;
842  break;
843  case 0xFFFE:
845  get<0>(res) += 2;
846  }
847  }
848  const std::uint16_t *pos = reinterpret_cast<const std::uint16_t *>(get<0>(res));
849  for (; *pos != 0x0000; ++pos) {
850  if (pos < reinterpret_cast<const std::uint16_t *>(get<2>(res))) {
851  get<1>(res) += 2;
852  } else {
853  if (addWarnings) {
854  diag.emplace_back(
855  DiagLevel::Warning, "Wide string in frame is not terminated properly.", "parsing termination of frame " + idToString());
856  }
857  break;
858  }
859  }
860  get<2>(res) = reinterpret_cast<const char *>(pos + 1);
861  break;
862  }
863  }
864  return res;
865 }
866 
872 string Id3v2Frame::parseString(const char *buffer, size_t dataSize, TagTextEncoding &encoding, bool addWarnings, Diagnostics &diag)
873 {
874  return stringFromSubstring(parseSubstring(buffer, dataSize, encoding, addWarnings, diag));
875 }
876 
884 u16string Id3v2Frame::parseWideString(const char *buffer, size_t dataSize, TagTextEncoding &encoding, bool addWarnings, Diagnostics &diag)
885 {
886  return wideStringFromSubstring(parseSubstring(buffer, dataSize, encoding, addWarnings, diag), encoding);
887 }
888 
896 void Id3v2Frame::parseBom(const char *buffer, std::size_t maxSize, TagTextEncoding &encoding, Diagnostics &diag)
897 {
898  switch (encoding) {
901  if ((maxSize >= 2) && (BE::toUInt16(buffer) == 0xFFFE)) {
903  } else if ((maxSize >= 2) && (BE::toUInt16(buffer) == 0xFEFF)) {
905  }
906  break;
907  default:
908  if ((maxSize >= 3) && (BE::toUInt24(buffer) == 0x00EFBBBF)) {
909  encoding = TagTextEncoding::Utf8;
910  diag.emplace_back(DiagLevel::Warning, "UTF-8 byte order mark found in text frame.", "parsing byte order mark of frame " + idToString());
911  }
912  }
913 }
914 
922 void Id3v2Frame::parseLegacyPicture(const char *buffer, std::size_t maxSize, TagValue &tagValue, std::uint8_t &typeInfo, Diagnostics &diag)
923 {
924  static const string context("parsing ID3v2.2 picture frame");
925  if (maxSize < 6) {
926  diag.emplace_back(DiagLevel::Critical, "Picture frame is incomplete.", context);
927  throw TruncatedDataException();
928  }
929  const char *end = buffer + maxSize;
930  auto dataEncoding = parseTextEncodingByte(static_cast<std::uint8_t>(*buffer), diag); // the first byte stores the encoding
931  typeInfo = static_cast<unsigned char>(*(buffer + 4));
932  auto substr = parseSubstring(buffer + 5, static_cast<size_t>(end - 5 - buffer), dataEncoding, true, diag);
933  tagValue.setDescription(string(get<0>(substr), get<1>(substr)), dataEncoding);
934  if (get<2>(substr) >= end) {
935  diag.emplace_back(DiagLevel::Critical, "Picture frame is incomplete (actual data is missing).", context);
936  throw TruncatedDataException();
937  }
938  tagValue.assignData(get<2>(substr), static_cast<size_t>(end - get<2>(substr)), TagDataType::Picture, dataEncoding);
939 }
940 
948 void Id3v2Frame::parsePicture(const char *buffer, std::size_t maxSize, TagValue &tagValue, std::uint8_t &typeInfo, Diagnostics &diag)
949 {
950  static const string context("parsing ID3v2.3 picture frame");
951  const char *end = buffer + maxSize;
952  auto dataEncoding = parseTextEncodingByte(static_cast<std::uint8_t>(*buffer), diag); // the first byte stores the encoding
953  auto mimeTypeEncoding = TagTextEncoding::Latin1;
954  auto substr = parseSubstring(buffer + 1, maxSize - 1, mimeTypeEncoding, true, diag);
955  if (get<1>(substr)) {
956  tagValue.setMimeType(string(get<0>(substr), get<1>(substr)));
957  }
958  if (get<2>(substr) >= end) {
959  diag.emplace_back(DiagLevel::Critical, "Picture frame is incomplete (type info, description and actual data are missing).", context);
960  throw TruncatedDataException();
961  }
962  typeInfo = static_cast<unsigned char>(*get<2>(substr));
963  if (++get<2>(substr) >= end) {
964  diag.emplace_back(DiagLevel::Critical, "Picture frame is incomplete (description and actual data are missing).", context);
965  throw TruncatedDataException();
966  }
967  substr = parseSubstring(get<2>(substr), static_cast<size_t>(end - get<2>(substr)), dataEncoding, true, diag);
968  tagValue.setDescription(string(get<0>(substr), get<1>(substr)), dataEncoding);
969  if (get<2>(substr) >= end) {
970  diag.emplace_back(DiagLevel::Critical, "Picture frame is incomplete (actual data is missing).", context);
971  throw TruncatedDataException();
972  }
973  tagValue.assignData(get<2>(substr), static_cast<size_t>(end - get<2>(substr)), TagDataType::Picture, dataEncoding);
974 }
975 
982 void Id3v2Frame::parseComment(const char *buffer, std::size_t dataSize, TagValue &tagValue, Diagnostics &diag)
983 {
984  static const string context("parsing comment/unsynchronized lyrics frame");
985  const char *end = buffer + dataSize;
986  if (dataSize < 5) {
987  diag.emplace_back(DiagLevel::Critical, "Comment frame is incomplete.", context);
988  throw TruncatedDataException();
989  }
990  TagTextEncoding dataEncoding = parseTextEncodingByte(static_cast<std::uint8_t>(*buffer), diag);
991  if (*(++buffer)) {
992  tagValue.setLocale(Locale(std::string(buffer, 3), LocaleFormat::ISO_639_2_B)); // does standard say whether T or B?
993  }
994  auto substr = parseSubstring(buffer += 3, dataSize -= 4, dataEncoding, true, diag);
995  tagValue.setDescription(string(get<0>(substr), get<1>(substr)), dataEncoding);
996  if (get<2>(substr) > end) {
997  diag.emplace_back(DiagLevel::Critical, "Comment frame is incomplete (description not terminated?).", context);
998  throw TruncatedDataException();
999  }
1000  substr = parseSubstring(get<2>(substr), static_cast<size_t>(end - get<2>(substr)), dataEncoding, false, diag);
1001  tagValue.assignData(get<0>(substr), get<1>(substr), TagDataType::Text, dataEncoding);
1002 }
1003 
1008 size_t Id3v2Frame::makeBom(char *buffer, TagTextEncoding encoding)
1009 {
1010  switch (encoding) {
1012  LE::getBytes(static_cast<std::uint16_t>(0xFEFF), buffer);
1013  return 2;
1015  BE::getBytes(static_cast<std::uint16_t>(0xFEFF), buffer);
1016  return 2;
1017  default:
1018  return 0;
1019  }
1020 }
1021 
1026  unique_ptr<char[]> &buffer, std::uint32_t &bufferSize, const TagValue &picture, std::uint8_t typeInfo, Diagnostics &diag)
1027 {
1028  // determine description
1029  TagTextEncoding descriptionEncoding = picture.descriptionEncoding();
1030  StringData convertedDescription;
1031  string::size_type descriptionSize = picture.description().find(
1032  "\0\0", 0, descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? 2 : 1);
1033  if (descriptionSize == string::npos) {
1034  descriptionSize = picture.description().size();
1035  }
1036  if (descriptionEncoding == TagTextEncoding::Utf8) {
1037  // UTF-8 is only supported by ID3v2.4, so convert back to UTF-16
1038  descriptionEncoding = TagTextEncoding::Utf16LittleEndian;
1039  convertedDescription = convertUtf8ToUtf16LE(picture.description().data(), descriptionSize);
1040  descriptionSize = convertedDescription.second;
1041  }
1042 
1043  // calculate needed buffer size and create buffer
1044  // note: encoding byte + image format + picture type byte + description size + 1 or 2 null bytes (depends on encoding) + data size
1045  const auto requiredBufferSize = 1 + 3 + 1 + descriptionSize
1046  + (descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? 4 : 1)
1047  + picture.dataSize();
1048  if (requiredBufferSize > numeric_limits<std::uint32_t>::max()) {
1049  diag.emplace_back(DiagLevel::Critical, "Required size exceeds maximum.", "making legacy picture frame");
1050  throw InvalidDataException();
1051  }
1052  buffer = make_unique<char[]>(bufferSize = static_cast<std::uint32_t>(requiredBufferSize));
1053  char *offset = buffer.get();
1054 
1055  // write encoding byte
1056  *offset = static_cast<char>(makeTextEncodingByte(descriptionEncoding));
1057 
1058  // write mime type
1059  const char *imageFormat;
1060  if (picture.mimeType() == "image/jpeg") {
1061  imageFormat = "JPG";
1062  } else if (picture.mimeType() == "image/png") {
1063  imageFormat = "PNG";
1064  } else if (picture.mimeType() == "image/gif") {
1065  imageFormat = "GIF";
1066  } else if (picture.mimeType() == "-->") {
1067  imageFormat = picture.mimeType().data();
1068  } else {
1069  imageFormat = "UND";
1070  }
1071  strncpy(++offset, imageFormat, 3);
1072 
1073  // write picture type
1074  *(offset += 3) = static_cast<char>(typeInfo);
1075 
1076  // write description
1077  offset += makeBom(offset + 1, descriptionEncoding);
1078  if (convertedDescription.first) {
1079  copy(convertedDescription.first.get(), convertedDescription.first.get() + descriptionSize, ++offset);
1080  } else {
1081  picture.description().copy(++offset, descriptionSize);
1082  }
1083  *(offset += descriptionSize) = 0x00; // terminate description and increase data offset
1084  if (descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian) {
1085  *(++offset) = 0x00;
1086  }
1087 
1088  // write actual data
1089  copy(picture.dataPointer(), picture.dataPointer() + picture.dataSize(), ++offset);
1090 }
1091 
1095 void Id3v2Frame::makePicture(std::unique_ptr<char[]> &buffer, std::uint32_t &bufferSize, const TagValue &picture, std::uint8_t typeInfo,
1096  std::uint8_t version, Diagnostics &diag)
1097 {
1098  if (version < 3) {
1099  makeLegacyPicture(buffer, bufferSize, picture, typeInfo, diag);
1100  return;
1101  }
1102 
1103  // determine description
1104  TagTextEncoding descriptionEncoding = picture.descriptionEncoding();
1105  StringData convertedDescription;
1106  string::size_type descriptionSize = picture.description().find(
1107  "\0\0", 0, descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? 2 : 1);
1108  if (descriptionSize == string::npos) {
1109  descriptionSize = picture.description().size();
1110  }
1111  if (version < 4 && descriptionEncoding == TagTextEncoding::Utf8) {
1112  // UTF-8 is only supported by ID3v2.4, so convert back to UTF-16
1113  descriptionEncoding = TagTextEncoding::Utf16LittleEndian;
1114  convertedDescription = convertUtf8ToUtf16LE(picture.description().data(), descriptionSize);
1115  descriptionSize = convertedDescription.second;
1116  }
1117  // determine mime-type
1118  string::size_type mimeTypeSize = picture.mimeType().find('\0');
1119  if (mimeTypeSize == string::npos) {
1120  mimeTypeSize = picture.mimeType().length();
1121  }
1122 
1123  // calculate needed buffer size and create buffer
1124  // note: encoding byte + mime type size + 0 byte + picture type byte + description size + 1 or 4 null bytes (depends on encoding) + data size
1125  const auto requiredBufferSize = 1 + mimeTypeSize + 1 + 1 + descriptionSize
1126  + (descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? 4 : 1)
1127  + picture.dataSize();
1128  if (requiredBufferSize > numeric_limits<uint32_t>::max()) {
1129  diag.emplace_back(DiagLevel::Critical, "Required size exceeds maximum.", "making picture frame");
1130  throw InvalidDataException();
1131  }
1132  buffer = make_unique<char[]>(bufferSize = static_cast<uint32_t>(requiredBufferSize));
1133  char *offset = buffer.get();
1134 
1135  // write encoding byte
1136  *offset = static_cast<char>(makeTextEncodingByte(descriptionEncoding));
1137 
1138  // write mime type
1139  picture.mimeType().copy(++offset, mimeTypeSize);
1140 
1141  *(offset += mimeTypeSize) = 0x00; // terminate mime type
1142  // write picture type
1143  *(++offset) = static_cast<char>(typeInfo);
1144 
1145  // write description
1146  offset += makeBom(offset + 1, descriptionEncoding);
1147  if (convertedDescription.first) {
1148  copy(convertedDescription.first.get(), convertedDescription.first.get() + descriptionSize, ++offset);
1149  } else {
1150  picture.description().copy(++offset, descriptionSize);
1151  }
1152  *(offset += descriptionSize) = 0x00; // terminate description and increase data offset
1153  if (descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian) {
1154  *(++offset) = 0x00;
1155  }
1156 
1157  // write actual data
1158  copy(picture.dataPointer(), picture.dataPointer() + picture.dataSize(), ++offset);
1159 }
1160 
1164 void Id3v2Frame::makeComment(unique_ptr<char[]> &buffer, std::uint32_t &bufferSize, const TagValue &comment, std::uint8_t version, Diagnostics &diag)
1165 {
1166  static const string context("making comment frame");
1167 
1168  // check whether type and other values are valid
1169  TagTextEncoding encoding = comment.dataEncoding();
1170  if (!comment.description().empty() && encoding != comment.descriptionEncoding()) {
1171  diag.emplace_back(DiagLevel::Critical, "Data encoding and description encoding aren't equal.", context);
1172  throw InvalidDataException();
1173  }
1175  if (language.length() > 3) {
1176  diag.emplace_back(DiagLevel::Critical, "The language must be 3 bytes long (ISO-639-2).", context);
1177  throw InvalidDataException();
1178  }
1179  StringData convertedDescription;
1180  string::size_type descriptionSize = comment.description().find(
1181  "\0\0", 0, encoding == TagTextEncoding::Utf16BigEndian || encoding == TagTextEncoding::Utf16LittleEndian ? 2 : 1);
1182  if (descriptionSize == string::npos) {
1183  descriptionSize = comment.description().size();
1184  }
1185  if (version < 4 && encoding == TagTextEncoding::Utf8) {
1186  // UTF-8 is only supported by ID3v2.4, so convert back to UTF-16
1188  convertedDescription = convertUtf8ToUtf16LE(comment.description().data(), descriptionSize);
1189  descriptionSize = convertedDescription.second;
1190  }
1191 
1192  // calculate needed buffer size and create buffer
1193  // note: encoding byte + language + description size + actual data size + BOMs and termination
1194  const auto data = comment.toString(encoding);
1195  const auto requiredBufferSize = 1 + 3 + descriptionSize + data.size()
1196  + (encoding == TagTextEncoding::Utf16BigEndian || encoding == TagTextEncoding::Utf16LittleEndian ? 6 : 1) + data.size();
1197  if (requiredBufferSize > numeric_limits<uint32_t>::max()) {
1198  diag.emplace_back(DiagLevel::Critical, "Required size exceeds maximum.", context);
1199  throw InvalidDataException();
1200  }
1201  buffer = make_unique<char[]>(bufferSize = static_cast<uint32_t>(requiredBufferSize));
1202  char *offset = buffer.get();
1203 
1204  // write encoding
1205  *offset = static_cast<char>(makeTextEncodingByte(encoding));
1206 
1207  // write language
1208  for (unsigned int i = 0; i < 3; ++i) {
1209  *(++offset) = (language.length() > i) ? language[i] : 0x00;
1210  }
1211 
1212  // write description
1213  offset += makeBom(offset + 1, encoding);
1214  if (convertedDescription.first) {
1215  copy(convertedDescription.first.get(), convertedDescription.first.get() + descriptionSize, ++offset);
1216  } else {
1217  comment.description().copy(++offset, descriptionSize);
1218  }
1219  offset += descriptionSize;
1220  *offset = 0x00; // terminate description and increase data offset
1222  *(++offset) = 0x00;
1223  }
1224 
1225  // write actual data
1226  offset += makeBom(offset + 1, encoding);
1227  data.copy(++offset, data.size());
1228 }
1229 
1230 } // namespace TagParser
static std::string formatList(const std::vector< std::string > &values)
Concatenates the specified string values to a list.
Definition: diagnostics.cpp:69
The Diagnostics class is a container for DiagMessage.
Definition: diagnostics.h:156
The Id3v2FrameMaker class helps making ID3v2 frames.
Definition: id3v2frame.h:22
void make(CppUtilities::BinaryWriter &writer)
Saves the frame (specified when constructing the object) using the specified writer.
Definition: id3v2frame.cpp:715
The Id3v2Frame class is used by Id3v2Tag to store the fields.
Definition: id3v2frame.h:86
bool isEncrypted() const
Returns whether the frame is encrypted.
Definition: id3v2frame.h:270
static std::size_t makeBom(char *buffer, TagTextEncoding encoding)
Writes the BOM for the specified encoding to the specified buffer.
std::uint32_t dataSize() const
Returns the size of the data stored in the frame in bytes.
Definition: id3v2frame.h:229
static std::uint8_t makeTextEncodingByte(TagTextEncoding textEncoding)
Returns a text encoding byte for the specified textEncoding.
Definition: id3v2frame.cpp:769
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:872
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:896
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.
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:922
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:948
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
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:799
bool hasGroupInformation() const
Returns whether the frame contains group information.
Definition: id3v2frame.h:278
std::uint16_t flag() const
Returns the flags.
Definition: id3v2frame.h:205
Id3v2Frame()
Constructs a new Id3v2Frame.
Definition: id3v2frame.cpp:40
bool isCompressed() const
Returns whether the frame is compressed.
Definition: id3v2frame.h:261
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.
TagTextEncoding parseTextEncodingByte(std::uint8_t textEncodingByte, Diagnostics &diag)
Returns the text encoding for the specified textEncodingByte.
Definition: id3v2frame.cpp:748
Id3v2FrameMaker prepareMaking(std::uint8_t version, Diagnostics &diag)
Prepares making.
Definition: id3v2frame.cpp:382
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:982
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).
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:884
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
friend class Id3v2FrameMaker
Definition: id3v2frame.h:88
std::uint8_t group() const
Returns the group.
Definition: id3v2frame.h:303
The exception that is thrown when the data to be parsed or to be made seems invalid and therefore can...
Definition: exceptions.h:25
The exception that is thrown when the data to be parsed holds no parsable information (e....
Definition: exceptions.h:18
The PositionInSet class describes the position of an element in a set which consists of a certain num...
Definition: positioninset.h:21
The TagField class is used by FieldMapBasedTag to store the fields.
void setTypeInfo(const TypeInfoType &typeInfo)
Sets the type info of the current TagField.
const TypeInfoType & typeInfo() const
Returns the type info of the current TagField.
const IdentifierType & id() const
Returns the id of the current TagField.
void setId(const IdentifierType &id)
Sets the id of the current Tag Field.
TagValue & value()
Returns the value of the current TagField.
The TagValue class wraps values of different types.
Definition: tagvalue.h:95
void setMimeType(std::string_view mimeType)
Sets the MIME type.
Definition: tagvalue.h:639
const std::string & mimeType() const
Returns the MIME type.
Definition: tagvalue.h:627
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:908
TagTextEncoding dataEncoding() const
Returns the data encoding.
Definition: tagvalue.h:755
void assignPosition(PositionInSet value)
Assigns the given PositionInSet value.
Definition: tagvalue.h:428
void assignData(const char *data, std::size_t length, TagDataType type=TagDataType::Binary, TagTextEncoding encoding=TagTextEncoding::Latin1)
void setDescription(std::string_view value, TagTextEncoding encoding=TagTextEncoding::Latin1)
Sets the description.
Definition: tagvalue.h:614
PositionInSet toPositionInSet() const
Converts the value of the current TagValue object to its equivalent PositionInSet representation.
Definition: tagvalue.cpp:401
TagDataType type() const
Returns the type of the assigned value.
Definition: tagvalue.h:468
void assignTimeSpan(CppUtilities::TimeSpan value)
Assigns the given TimeSpan value.
Definition: tagvalue.h:441
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:778
void clearDataAndMetadata()
Wipes assigned data including meta data.
Definition: tagvalue.h:549
std::size_t dataSize() const
Returns the size of the assigned value in bytes.
Definition: tagvalue.h:559
TagTextEncoding descriptionEncoding() const
Returns the description encoding.
Definition: tagvalue.h:765
void assignStandardGenreIndex(int index)
Assigns the given standard genre index to be assigned.
Definition: tagvalue.h:459
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:485
void setLocale(const Locale &locale)
Sets the setLocale.
Definition: tagvalue.h:681
bool isEmpty() const
Returns whether no or an empty value is assigned.
Definition: tagvalue.h:527
const std::string & description() const
Returns the description.
Definition: tagvalue.h:598
int toStandardGenreIndex() const
Converts the value of the current TagValue object to its equivalent standard genre index.
Definition: tagvalue.cpp:360
CppUtilities::TimeSpan toTimeSpan() const
Converts the value of the current TagValue object to its equivalent TimeSpan representation.
Definition: tagvalue.cpp:439
char * dataPointer()
Returns a pointer to the raw data assigned to the current instance.
Definition: tagvalue.h:570
The exception that is thrown when the data to be parsed is truncated and therefore can not be parsed ...
Definition: exceptions.h:39
The exception that is thrown when an operation fails because the detected or specified version is not...
Definition: exceptions.h:53
TAG_PARSER_EXPORT bool isPreId3v24Id(std::uint32_t id)
TAG_PARSER_EXPORT std::uint32_t convertToShortId(std::uint32_t id)
Converts the specified long frame ID to the equivalent short frame ID.
constexpr bool isLongId(std::uint32_t id)
Returns an indication whether the specified id is a long frame id.
Definition: id3v2frameids.h:85
constexpr bool isTextFrame(std::uint32_t id)
Returns an indication whether the specified id is a text frame id.
constexpr bool isShortId(std::uint32_t id)
Returns an indication whether the specified id is a short frame id.
Definition: id3v2frameids.h:93
TAG_PARSER_EXPORT bool isOnlyId3v24Id(std::uint32_t id)
TAG_PARSER_EXPORT std::uint32_t convertToLongId(std::uint32_t id)
Converts the specified short frame ID to the equivalent long frame ID.
constexpr TAG_PARSER_EXPORT std::string_view comment()
constexpr TAG_PARSER_EXPORT std::string_view language()
constexpr TAG_PARSER_EXPORT std::string_view duration()
constexpr TAG_PARSER_EXPORT std::string_view version()
Contains all classes and functions of the TagInfo library.
Definition: aaccodebook.h:10
constexpr int characterSize(TagTextEncoding encoding)
Returns the size of one character for the specified encoding in bytes.
Definition: tagvalue.h:57
u16string wideStringFromSubstring(tuple< const char *, size_t, const char * > substr, TagTextEncoding encoding)
Returns an std::u16string instance for the substring parsed using parseSubstring().
Definition: id3v2frame.cpp:117
constexpr auto maxId3v2FrameDataSize(numeric_limits< std::uint32_t >::max() - 15)
The maximum (supported) size of an ID3v2Frame.
TagTextEncoding
Specifies the text encoding.
Definition: tagvalue.h:28
int parseGenreIndex(const stringtype &denotation)
Helper function to parse the genre index.
Definition: id3v2frame.cpp:68
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
The Locale struct specifies a language and/or a country using one or more LocaleDetail objects.
Definition: localehelper.h:61