Tag Parser  8.0.1
C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags
tagvalue.cpp
Go to the documentation of this file.
1 #include "./tagvalue.h"
2 #include "./tag.h"
3 
4 #include "./id3/id3genres.h"
5 
6 #include <c++utilities/conversion/binaryconversion.h>
7 #include <c++utilities/conversion/conversionexception.h>
8 #include <c++utilities/conversion/stringbuilder.h>
9 #include <c++utilities/conversion/stringconversion.h>
10 
11 #include <algorithm>
12 #include <cstring>
13 #include <utility>
14 
15 using namespace std;
16 using namespace ConversionUtilities;
17 using namespace ChronoUtilities;
18 
19 namespace TagParser {
20 
24 const char *tagDataTypeString(TagDataType dataType)
25 {
26  switch (dataType) {
27  case TagDataType::Text:
28  return "text";
29  case TagDataType::Integer:
30  return "integer";
31  case TagDataType::PositionInSet:
32  return "position in set";
33  case TagDataType::StandardGenreIndex:
34  return "genre index";
35  case TagDataType::TimeSpan:
36  return "time span";
38  return "date time";
39  case TagDataType::Picture:
40  return "picture";
41  case TagDataType::Binary:
42  return "binary";
43  default:
44  return "undefined";
45  }
46 }
47 
59 TagValue::TagValue(const TagValue &other)
60  : m_size(other.m_size)
61  , m_desc(other.m_desc)
62  , m_mimeType(other.m_mimeType)
63  , m_language(other.m_language)
64  , m_type(other.m_type)
65  , m_encoding(other.m_encoding)
66  , m_descEncoding(other.m_descEncoding)
67  , m_labeledAsReadonly(other.m_labeledAsReadonly)
68 {
69  if (!other.isEmpty()) {
70  m_ptr = make_unique<char[]>(m_size);
71  std::copy(other.m_ptr.get(), other.m_ptr.get() + other.m_size, m_ptr.get());
72  }
73 }
74 
79 {
80  if (this == &other) {
81  return *this;
82  }
83  m_size = other.m_size;
84  m_type = other.m_type;
85  m_desc = other.m_desc;
86  m_mimeType = other.m_mimeType;
87  m_language = other.m_language;
88  m_labeledAsReadonly = other.m_labeledAsReadonly;
89  m_encoding = other.m_encoding;
90  m_descEncoding = other.m_descEncoding;
91  if (other.isEmpty()) {
92  m_ptr.reset();
93  } else {
94  m_ptr = make_unique<char[]>(m_size);
95  std::copy(other.m_ptr.get(), other.m_ptr.get() + other.m_size, m_ptr.get());
96  }
97  return *this;
98 }
99 
108 bool TagValue::operator==(const TagValue &other) const
109 {
110  // check whether meta-data is equal
111  if (m_desc != other.m_desc || (!m_desc.empty() && m_descEncoding != other.m_descEncoding) || m_mimeType != other.m_mimeType
112  || m_language != other.m_language || m_labeledAsReadonly != other.m_labeledAsReadonly) {
113  return false;
114  }
115 
116  // check for equality if both types are identical
117  if (m_type == other.m_type) {
118  switch (m_type) {
119  case TagDataType::Text:
120  if (m_size != other.m_size && m_encoding != other.m_encoding) {
121  // don't consider differently encoded text values equal
122  return false;
123  }
124  return strncmp(m_ptr.get(), other.m_ptr.get(), m_size) == 0;
126  return toPositionInSet() == other.toPositionInSet();
128  return toInteger() == other.toInteger();
130  return toStandardGenreIndex() == other.toStandardGenreIndex();
132  return toTimeSpan() == other.toTimeSpan();
134  return toDateTime() == other.toDateTime();
136  case TagDataType::Binary:
138  if (m_size != other.m_size) {
139  return false;
140  }
141  return strncmp(m_ptr.get(), other.m_ptr.get(), m_size) == 0;
142  }
143  return false;
144  }
145 
146  // check for equality if types are different by comparing the string representation
147  try {
148  return toString() == other.toString(m_encoding);
149  } catch (const ConversionException &) {
150  return false;
151  }
152 }
153 
162 {
163  m_desc.clear();
164  m_mimeType.clear();
165  m_language.clear();
166  m_labeledAsReadonly = false;
167  m_encoding = TagTextEncoding::Latin1;
168  m_descEncoding = TagTextEncoding::Latin1;
169  m_type = TagDataType::Undefined;
170 }
171 
177 int32 TagValue::toInteger() const
178 {
179  if (isEmpty()) {
180  return 0;
181  }
182  switch (m_type) {
183  case TagDataType::Text:
184  switch (m_encoding) {
188  return ConversionUtilities::bufferToNumber<int32>(m_ptr.get(), m_size);
191  u16string u16str(reinterpret_cast<char16_t *>(m_ptr.get()), m_size / 2);
192  ensureHostByteOrder(u16str, m_encoding);
193  return ConversionUtilities::stringToNumber<int32>(u16str);
194  }
198  if (m_size == sizeof(int32)) {
199  return *reinterpret_cast<int32 *>(m_ptr.get());
200  }
201  throw ConversionException("Can not convert assigned data to integer because the data size is not appropriate.");
202  default:
203  throw ConversionException(argsToString("Can not convert ", tagDataTypeString(m_type), " to integer."));
204  }
205 }
206 
213 {
214  if (isEmpty()) {
215  return 0;
216  }
217  int index = 0;
218  switch (m_type) {
219  case TagDataType::Text: {
220  try {
221  index = toInteger();
222  } catch (const ConversionException &) {
224  if (m_encoding == TagTextEncoding::Latin1) {
225  // no need to convert Latin-1 to UTF-8 (makes no difference in case of genre strings)
226  encoding = TagTextEncoding::Unspecified;
227  }
228  index = Id3Genres::indexFromString(toString(encoding));
229  }
230  break;
231  }
234  if (m_size != sizeof(int32)) {
235  throw ConversionException("The assigned index/integer is of unappropriate size.");
236  }
237  index = static_cast<int>(*reinterpret_cast<int32 *>(m_ptr.get()));
238  break;
239  default:
240  throw ConversionException(argsToString("Can not convert ", tagDataTypeString(m_type), " to genre index."));
241  }
242  if (!Id3Genres::isIndexSupported(index)) {
243  throw ConversionException("The assigned number is not a valid standard genre index.");
244  }
245  return index;
246 }
247 
254 {
255  if (isEmpty()) {
256  return PositionInSet();
257  }
258  switch (m_type) {
259  case TagDataType::Text:
260  switch (m_encoding) {
264  return PositionInSet(string(m_ptr.get(), m_size));
267  u16string u16str(reinterpret_cast<char16_t *>(m_ptr.get()), m_size / 2);
268  ensureHostByteOrder(u16str, m_encoding);
269  return PositionInSet(u16str);
270  }
273  switch (m_size) {
274  case sizeof(int32):
275  return PositionInSet(*(reinterpret_cast<int32 *>(m_ptr.get())));
276  case 2 * sizeof(int32):
277  return PositionInSet(*(reinterpret_cast<int32 *>(m_ptr.get())), *(reinterpret_cast<int32 *>(m_ptr.get() + sizeof(int32))));
278  default:
279  throw ConversionException("The size of the assigned data is not appropriate.");
280  }
281  default:
282  throw ConversionException(argsToString("Can not convert ", tagDataTypeString(m_type), " to position in set."));
283  }
284 }
285 
292 {
293  if (isEmpty()) {
294  return TimeSpan();
295  }
296  switch (m_type) {
297  case TagDataType::Text:
298  return TimeSpan::fromString(toString(m_encoding == TagTextEncoding::Utf8 ? TagTextEncoding::Utf8 : TagTextEncoding::Latin1));
301  switch (m_size) {
302  case sizeof(int32):
303  return TimeSpan(*(reinterpret_cast<int32 *>(m_ptr.get())));
304  case sizeof(int64):
305  return TimeSpan(*(reinterpret_cast<int64 *>(m_ptr.get())));
306  default:
307  throw ConversionException("The size of the assigned integer is not appropriate for conversion to time span.");
308  }
309  default:
310  throw ConversionException(argsToString("Can not convert ", tagDataTypeString(m_type), " to time span."));
311  }
312 }
313 
319 DateTime TagValue::toDateTime() const
320 {
321  if (isEmpty()) {
322  return DateTime();
323  }
324  switch (m_type) {
325  case TagDataType::Text:
326  return DateTime::fromString(toString(m_encoding == TagTextEncoding::Utf8 ? TagTextEncoding::Utf8 : TagTextEncoding::Latin1));
329  if (m_size == sizeof(int32)) {
330  return DateTime(*(reinterpret_cast<uint32 *>(m_ptr.get())));
331  } else if (m_size == sizeof(int64)) {
332  return DateTime(*(reinterpret_cast<uint64 *>(m_ptr.get())));
333  } else {
334  throw ConversionException("The size of the assigned integer is not appropriate for conversion to date time.");
335  }
336  default:
337  throw ConversionException(argsToString("Can not convert ", tagDataTypeString(m_type), " to date time."));
338  }
339 }
340 
344 pair<const char *, float> encodingParameter(TagTextEncoding tagTextEncoding)
345 {
346  switch (tagTextEncoding) {
348  return make_pair("ISO-8859-1", 1.0f);
350  return make_pair("UTF-8", 1.0f);
352  return make_pair("UTF-16LE", 2.0f);
354  return make_pair("UTF-16BE", 2.0f);
355  default:
356  return make_pair(nullptr, 0.0f);
357  }
358 }
359 
370 {
371  if (m_encoding == encoding) {
372  return;
373  }
374  if (type() == TagDataType::Text) {
375  StringData encodedData;
376  switch (encoding) {
378  // use pre-defined methods when encoding to UTF-8
379  switch (dataEncoding()) {
381  encodedData = convertLatin1ToUtf8(m_ptr.get(), m_size);
382  break;
384  encodedData = convertUtf16LEToUtf8(m_ptr.get(), m_size);
385  break;
387  encodedData = convertUtf16BEToUtf8(m_ptr.get(), m_size);
388  break;
389  default:;
390  }
391  break;
392  default: {
393  // otherwise, determine input and output parameter to use general covertString method
394  const auto inputParameter = encodingParameter(dataEncoding());
395  const auto outputParameter = encodingParameter(encoding);
396  encodedData
397  = convertString(inputParameter.first, outputParameter.first, m_ptr.get(), m_size, outputParameter.second / inputParameter.second);
398  }
399  }
400  // can't just move the encoded data because it needs to be deleted with free
401  m_ptr = make_unique<char[]>(m_size = encodedData.second);
402  copy(encodedData.first.get(), encodedData.first.get() + encodedData.second, m_ptr.get());
403  }
404  m_encoding = encoding;
405 }
406 
412 {
413  if (type() == TagDataType::Text && !tag->canEncodingBeUsed(dataEncoding())) {
415  }
416 }
417 
427 void TagValue::toString(string &result, TagTextEncoding encoding) const
428 {
429  if (isEmpty()) {
430  result.clear();
431  return;
432  }
433 
434  switch (m_type) {
435  case TagDataType::Text:
436  if (encoding == TagTextEncoding::Unspecified || dataEncoding() == TagTextEncoding::Unspecified || encoding == dataEncoding()) {
437  result.assign(m_ptr.get(), m_size);
438  } else {
439  StringData encodedData;
440  switch (encoding) {
442  // use pre-defined methods when encoding to UTF-8
443  switch (dataEncoding()) {
445  encodedData = convertLatin1ToUtf8(m_ptr.get(), m_size);
446  break;
448  encodedData = convertUtf16LEToUtf8(m_ptr.get(), m_size);
449  break;
451  encodedData = convertUtf16BEToUtf8(m_ptr.get(), m_size);
452  break;
453  default:;
454  }
455  break;
456  default: {
457  // otherwise, determine input and output parameter to use general covertString method
458  const auto inputParameter = encodingParameter(dataEncoding());
459  const auto outputParameter = encodingParameter(encoding);
460  encodedData
461  = convertString(inputParameter.first, outputParameter.first, m_ptr.get(), m_size, outputParameter.second / inputParameter.second);
462  }
463  }
464  result.assign(encodedData.first.get(), encodedData.second);
465  }
466  return;
468  result = ConversionUtilities::numberToString(toInteger());
469  break;
471  result = toPositionInSet().toString();
472  break;
474  if (const char *genreName = Id3Genres::stringFromIndex(toInteger())) {
475  result.assign(genreName);
476  break;
477  } else {
478  throw ConversionException("No string representation for the assigned standard genre index available.");
479  }
481  result = toTimeSpan().toString();
482  break;
484  result = toDateTime().toString();
485  break;
486  default:
487  throw ConversionException(argsToString("Can not convert ", tagDataTypeString(m_type), " to string."));
488  }
490  auto encodedData = encoding == TagTextEncoding::Utf16LittleEndian ? convertUtf8ToUtf16LE(result.data(), result.size())
491  : convertUtf8ToUtf16BE(result.data(), result.size());
492  result.assign(encodedData.first.get(), encodedData.second);
493  }
494 }
495 
503 void TagValue::toWString(std::u16string &result, TagTextEncoding encoding) const
504 {
505  if (isEmpty()) {
506  result.clear();
507  return;
508  }
509 
510  string regularStrRes;
511  switch (m_type) {
512  case TagDataType::Text:
513  if (encoding == TagTextEncoding::Unspecified || encoding == dataEncoding()) {
514  result.assign(reinterpret_cast<const char16_t *>(m_ptr.get()), m_size / sizeof(char16_t));
515  } else {
516  StringData encodedData;
517  switch (encoding) {
519  // use pre-defined methods when encoding to UTF-8
520  switch (dataEncoding()) {
522  encodedData = convertLatin1ToUtf8(m_ptr.get(), m_size);
523  break;
525  encodedData = convertUtf16LEToUtf8(m_ptr.get(), m_size);
526  break;
528  encodedData = convertUtf16BEToUtf8(m_ptr.get(), m_size);
529  break;
530  default:;
531  }
532  break;
533  default: {
534  // otherwise, determine input and output parameter to use general covertString method
535  const auto inputParameter = encodingParameter(dataEncoding());
536  const auto outputParameter = encodingParameter(encoding);
537  encodedData
538  = convertString(inputParameter.first, outputParameter.first, m_ptr.get(), m_size, outputParameter.second / inputParameter.second);
539  }
540  }
541  result.assign(reinterpret_cast<const char16_t *>(encodedData.first.get()), encodedData.second / sizeof(char16_t));
542  }
543  return;
545  regularStrRes = ConversionUtilities::numberToString(toInteger());
546  break;
548  regularStrRes = toPositionInSet().toString();
549  break;
551  if (const char *const genreName = Id3Genres::stringFromIndex(toInteger())) {
552  regularStrRes.assign(genreName);
553  break;
554  }
555  throw ConversionException("No string representation for the assigned standard genre index available.");
557  regularStrRes = toTimeSpan().toString();
558  break;
559  default:
560  throw ConversionException(argsToString("Can not convert ", tagDataTypeString(m_type), " to string."));
561  }
563  auto encodedData = encoding == TagTextEncoding::Utf16LittleEndian ? convertUtf8ToUtf16LE(regularStrRes.data(), result.size())
564  : convertUtf8ToUtf16BE(regularStrRes.data(), result.size());
565  result.assign(reinterpret_cast<const char16_t *>(encodedData.first.get()), encodedData.second / sizeof(const char16_t));
566  }
567 }
568 
579 void TagValue::assignText(const char *text, std::size_t textSize, TagTextEncoding textEncoding, TagTextEncoding convertTo)
580 {
581  m_type = TagDataType::Text;
582  m_encoding = convertTo == TagTextEncoding::Unspecified ? textEncoding : convertTo;
583 
584  stripBom(text, textSize, textEncoding);
585  if (!textSize) {
586  m_size = 0;
587  m_ptr.reset();
588  return;
589  }
590 
591  if (convertTo == TagTextEncoding::Unspecified || textEncoding == convertTo) {
592  m_ptr = make_unique<char[]>(m_size = textSize);
593  copy(text, text + textSize, m_ptr.get());
594  return;
595  }
596 
597  StringData encodedData;
598  switch (textEncoding) {
600  // use pre-defined methods when encoding to UTF-8
601  switch (convertTo) {
603  encodedData = convertUtf8ToLatin1(text, textSize);
604  break;
606  encodedData = convertUtf8ToUtf16LE(text, textSize);
607  break;
609  encodedData = convertUtf8ToUtf16BE(text, textSize);
610  break;
611  default:;
612  }
613  break;
614  default: {
615  // otherwise, determine input and output parameter to use general covertString method
616  const auto inputParameter = encodingParameter(textEncoding);
617  const auto outputParameter = encodingParameter(convertTo);
618  encodedData = convertString(inputParameter.first, outputParameter.first, text, textSize, outputParameter.second / inputParameter.second);
619  }
620  }
621  // can't just move the encoded data because it needs to be deleted with free
622  m_ptr = make_unique<char[]>(m_size = encodedData.second);
623  copy(encodedData.first.get(), encodedData.first.get() + encodedData.second, m_ptr.get());
624 }
625 
630 void TagValue::assignInteger(int value)
631 {
632  m_size = sizeof(value);
633  m_ptr = make_unique<char[]>(m_size);
634  std::copy(reinterpret_cast<const char *>(&value), reinterpret_cast<const char *>(&value) + m_size, m_ptr.get());
635  m_type = TagDataType::Integer;
636  m_encoding = TagTextEncoding::Latin1;
637 }
638 
647 void TagValue::assignData(const char *data, size_t length, TagDataType type, TagTextEncoding encoding)
648 {
649  if (type == TagDataType::Text) {
650  stripBom(data, length, encoding);
651  }
652  if (length > m_size) {
653  m_ptr = make_unique<char[]>(length);
654  }
655  if (length) {
656  std::copy(data, data + length, m_ptr.get());
657  } else {
658  m_ptr.reset();
659  }
660  m_size = length;
661  m_type = type;
662  m_encoding = encoding;
663 }
664 
677 void TagValue::assignData(unique_ptr<char[]> &&data, size_t length, TagDataType type, TagTextEncoding encoding)
678 {
679  m_size = length;
680  m_type = type;
681  m_encoding = encoding;
682  m_ptr = move(data);
683 }
684 
688 void TagValue::stripBom(const char *&text, size_t &length, TagTextEncoding encoding)
689 {
690  switch (encoding) {
692  if ((length >= 3) && (ConversionUtilities::BE::toUInt24(text) == 0x00EFBBBF)) {
693  text += 3;
694  length -= 3;
695  }
696  break;
698  if ((length >= 2) && (ConversionUtilities::LE::toUInt16(text) == 0xFEFF)) {
699  text += 2;
700  length -= 2;
701  }
702  break;
704  if ((length >= 2) && (ConversionUtilities::BE::toUInt16(text) == 0xFEFF)) {
705  text += 2;
706  length -= 2;
707  }
708  break;
709  default:;
710  }
711 }
712 
717 void TagValue::ensureHostByteOrder(u16string &u16str, TagTextEncoding currentEncoding)
718 {
719  if (currentEncoding !=
720 #if defined(CONVERSION_UTILITIES_BYTE_ORDER_LITTLE_ENDIAN)
722 #elif defined(CONVERSION_UTILITIES_BYTE_ORDER_BIG_ENDIAN)
724 #else
725 #error "Host byte order not supported"
726 #endif
727  ) {
728  for (auto &c : u16str) {
729  c = swapOrder(static_cast<uint16>(c));
730  }
731  }
732 }
733 
738 {
739  static TagValue emptyTagValue;
740  return emptyTagValue;
741 }
742 
743 } // namespace TagParser
TagTextEncoding dataEncoding() const
Returns the data encoding.
Definition: tagvalue.h:538
StringType toString() const
Returns the string representation of the current PositionInSet.
static void stripBom(const char *&text, std::size_t &length, TagTextEncoding encoding)
Strips the byte order mask from the specified text.
Definition: tagvalue.cpp:688
The Tag class is used to store, read and write tag information.
Definition: tag.h:95
pair< const char *, float > encodingParameter(TagTextEncoding tagTextEncoding)
Returns the encoding parameter (name of the character set and bytes per character) for the specified ...
Definition: tagvalue.cpp:344
virtual TagTextEncoding proposedTextEncoding() const
Returns the proposed text encoding.
Definition: tag.h:146
std::u16string toWString(TagTextEncoding encoding=TagTextEncoding::Unspecified) const
Converts the value of the current TagValue object to its equivalent std::wstring representation.
Definition: tagvalue.h:378
STL namespace.
The PositionInSet class describes the position of an element in a set which consists of a certain num...
Definition: positioninset.h:21
TagDataType
Specifies the data type.
Definition: tagvalue.h:53
static const char * stringFromIndex(int index)
Returns the genre name for the specified numerical denotation as C-style string.
Definition: id3genres.h:26
int toStandardGenreIndex() const
Converts the value of the current TagValue object to its equivalent standard genre index...
Definition: tagvalue.cpp:212
static int indexFromString(const std::string &genre)
Returns the numerical denotation of the specified genre or -1 if genre is unknown.
Definition: id3genres.cpp:41
bool isEmpty() const
Returns an indication whether an value is assigned.
Definition: tagvalue.h:389
void clearMetadata()
Wipes assigned meta data.
Definition: tagvalue.cpp:161
ChronoUtilities::TimeSpan toTimeSpan() const
Converts the value of the current TagValue object to its equivalent TimeSpan representation.
Definition: tagvalue.cpp:291
static const TagValue & empty()
Returns an empty TagValue.
Definition: tagvalue.cpp:737
PositionInSet toPositionInSet() const
Converts the value of the current TagValue object to its equivalent PositionInSet representation...
Definition: tagvalue.cpp:253
bool operator==(const TagValue &other) const
Returns whether both instances are equal.
Definition: tagvalue.cpp:108
const char * tagDataTypeString(TagDataType dataType)
Returns the string representation of the specified dataType.
Definition: tagvalue.cpp:24
void convertDataEncoding(TagTextEncoding encoding)
Converts the currently assigned text value to the specified encoding.
Definition: tagvalue.cpp:369
TagDataType type() const
Returns the type of the assigned value.
Definition: tagvalue.h:353
ChronoUtilities::DateTime toDateTime() const
Converts the value of the current TagValue object to its equivalent DateTime representation.
Definition: tagvalue.cpp:319
int32 toInteger() const
Converts the value of the current TagValue object to its equivalent integer representation.
Definition: tagvalue.cpp:177
void assignInteger(int value)
Assigns the given integer value.
Definition: tagvalue.cpp:630
void assignText(const char *text, std::size_t textSize, TagTextEncoding textEncoding=TagTextEncoding::Latin1, TagTextEncoding convertTo=TagTextEncoding::Unspecified)
Assigns a copy of the given text.
Definition: tagvalue.cpp:579
TagValue & operator=(const TagValue &other)
Assigns the value of another TagValue to the current instance.
Definition: tagvalue.cpp:78
void assignData(const char *data, std::size_t length, TagDataType type=TagDataType::Binary, TagTextEncoding encoding=TagTextEncoding::Latin1)
static constexpr bool isIndexSupported(int index)
Returns an indication whether the specified numerical denotation is supported by this class...
Definition: id3genres.h:43
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 convertDataEncodingForTag(const Tag *tag)
Ensures the encoding of the currently assigned text value is supported by the specified tag...
Definition: tagvalue.cpp:411
The TagValue class wraps values of different types.
Definition: tagvalue.h:65
TagTextEncoding
Specifies the text encoding.
Definition: tagvalue.h:24
Contains all classes and functions of the TagInfo library.
Definition: aaccodebook.h:9
virtual bool canEncodingBeUsed(TagTextEncoding encoding) const
Returns an indication whether the specified encoding can be used to provide string values for the tag...
Definition: tag.h:151
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