Tag Parser  7.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 
115 void Id3v2Frame::parse(BinaryReader &reader, uint32 version, uint32 maximalSize, Diagnostics &diag)
116 {
117  static const string defaultContext("parsing ID3v2 frame");
118  string context;
119 
120  // parse header
121  if (version < 3) {
122  // parse header for ID3v2.1 and ID3v2.2
123  // -> read ID
124  setId(reader.readUInt24BE());
125  if (id() & 0xFFFF0000u) {
126  m_padding = false;
127  } else {
128  // padding reached
129  m_padding = true;
130  diag.emplace_back(DiagLevel::Debug, "Frame ID starts with null-byte -> padding reached.", defaultContext);
131  throw NoDataFoundException();
132  }
133 
134  // -> update context
135  context = "parsing " % frameIdString() + " frame";
136 
137  // -> read size, check whether frame is truncated
138  m_dataSize = reader.readUInt24BE();
139  m_totalSize = m_dataSize + 6;
140  if (m_totalSize > maximalSize) {
141  diag.emplace_back(DiagLevel::Warning, "The frame is truncated and will be ignored.", context);
142  throw TruncatedDataException();
143  }
144 
145  // -> no flags/group in ID3v2.2
146  m_flag = 0;
147  m_group = 0;
148 
149  } else {
150  // parse header for ID3v2.3 and ID3v2.4
151  // -> read ID
152  setId(reader.readUInt32BE());
153  if (id() & 0xFF000000u) {
154  m_padding = false;
155  } else {
156  // padding reached
157  m_padding = true;
158  diag.emplace_back(DiagLevel::Debug, "Frame ID starts with null-byte -> padding reached.", defaultContext);
159  throw NoDataFoundException();
160  }
161 
162  // -> update context
163  context = "parsing " % frameIdString() + " frame";
164 
165  // -> read size, check whether frame is truncated
166  m_dataSize = version >= 4 ? reader.readSynchsafeUInt32BE() : reader.readUInt32BE();
167  m_totalSize = m_dataSize + 10;
168  if (m_totalSize > maximalSize) {
169  diag.emplace_back(DiagLevel::Warning, "The frame is truncated and will be ignored.", context);
170  throw TruncatedDataException();
171  }
172 
173  // -> read flags and group
174  m_flag = reader.readUInt16BE();
175  m_group = hasGroupInformation() ? reader.readByte() : 0;
176  if (isEncrypted()) {
177  // encryption is not implemented
178  diag.emplace_back(DiagLevel::Critical, "Encrypted frames aren't supported.", context);
180  }
181  }
182 
183  // frame size mustn't be 0
184  if (m_dataSize <= 0) {
185  diag.emplace_back(DiagLevel::Critical, "The frame size is 0.", context);
186  throw InvalidDataException();
187  }
188 
189  // parse the data
190  unique_ptr<char[]> buffer;
191 
192  // -> decompress data if compressed; otherwise just read it
193  if (isCompressed()) {
194  uLongf decompressedSize = version >= 4 ? reader.readSynchsafeUInt32BE() : reader.readUInt32BE();
195  if (decompressedSize < m_dataSize) {
196  diag.emplace_back(DiagLevel::Critical, "The decompressed size is smaller than the compressed size.", context);
197  throw InvalidDataException();
198  }
199  const auto bufferCompressed = make_unique<char[]>(m_dataSize);
200  reader.read(bufferCompressed.get(), m_dataSize);
201  buffer = make_unique<char[]>(decompressedSize);
202  switch (
203  uncompress(reinterpret_cast<Bytef *>(buffer.get()), &decompressedSize, reinterpret_cast<Bytef *>(bufferCompressed.get()), m_dataSize)) {
204  case Z_MEM_ERROR:
205  diag.emplace_back(DiagLevel::Critical, "Decompressing failed. The source buffer was too small.", context);
206  throw InvalidDataException();
207  case Z_BUF_ERROR:
208  diag.emplace_back(DiagLevel::Critical, "Decompressing failed. The destination buffer was too small.", context);
209  throw InvalidDataException();
210  case Z_DATA_ERROR:
211  diag.emplace_back(DiagLevel::Critical, "Decompressing failed. The input data was corrupted or incomplete.", context);
212  throw InvalidDataException();
213  case Z_OK:
214  break;
215  default:
216  diag.emplace_back(DiagLevel::Critical, "Decompressing failed (unknown reason).", context);
217  throw InvalidDataException();
218  }
219  if (decompressedSize > maxId3v2FrameDataSize) {
220  diag.emplace_back(DiagLevel::Critical, "The decompressed data exceeds the maximum supported frame size.", context);
221  throw InvalidDataException();
222  }
223  m_dataSize = static_cast<uint32>(decompressedSize);
224  } else {
225  buffer = make_unique<char[]>(m_dataSize);
226  reader.read(buffer.get(), m_dataSize);
227  }
228 
229  // -> get tag value depending of field type
230  if (Id3v2FrameIds::isTextFrame(id())) {
231  // frame contains text
232  TagTextEncoding dataEncoding = parseTextEncodingByte(static_cast<byte>(*buffer.get()), diag); // the first byte stores the encoding
233  if ((version >= 3 && (id() == Id3v2FrameIds::lTrackPosition || id() == Id3v2FrameIds::lDiskPosition))
234  || (version < 3 && (id() == Id3v2FrameIds::sTrackPosition || id() == Id3v2FrameIds::sDiskPosition))) {
235  // the track number or the disk number frame
236  try {
237  if (characterSize(dataEncoding) > 1) {
238  value().assignPosition(PositionInSet(parseWideString(buffer.get() + 1, m_dataSize - 1, dataEncoding, false, diag)));
239  } else {
240  value().assignPosition(PositionInSet(parseString(buffer.get() + 1, m_dataSize - 1, dataEncoding, false, diag)));
241  }
242  } catch (const ConversionException &) {
243  diag.emplace_back(DiagLevel::Warning, "The value of track/disk position frame is not numeric and will be ignored.", context);
244  }
245 
246  } else if ((version >= 3 && id() == Id3v2FrameIds::lLength) || (version < 3 && id() == Id3v2FrameIds::sLength)) {
247  // frame contains length
248  try {
249  string milliseconds;
250  if (dataEncoding == TagTextEncoding::Utf16BigEndian || dataEncoding == TagTextEncoding::Utf16LittleEndian) {
251  const auto parsedStringRef = parseSubstring(buffer.get() + 1, m_dataSize - 1, dataEncoding, false, diag);
252  const auto convertedStringData = dataEncoding == TagTextEncoding::Utf16BigEndian
253  ? convertUtf16BEToUtf8(get<0>(parsedStringRef), get<1>(parsedStringRef))
254  : convertUtf16LEToUtf8(get<0>(parsedStringRef), get<1>(parsedStringRef));
255  milliseconds = string(convertedStringData.first.get(), convertedStringData.second);
256  } else { // Latin-1 or UTF-8
257  milliseconds = parseString(buffer.get() + 1, m_dataSize - 1, dataEncoding, false, diag);
258  }
259  value().assignTimeSpan(TimeSpan::fromMilliseconds(stringToNumber<double>(milliseconds)));
260  } catch (const ConversionException &) {
261  diag.emplace_back(DiagLevel::Warning, "The value of the length frame is not numeric and will be ignored.", context);
262  }
263 
264  } else if ((version >= 3 && id() == Id3v2FrameIds::lGenre) || (version < 3 && id() == Id3v2FrameIds::sGenre)) {
265  // genre/content type
266  int genreIndex;
267  if (characterSize(dataEncoding) > 1) {
268  const auto genreDenotation = parseWideString(buffer.get() + 1, m_dataSize - 1, dataEncoding, false, diag);
269  genreIndex = parseGenreIndex(genreDenotation);
270  } else {
271  const auto genreDenotation = parseString(buffer.get() + 1, m_dataSize - 1, dataEncoding, false, diag);
272  genreIndex = parseGenreIndex(genreDenotation);
273  }
274  if (genreIndex != -1) {
275  // genre is specified as ID3 genre number
276  value().assignStandardGenreIndex(genreIndex);
277  } else {
278  // genre is specified as string
279  // string might be null terminated
280  const auto substr = parseSubstring(buffer.get() + 1, m_dataSize - 1, dataEncoding, false, diag);
281  value().assignData(get<0>(substr), get<1>(substr), TagDataType::Text, dataEncoding);
282  }
283  } else {
284  // any other text frame
285  const auto substr = parseSubstring(buffer.get() + 1, m_dataSize - 1, dataEncoding, false, diag);
286  value().assignData(get<0>(substr), get<1>(substr), TagDataType::Text, dataEncoding);
287  }
288 
289  } else if (version >= 3 && id() == Id3v2FrameIds::lCover) {
290  // frame stores picture
291  byte type;
292  parsePicture(buffer.get(), m_dataSize, value(), type, diag);
293  setTypeInfo(type);
294 
295  } else if (version < 3 && id() == Id3v2FrameIds::sCover) {
296  // frame stores legacy picutre
297  byte type;
298  parseLegacyPicture(buffer.get(), m_dataSize, value(), type, diag);
299  setTypeInfo(type);
300 
301  } else if (((version >= 3 && id() == Id3v2FrameIds::lComment) || (version < 3 && id() == Id3v2FrameIds::sComment))
303  // comment frame or unsynchronized lyrics frame (these two frame types have the same structure)
304  parseComment(buffer.get(), m_dataSize, value(), diag);
305 
306  } else {
307  // unknown frame
308  value().assignData(buffer.get(), m_dataSize, TagDataType::Undefined);
309  }
310 }
311 
323 {
324  return Id3v2FrameMaker(*this, version, diag);
325 }
326 
335 void Id3v2Frame::make(BinaryWriter &writer, byte version, Diagnostics &diag)
336 {
337  prepareMaking(version, diag).make(writer);
338 }
339 
343 void Id3v2Frame::reset()
344 {
345  m_flag = 0;
346  m_group = 0;
347  m_parsedVersion = 0;
348  m_dataSize = 0;
349  m_totalSize = 0;
350  m_padding = false;
351 }
352 
364 Id3v2FrameMaker::Id3v2FrameMaker(Id3v2Frame &frame, byte version, Diagnostics &diag)
365  : m_frame(frame)
366  , m_frameId(m_frame.id())
367  , m_version(version)
368 {
369  const string context("making " % m_frame.frameIdString() + " frame");
370 
371  // validate assigned data
372  const auto value(m_frame.value());
373  if (value.isEmpty()) {
374  diag.emplace_back(DiagLevel::Critical, "Cannot make an empty frame.", context);
375  throw InvalidDataException();
376  }
377  if (m_frame.isEncrypted()) {
378  diag.emplace_back(DiagLevel::Critical, "Cannot make an encrypted frame (isn't supported by this tagging library).", context);
379  throw InvalidDataException();
380  }
381  if (m_frame.hasPaddingReached()) {
382  diag.emplace_back(DiagLevel::Critical, "Cannot make a frame which is marked as padding.", context);
383  throw InvalidDataException();
384  }
385  if (version < 3 && m_frame.isCompressed()) {
386  diag.emplace_back(DiagLevel::Warning, "Compression is not supported by the version of ID3v2 and won't be applied.", context);
387  }
388  if (version < 3 && (m_frame.flag() || m_frame.group())) {
389  diag.emplace_back(DiagLevel::Warning,
390  "The existing flag and group information is not supported by the version of ID3v2 and will be ignored/discarted.", context);
391  }
392 
393  // convert frame ID if necessary
394  if (version >= 3) {
395  if (Id3v2FrameIds::isShortId(m_frameId)) {
396  // try to convert the short frame ID to its long equivalent
397  if (!(m_frameId = Id3v2FrameIds::convertToLongId(m_frameId))) {
398  diag.emplace_back(DiagLevel::Critical,
399  "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.",
400  context);
401  throw InvalidDataException();
402  }
403  }
404  } else {
405  if (Id3v2FrameIds::isLongId(m_frameId)) {
406  // try to convert the long frame ID to its short equivalent
407  if (!(m_frameId = Id3v2FrameIds::convertToShortId(m_frameId))) {
408  diag.emplace_back(DiagLevel::Critical,
409  "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.",
410  context);
411  throw InvalidDataException();
412  }
413  }
414  }
415 
416  // make actual data depending on the frame ID
417  try {
418  if (Id3v2FrameIds::isTextFrame(m_frameId)) {
419  // make text frames
420  if ((version >= 3 && (m_frameId == Id3v2FrameIds::lTrackPosition || m_frameId == Id3v2FrameIds::lDiskPosition))
421  || (version < 3 && (m_frameId == Id3v2FrameIds::sTrackPosition || m_frameId == Id3v2FrameIds::sDiskPosition))) {
422  // make track number or disk number frame
423  // -> convert the position to string
424  const auto positionStr(value.toString(TagTextEncoding::Latin1));
425  // -> warn if value is no valid position (although we just store a string after all)
427  try {
429  } catch (const ConversionException &) {
430  diag.emplace_back(DiagLevel::Warning,
431  argsToString("The track/disk number \"", positionStr, "\" is not of the expected form, eg. \"4/10\"."), context);
432  }
433  }
434  m_frame.makeString(m_data, m_decompressedSize, positionStr, TagTextEncoding::Latin1);
435  } else if ((version >= 3 && m_frameId == Id3v2FrameIds::lLength) || (version < 3 && m_frameId == Id3v2FrameIds::sLength)) {
436  // make length frame
437  const auto duration(value.toTimeSpan());
438  if (duration.isNegative()) {
439  diag.emplace_back(DiagLevel::Critical, argsToString("Assigned duration \"", duration.toString(), "\" is negative."), context);
440  throw InvalidDataException();
441  }
442  m_frame.makeString(m_data, m_decompressedSize, ConversionUtilities::numberToString(static_cast<uint64>(duration.totalMilliseconds())),
445  && ((version >= 3 && m_frameId == Id3v2FrameIds::lGenre) || (version < 3 && m_frameId == Id3v2FrameIds::sGenre))) {
446  // make pre-defined genre frame
447  m_frame.makeString(
448  m_data, m_decompressedSize, ConversionUtilities::numberToString(value.toStandardGenreIndex()), TagTextEncoding::Latin1);
449  } else {
450  // make other text frames
451  if (version <= 3 && value.dataEncoding() == TagTextEncoding::Utf8) {
452  // UTF-8 is only supported by ID3v2.4, so convert back to UTF-16
453  m_frame.makeString(
455  } else {
456  // just keep encoding of the assigned value
457  m_frame.makeString(m_data, m_decompressedSize, value.toString(), value.dataEncoding());
458  }
459  }
460 
461  } else if ((version >= 3 && m_frameId == Id3v2FrameIds::lCover) || (version < 3 && m_frameId == Id3v2FrameIds::sCover)) {
462  // make picture frame
463  m_frame.makePicture(m_data, m_decompressedSize, value, m_frame.isTypeInfoAssigned() ? m_frame.typeInfo() : 0, version);
464 
465  } else if (((version >= 3 && m_frameId == Id3v2FrameIds::lComment) || (version < 3 && m_frameId == Id3v2FrameIds::sComment))
466  || ((version >= 3 && m_frameId == Id3v2FrameIds::lUnsynchronizedLyrics)
467  || (version < 3 && m_frameId == Id3v2FrameIds::sUnsynchronizedLyrics))) {
468  // make comment frame or the unsynchronized lyrics frame
469  m_frame.makeComment(m_data, m_decompressedSize, value, version, diag);
470 
471  } else {
472  // make unknown frame
474  diag.emplace_back(DiagLevel::Critical, "Assigned value exceeds maximum size.", context);
475  throw InvalidDataException();
476  }
477  m_data = make_unique<char[]>(m_decompressedSize = static_cast<uint32>(value.dataSize()));
478  copy(value.dataPointer(), value.dataPointer() + m_decompressedSize, m_data.get());
479  }
480  } catch (const ConversionException &) {
481  try {
482  diag.emplace_back(DiagLevel::Critical,
483  argsToString("Assigned value \"", value.toString(TagTextEncoding::Utf8), "\" can not be converted appropriately."), context);
484  } catch (const ConversionException &) {
485  diag.emplace_back(DiagLevel::Critical, "Assigned value can not be converted appropriately.", context);
486  }
487  throw InvalidDataException();
488  }
489 
490  // apply compression if frame should be compressed
491  if (version >= 3 && m_frame.isCompressed()) {
492  auto compressedSize = compressBound(m_decompressedSize);
493  auto compressedData = make_unique<char[]>(compressedSize);
494  switch (compress(reinterpret_cast<Bytef *>(compressedData.get()), reinterpret_cast<uLongf *>(&compressedSize),
495  reinterpret_cast<Bytef *>(m_data.get()), m_decompressedSize)) {
496  case Z_MEM_ERROR:
497  diag.emplace_back(DiagLevel::Critical, "Decompressing failed. The source buffer was too small.", context);
498  throw InvalidDataException();
499  case Z_BUF_ERROR:
500  diag.emplace_back(DiagLevel::Critical, "Decompressing failed. The destination buffer was too small.", context);
501  throw InvalidDataException();
502  case Z_OK:;
503  }
504  if (compressedSize > maxId3v2FrameDataSize) {
505  diag.emplace_back(DiagLevel::Critical, "Compressed size exceeds maximum data size.", context);
506  throw InvalidDataException();
507  }
508  m_data.swap(compressedData);
509  m_dataSize = static_cast<uint32>(compressedSize);
510  } else {
511  m_dataSize = m_decompressedSize;
512  }
513 
514  // calculate required size
515  // -> data size
516  m_requiredSize = m_dataSize;
517  if (version < 3) {
518  // -> header size
519  m_requiredSize += 6;
520  } else {
521  // -> header size
522  m_requiredSize += 10;
523  // -> group byte
524  if (m_frame.hasGroupInformation()) {
525  m_requiredSize += 1;
526  }
527  // -> decompressed size
528  if (version >= 3 && m_frame.isCompressed()) {
529  m_requiredSize += 4;
530  }
531  }
532 }
533 
541 void Id3v2FrameMaker::make(BinaryWriter &writer)
542 {
543  if (m_version < 3) {
544  writer.writeUInt24BE(m_frameId);
545  writer.writeUInt24BE(m_dataSize);
546  } else {
547  writer.writeUInt32BE(m_frameId);
548  if (m_version >= 4) {
549  writer.writeSynchsafeUInt32BE(m_dataSize);
550  } else {
551  writer.writeUInt32BE(m_dataSize);
552  }
553  writer.writeUInt16BE(m_frame.flag());
554  if (m_frame.hasGroupInformation()) {
555  writer.writeByte(m_frame.group());
556  }
557  if (m_version >= 3 && m_frame.isCompressed()) {
558  if (m_version >= 4) {
559  writer.writeSynchsafeUInt32BE(m_decompressedSize);
560  } else {
561  writer.writeUInt32BE(m_decompressedSize);
562  }
563  }
564  }
565  writer.write(m_data.get(), m_dataSize);
566 }
567 
575 {
576  switch (textEncodingByte) {
584  return TagTextEncoding::Utf8;
585  default:
586  diag.emplace_back(
587  DiagLevel::Warning, "The charset of the frame is invalid. Latin-1 will be used.", "parsing encoding of frame " + frameIdString());
589  }
590 }
591 
596 {
597  switch (textEncoding) {
606  default:
607  return 0;
608  }
609 }
610 
625 tuple<const char *, size_t, const char *> Id3v2Frame::parseSubstring(
626  const char *buffer, std::size_t bufferSize, TagTextEncoding &encoding, bool addWarnings, Diagnostics &diag)
627 {
628  tuple<const char *, size_t, const char *> res(buffer, 0, buffer + bufferSize);
629  switch (encoding) {
632  case TagTextEncoding::Utf8: {
633  if ((bufferSize >= 3) && (ConversionUtilities::BE::toUInt24(buffer) == 0x00EFBBBF)) {
634  if (encoding == TagTextEncoding::Latin1) {
635  diag.emplace_back(DiagLevel::Critical, "Denoted character set is Latin-1 but an UTF-8 BOM is present - assuming UTF-8.",
636  "parsing frame " + frameIdString());
637  encoding = TagTextEncoding::Utf8;
638  }
639  get<0>(res) += 3;
640  }
641  const char *pos = get<0>(res);
642  for (; *pos != 0x00; ++pos) {
643  if (pos < get<2>(res)) {
644  ++get<1>(res);
645  } else {
646  if (addWarnings) {
647  diag.emplace_back(
648  DiagLevel::Warning, "String in frame is not terminated properly.", "parsing termination of frame " + frameIdString());
649  }
650  break;
651  }
652  }
653  get<2>(res) = pos + 1;
654  break;
655  }
658  if (bufferSize >= 2) {
659  switch (ConversionUtilities::LE::toUInt16(buffer)) {
660  case 0xFEFF:
661  if (encoding == TagTextEncoding::Utf16BigEndian) {
662  diag.emplace_back(DiagLevel::Critical,
663  "Denoted character set is UTF-16 Big Endian but UTF-16 Little Endian BOM is present - assuming UTF-16 LE.",
664  "parsing frame " + frameIdString());
666  }
667  get<0>(res) += 2;
668  break;
669  case 0xFFFE:
671  get<0>(res) += 2;
672  }
673  }
674  const uint16 *pos = reinterpret_cast<const uint16 *>(get<0>(res));
675  for (; *pos != 0x0000; ++pos) {
676  if (pos < reinterpret_cast<const uint16 *>(get<2>(res))) {
677  get<1>(res) += 2;
678  } else {
679  if (addWarnings) {
680  diag.emplace_back(
681  DiagLevel::Warning, "Wide string in frame is not terminated properly.", "parsing termination of frame " + frameIdString());
682  }
683  break;
684  }
685  }
686  get<2>(res) = reinterpret_cast<const char *>(pos + 1);
687  break;
688  }
689  }
690  return res;
691 }
692 
698 string Id3v2Frame::parseString(const char *buffer, size_t dataSize, TagTextEncoding &encoding, bool addWarnings, Diagnostics &diag)
699 {
700  const auto substr = parseSubstring(buffer, dataSize, encoding, addWarnings, diag);
701  return string(get<0>(substr), get<1>(substr));
702 }
703 
711 u16string Id3v2Frame::parseWideString(const char *buffer, size_t dataSize, TagTextEncoding &encoding, bool addWarnings, Diagnostics &diag)
712 {
713  const auto substr = parseSubstring(buffer, dataSize, encoding, addWarnings, diag);
714  u16string res(reinterpret_cast<u16string::const_pointer>(get<0>(substr)), get<1>(substr) / 2);
715  TagValue::ensureHostByteOrder(res, encoding);
716  return res;
717 }
718 
728 void Id3v2Frame::parseBom(const char *buffer, size_t maxSize, TagTextEncoding &encoding, Diagnostics &diag)
729 {
730  switch (encoding) {
733  if ((maxSize >= 2) && (ConversionUtilities::BE::toUInt16(buffer) == 0xFFFE)) {
735  } else if ((maxSize >= 2) && (ConversionUtilities::BE::toUInt16(buffer) == 0xFEFF)) {
737  }
738  break;
739  default:
740  if ((maxSize >= 3) && (ConversionUtilities::BE::toUInt24(buffer) == 0x00EFBBBF)) {
741  encoding = TagTextEncoding::Utf8;
742  diag.emplace_back(DiagLevel::Warning, "UTF-8 byte order mark found in text frame.", "parsing byte oder mark of frame " + frameIdString());
743  }
744  }
745 }
746 
754 void Id3v2Frame::parseLegacyPicture(const char *buffer, std::size_t maxSize, TagValue &tagValue, byte &typeInfo, Diagnostics &diag)
755 {
756  static const string context("parsing ID3v2.2 picture frame");
757  if (maxSize < 6) {
758  diag.emplace_back(DiagLevel::Critical, "Picture frame is incomplete.", context);
759  throw TruncatedDataException();
760  }
761  const char *end = buffer + maxSize;
762  auto dataEncoding = parseTextEncodingByte(static_cast<byte>(*buffer), diag); // the first byte stores the encoding
763  typeInfo = static_cast<unsigned char>(*(buffer + 4));
764  auto substr = parseSubstring(buffer + 5, static_cast<size_t>(end - 5 - buffer), dataEncoding, true, diag);
765  tagValue.setDescription(string(get<0>(substr), get<1>(substr)), dataEncoding);
766  if (get<2>(substr) >= end) {
767  diag.emplace_back(DiagLevel::Critical, "Picture frame is incomplete (actual data is missing).", context);
768  throw TruncatedDataException();
769  }
770  tagValue.assignData(get<2>(substr), static_cast<size_t>(end - get<2>(substr)), TagDataType::Picture, dataEncoding);
771 }
772 
780 void Id3v2Frame::parsePicture(const char *buffer, std::size_t maxSize, TagValue &tagValue, byte &typeInfo, Diagnostics &diag)
781 {
782  static const string context("parsing ID3v2.3 picture frame");
783  const char *end = buffer + maxSize;
784  auto dataEncoding = parseTextEncodingByte(static_cast<byte>(*buffer), diag); // the first byte stores the encoding
785  auto mimeTypeEncoding = TagTextEncoding::Latin1;
786  auto substr = parseSubstring(buffer + 1, maxSize - 1, mimeTypeEncoding, true, diag);
787  if (get<1>(substr)) {
788  tagValue.setMimeType(string(get<0>(substr), get<1>(substr)));
789  }
790  if (get<2>(substr) >= end) {
791  diag.emplace_back(DiagLevel::Critical, "Picture frame is incomplete (type info, description and actual data are missing).", context);
792  throw TruncatedDataException();
793  }
794  typeInfo = static_cast<unsigned char>(*get<2>(substr));
795  if (++get<2>(substr) >= end) {
796  diag.emplace_back(DiagLevel::Critical, "Picture frame is incomplete (description and actual data are missing).", context);
797  throw TruncatedDataException();
798  }
799  substr = parseSubstring(get<2>(substr), static_cast<size_t>(end - get<2>(substr)), dataEncoding, true, diag);
800  tagValue.setDescription(string(get<0>(substr), get<1>(substr)), dataEncoding);
801  if (get<2>(substr) >= end) {
802  diag.emplace_back(DiagLevel::Critical, "Picture frame is incomplete (actual data is missing).", context);
803  throw TruncatedDataException();
804  }
805  tagValue.assignData(get<2>(substr), static_cast<size_t>(end - get<2>(substr)), TagDataType::Picture, dataEncoding);
806 }
807 
814 void Id3v2Frame::parseComment(const char *buffer, std::size_t dataSize, TagValue &tagValue, Diagnostics &diag)
815 {
816  static const string context("parsing comment/unsynchronized lyrics frame");
817  const char *end = buffer + dataSize;
818  if (dataSize < 5) {
819  diag.emplace_back(DiagLevel::Critical, "Comment frame is incomplete.", context);
820  throw TruncatedDataException();
821  }
822  TagTextEncoding dataEncoding = parseTextEncodingByte(static_cast<byte>(*buffer), diag);
823  if (*(++buffer)) {
824  tagValue.setLanguage(string(buffer, 3));
825  }
826  auto substr = parseSubstring(buffer += 3, dataSize -= 4, dataEncoding, true, diag);
827  tagValue.setDescription(string(get<0>(substr), get<1>(substr)), dataEncoding);
828  if (get<2>(substr) > end) {
829  diag.emplace_back(DiagLevel::Critical, "Comment frame is incomplete (description not terminated?).", context);
830  throw TruncatedDataException();
831  }
832  substr = parseSubstring(get<2>(substr), static_cast<size_t>(end - get<2>(substr)), dataEncoding, false, diag);
833  tagValue.assignData(get<0>(substr), get<1>(substr), TagDataType::Text, dataEncoding);
834 }
835 
843 void Id3v2Frame::makeString(std::unique_ptr<char[]> &buffer, uint32 &bufferSize, const std::string &value, TagTextEncoding encoding)
844 {
845  makeEncodingAndData(buffer, bufferSize, encoding, value.data(), value.size());
846 }
847 
857  std::unique_ptr<char[]> &buffer, uint32 &bufferSize, TagTextEncoding encoding, const char *data, std::size_t dataSize)
858 {
859  // calculate buffer size and allocate buffer
860  if (!data) {
861  dataSize = 0;
862  }
863  char *bufferDataAddress;
864  switch (encoding) {
867  case TagTextEncoding::Unspecified: // assumption
868  // allocate buffer
869  buffer = make_unique<char[]>(bufferSize = 1 + dataSize + 1);
870  buffer[0] = static_cast<char>(makeTextEncodingByte(encoding)); // set text encoding byte
871  bufferDataAddress = buffer.get() + 1;
872  break;
875  // allocate buffer
876  buffer = make_unique<char[]>(bufferSize = 1 + 2 + dataSize + 2);
877  buffer[0] = static_cast<char>(makeTextEncodingByte(encoding)); // set text encoding byte
878  ConversionUtilities::LE::getBytes(
879  encoding == TagTextEncoding::Utf16LittleEndian ? static_cast<uint16>(0xFEFF) : static_cast<uint16>(0xFFFE), buffer.get() + 1);
880  bufferDataAddress = buffer.get() + 3;
881  break;
882  default:
883  return;
884  }
885 
886  // write string data
887  if (dataSize) {
888  copy(data, data + dataSize, bufferDataAddress);
889  }
890 }
891 
896 size_t Id3v2Frame::makeBom(char *buffer, TagTextEncoding encoding)
897 {
898  switch (encoding) {
900  ConversionUtilities::LE::getBytes(static_cast<uint16>(0xFEFF), buffer);
901  return 2;
903  ConversionUtilities::BE::getBytes(static_cast<uint16>(0xFEFF), buffer);
904  return 2;
905  default:
906  return 0;
907  }
908 }
909 
913 void Id3v2Frame::makeLegacyPicture(unique_ptr<char[]> &buffer, uint32 &bufferSize, const TagValue &picture, byte typeInfo)
914 {
915  // determine description
916  TagTextEncoding descriptionEncoding = picture.descriptionEncoding();
917  StringData convertedDescription;
918  string::size_type descriptionSize = picture.description().find(
919  "\0\0", 0, descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? 2 : 1);
920  if (descriptionSize == string::npos) {
921  descriptionSize = picture.description().size();
922  }
923  if (descriptionEncoding == TagTextEncoding::Utf8) {
924  // UTF-8 is only supported by ID3v2.4, so convert back to UTF-16
925  descriptionEncoding = TagTextEncoding::Utf16LittleEndian;
926  convertedDescription = convertUtf8ToUtf16LE(picture.description().data(), descriptionSize);
927  descriptionSize = convertedDescription.second;
928  }
929  // calculate needed buffer size and create buffer
930  const uint32 dataSize = picture.dataSize();
931  buffer = make_unique<char[]>(bufferSize = 1 + 3 + 1 + descriptionSize
932  + (descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? 4 : 1)
933  + dataSize);
934  // note: encoding byte + image format + picture type byte + description size + 1 or 2 null bytes (depends on encoding) + data size
935  char *offset = buffer.get();
936  // write encoding byte
937  *offset = static_cast<char>(makeTextEncodingByte(descriptionEncoding));
938  // write mime type
939  const char *imageFormat;
940  if (picture.mimeType() == "image/jpeg") {
941  imageFormat = "JPG";
942  } else if (picture.mimeType() == "image/png") {
943  imageFormat = "PNG";
944  } else if (picture.mimeType() == "image/gif") {
945  imageFormat = "GIF";
946  } else if (picture.mimeType() == "-->") {
947  imageFormat = picture.mimeType().data();
948  } else {
949  imageFormat = "UND";
950  }
951  strncpy(++offset, imageFormat, 3);
952  // write picture type
953  *(offset += 3) = static_cast<char>(typeInfo);
954  // write description
955  offset += makeBom(offset + 1, descriptionEncoding);
956  if (convertedDescription.first) {
957  copy(convertedDescription.first.get(), convertedDescription.first.get() + descriptionSize, ++offset);
958  } else {
959  picture.description().copy(++offset, descriptionSize);
960  }
961  *(offset += descriptionSize) = 0x00; // terminate description and increase data offset
962  if (descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian) {
963  *(++offset) = 0x00;
964  }
965  // write actual data
966  copy(picture.dataPointer(), picture.dataPointer() + picture.dataSize(), ++offset);
967 }
968 
972 void Id3v2Frame::makePicture(std::unique_ptr<char[]> &buffer, uint32 &bufferSize, const TagValue &picture, byte typeInfo, byte version)
973 {
974  if (version < 3) {
975  makeLegacyPicture(buffer, bufferSize, picture, typeInfo);
976  return;
977  }
978 
979  // determine description
980  TagTextEncoding descriptionEncoding = picture.descriptionEncoding();
981  StringData convertedDescription;
982  string::size_type descriptionSize = picture.description().find(
983  "\0\0", 0, descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? 2 : 1);
984  if (descriptionSize == string::npos) {
985  descriptionSize = picture.description().size();
986  }
987  if (version < 4 && descriptionEncoding == TagTextEncoding::Utf8) {
988  // UTF-8 is only supported by ID3v2.4, so convert back to UTF-16
989  descriptionEncoding = TagTextEncoding::Utf16LittleEndian;
990  convertedDescription = convertUtf8ToUtf16LE(picture.description().data(), descriptionSize);
991  descriptionSize = convertedDescription.second;
992  }
993  // determine mime-type
994  string::size_type mimeTypeSize = picture.mimeType().find('\0');
995  if (mimeTypeSize == string::npos) {
996  mimeTypeSize = picture.mimeType().length();
997  }
998  // calculate needed buffer size and create buffer
999  const uint32 dataSize = picture.dataSize();
1000  buffer = make_unique<char[]>(bufferSize = 1 + mimeTypeSize + 1 + 1 + descriptionSize
1001  + (descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? 4 : 1)
1002  + dataSize);
1003  // note: encoding byte + mime type size + 0 byte + picture type byte + description size + 1 or 4 null bytes (depends on encoding) + data size
1004  char *offset = buffer.get();
1005  // write encoding byte
1006  *offset = static_cast<char>(makeTextEncodingByte(descriptionEncoding));
1007  // write mime type
1008  picture.mimeType().copy(++offset, mimeTypeSize);
1009  *(offset += mimeTypeSize) = 0x00; // terminate mime type
1010  // write picture type
1011  *(++offset) = static_cast<char>(typeInfo);
1012  // write description
1013  offset += makeBom(offset + 1, descriptionEncoding);
1014  if (convertedDescription.first) {
1015  copy(convertedDescription.first.get(), convertedDescription.first.get() + descriptionSize, ++offset);
1016  } else {
1017  picture.description().copy(++offset, descriptionSize);
1018  }
1019  *(offset += descriptionSize) = 0x00; // terminate description and increase data offset
1020  if (descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian) {
1021  *(++offset) = 0x00;
1022  }
1023  // write actual data
1024  copy(picture.dataPointer(), picture.dataPointer() + picture.dataSize(), ++offset);
1025 }
1026 
1030 void Id3v2Frame::makeComment(unique_ptr<char[]> &buffer, uint32 &bufferSize, const TagValue &comment, byte version, Diagnostics &diag)
1031 {
1032  static const string context("making comment frame");
1033  // check type and other values are valid
1034  TagTextEncoding encoding = comment.dataEncoding();
1035  if (!comment.description().empty() && encoding != comment.descriptionEncoding()) {
1036  diag.emplace_back(DiagLevel::Critical, "Data enoding and description encoding aren't equal.", context);
1037  throw InvalidDataException();
1038  }
1039  const string &lng = comment.language();
1040  if (lng.length() > 3) {
1041  diag.emplace_back(DiagLevel::Critical, "The language must be 3 bytes long (ISO-639-2).", context);
1042  throw InvalidDataException();
1043  }
1044  StringData convertedDescription;
1045  string::size_type descriptionSize = comment.description().find(
1046  "\0\0", 0, encoding == TagTextEncoding::Utf16BigEndian || encoding == TagTextEncoding::Utf16LittleEndian ? 2 : 1);
1047  if (descriptionSize == string::npos) {
1048  descriptionSize = comment.description().size();
1049  }
1050  if (version < 4 && encoding == TagTextEncoding::Utf8) {
1051  // UTF-8 is only supported by ID3v2.4, so convert back to UTF-16
1053  convertedDescription = convertUtf8ToUtf16LE(comment.description().data(), descriptionSize);
1054  descriptionSize = convertedDescription.second;
1055  }
1056  // calculate needed buffer size and create buffer
1057  const auto data = comment.toString(encoding);
1058  buffer = make_unique<char[]>(bufferSize = 1 + 3 + descriptionSize + data.size()
1059  + (encoding == TagTextEncoding::Utf16BigEndian || encoding == TagTextEncoding::Utf16LittleEndian ? 6 : 1) + data.size());
1060  // note: encoding byte + language + description size + actual data size + BOMs and termination
1061  char *offset = buffer.get();
1062  // write encoding
1063  *offset = static_cast<char>(makeTextEncodingByte(encoding));
1064  // write language
1065  for (unsigned int i = 0; i < 3; ++i) {
1066  *(++offset) = (lng.length() > i) ? lng[i] : 0x00;
1067  }
1068  // write description
1069  offset += makeBom(offset + 1, encoding);
1070  if (convertedDescription.first) {
1071  copy(convertedDescription.first.get(), convertedDescription.first.get() + descriptionSize, ++offset);
1072  } else {
1073  comment.description().copy(++offset, descriptionSize);
1074  }
1075  offset += descriptionSize;
1076  *offset = 0x00; // terminate description and increase data offset
1078  *(++offset) = 0x00;
1079  }
1080  // write actual data
1081  offset += makeBom(offset + 1, encoding);
1082  data.copy(++offset, data.size());
1083 }
1084 
1085 } // namespace TagParser
TagTextEncoding dataEncoding() const
Returns the data encoding.
Definition: tagvalue.h:535
uint16 flag() const
Returns the flags.
Definition: id3v2frame.h:187
bool isShortId(uint32 id)
Returns an indication whether the specified id is a short frame id.
Definition: id3v2frameids.h:77
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.
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).
Definition: id3v2frame.cpp:913
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:260
byte group() const
Returns the group.
Definition: id3v2frame.h:285
TagTextEncoding descriptionEncoding() const
Returns the description encoding.
Definition: tagvalue.h:545
const std::string & description() const
Returns the description.
Definition: tagvalue.h:443
TAG_PARSER_EXPORT const char * version()
void setMimeType(const std::string &mimeType)
Sets the MIME type.
Definition: tagvalue.h:478
byte makeTextEncodingByte(TagTextEncoding textEncoding)
Returns a text encoding byte for the specified textEncoding.
Definition: id3v2frame.cpp:595
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:574
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:335
std::u16string parseWideString(const char *buffer, std::size_t dataSize, TagTextEncoding &encoding, bool addWarnings, Diagnostics &diag)
Parses a substring in the specified buffer.
Definition: id3v2frame.cpp:711
STL namespace.
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.
TAG_PARSER_EXPORT const char * comment()
constexpr auto maxId3v2FrameDataSize(numeric_limits< uint32 >::max() - 15)
The maximum (supported) size of an ID3v2Frame.
std::string parseString(const char *buffer, std::size_t maxSize, TagTextEncoding &encoding, bool addWarnings, Diagnostics &diag)
Parses a substring in the specified buffer.
Definition: id3v2frame.cpp:698
bool isCompressed() const
Returns whether the frame is compressed.
Definition: id3v2frame.h:243
void makePicture(std::unique_ptr< char[]> &buffer, uint32 &bufferSize, const TagValue &picture, byte typeInfo, byte version)
Writes the specified picture to the specified buffer.
Definition: id3v2frame.cpp:972
The Id3v2Frame class is used by Id3v2Tag to store the fields.
Definition: id3v2frame.h:84
bool isEmpty() const
Returns an indication whether an value is assigned.
Definition: tagvalue.h:384
const std::string & mimeType() const
Returns the MIME type.
Definition: tagvalue.h:467
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:754
std::size_t dataSize() const
Returns the size of the assigned value in bytes.
Definition: tagvalue.h:416
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:728
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:843
uint32 convertToShortId(uint32 id)
Converts the specified long frame ID to the equivalent short frame ID.
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:856
ChronoUtilities::TimeSpan toTimeSpan() const
Converts the value of the current TagValue object to its equivalent TimeSpan representation.
Definition: tagvalue.cpp:291
bool isLongId(uint32 id)
Returns an indication whether the specified id is a long frame id.
Definition: id3v2frameids.h:69
Contains utility classes helping to read and write streams.
bool isEncrypted() const
Returns whether the frame is encrypted.
Definition: id3v2frame.h:252
std::string frameIdString() const
Returns the frame ID as string.
Definition: id3v2frame.h:179
void assignPosition(PositionInSet value)
Assigns the given PositionInSet value.
Definition: tagvalue.h:308
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:814
void assignStandardGenreIndex(int index)
Assigns the given standard genre index to be assigned.
Definition: tagvalue.h:339
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:780
std::tuple< const char *, size_t, const char * > parseSubstring(const char *buffer, std::size_t maxSize, TagTextEncoding &encoding, bool addWarnings, Diagnostics &diag)
Parses a substring in the specified buffer.
Definition: id3v2frame.cpp:625
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:499
char * dataPointer()
Returns a pointer to the raw data assigned to the current instance.
Definition: tagvalue.h:427
std::size_t makeBom(char *buffer, TagTextEncoding encoding)
Writes the BOM for the specified encoding to the specified buffer.
Definition: id3v2frame.cpp:896
TagDataType type() const
Returns the type of the assigned value.
Definition: tagvalue.h:348
bool isTextFrame(uint32 id)
Returns an indication whether the specified id is a text frame id.
Definition: id3v2frameids.h:85
void make(IoUtilities::BinaryWriter &writer)
Saves the frame (specified when constructing the object) using the specified writer.
Definition: id3v2frame.cpp:541
TAG_PARSER_EXPORT const char * duration()
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
void setDescription(const std::string &value, TagTextEncoding encoding=TagTextEncoding::Latin1)
Sets the description.
Definition: tagvalue.h:456
void assignData(const char *data, std::size_t length, TagDataType type=TagDataType::Binary, TagTextEncoding encoding=TagTextEncoding::Latin1)
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:360
void assignTimeSpan(ChronoUtilities::TimeSpan value)
Assigns the given TimeSpan value.
Definition: tagvalue.h:321
constexpr int characterSize(TagTextEncoding encoding)
Returns the size of one character for the specified encoding in bytes.
Definition: tagvalue.h:35
The TagValue class wraps values of different types.
Definition: tagvalue.h:64
uint32 dataSize() const
Returns the size of the data stored in the frame in bytes.
Definition: id3v2frame.h:211
Id3v2FrameMaker prepareMaking(byte version, Diagnostics &diag)
Prepares making.
Definition: id3v2frame.cpp:322
TagTextEncoding
Specifies the text encoding.
Definition: tagvalue.h:23
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:115
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:154
TagValue & value()
Returns the value of the current TagField.