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