Tag Parser  7.0.0
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/stringconversion.h>
9 
10 #include <algorithm>
11 #include <cstring>
12 #include <utility>
13 
14 using namespace std;
15 using namespace ConversionUtilities;
16 using namespace ChronoUtilities;
17 
18 namespace TagParser {
19 
31 TagValue::TagValue(const TagValue &other)
32  : m_size(other.m_size)
33  , m_desc(other.m_desc)
34  , m_mimeType(other.m_mimeType)
35  , m_language(other.m_language)
36  , m_type(other.m_type)
37  , m_encoding(other.m_encoding)
38  , m_descEncoding(other.m_descEncoding)
39  , m_labeledAsReadonly(other.m_labeledAsReadonly)
40 {
41  if (!other.isEmpty()) {
42  m_ptr = make_unique<char[]>(m_size);
43  std::copy(other.m_ptr.get(), other.m_ptr.get() + other.m_size, m_ptr.get());
44  }
45 }
46 
51 {
52  if (this != &other) {
53  m_size = other.m_size;
54  m_type = other.m_type;
55  m_desc = other.m_desc;
56  m_mimeType = other.m_mimeType;
57  m_language = other.m_language;
58  m_labeledAsReadonly = other.m_labeledAsReadonly;
59  m_encoding = other.m_encoding;
60  m_descEncoding = other.m_descEncoding;
61  if (other.isEmpty()) {
62  m_ptr.reset();
63  } else {
64  m_ptr = make_unique<char[]>(m_size);
65  std::copy(other.m_ptr.get(), other.m_ptr.get() + other.m_size, m_ptr.get());
66  }
67  }
68  return *this;
69 }
70 
79 bool TagValue::operator==(const TagValue &other) const
80 {
81  if (m_desc != other.m_desc || (!m_desc.empty() && m_descEncoding != other.m_descEncoding) || m_mimeType != other.m_mimeType
82  || m_language != other.m_language || m_labeledAsReadonly != other.m_labeledAsReadonly) {
83  return false;
84  }
85  if (m_type == other.m_type) {
86  switch (m_type) {
87  case TagDataType::Text:
88  if (m_size != other.m_size && m_encoding != other.m_encoding) {
89  // don't consider differently encoded text values equal
90  return false;
91  }
92  return strncmp(m_ptr.get(), other.m_ptr.get(), m_size) == 0;
94  return toPositionInSet() == other.toPositionInSet();
96  return toInteger() == other.toInteger();
98  return toStandardGenreIndex() == other.toStandardGenreIndex();
100  return toTimeSpan() == other.toTimeSpan();
102  return toDateTime() == other.toDateTime();
104  case TagDataType::Binary:
106  if (m_size != other.m_size) {
107  return false;
108  }
109  return strncmp(m_ptr.get(), other.m_ptr.get(), m_size) == 0;
110  default:
111  return false;
112  }
113  } else {
114  // different types
115  try {
116  // try to convert both values to string
117  // if the string representations are equal, both values can also be considered equal
118  return toString() == other.toString(m_encoding);
119  } catch (const ConversionException &) {
120  return false;
121  }
122  }
123 }
124 
133 {
134  m_desc.clear();
135  m_mimeType.clear();
136  m_language.clear();
137  m_labeledAsReadonly = false;
138  m_encoding = TagTextEncoding::Latin1;
139  m_descEncoding = TagTextEncoding::Latin1;
140  m_type = TagDataType::Undefined;
141 }
142 
148 int32 TagValue::toInteger() const
149 {
150  if (isEmpty()) {
151  return 0;
152  }
153  switch (m_type) {
154  case TagDataType::Text:
155  switch (m_encoding) {
159  return ConversionUtilities::bufferToNumber<int32>(m_ptr.get(), m_size);
162  u16string u16str(reinterpret_cast<char16_t *>(m_ptr.get()), m_size / 2);
163  ensureHostByteOrder(u16str, m_encoding);
164  return ConversionUtilities::stringToNumber<int32>(u16str);
165  }
169  if (m_size == sizeof(int32)) {
170  return *reinterpret_cast<int32 *>(m_ptr.get());
171  }
172  throw ConversionException("Can not convert assigned data to integer because the data size is not appropriate.");
173  default:
174  throw ConversionException("Can not convert binary data/picture/time span/date time to integer.");
175  }
176 }
177 
184 {
185  if (isEmpty()) {
186  return 0;
187  }
188  int index = 0;
189  switch (m_type) {
190  case TagDataType::Text: {
191  const string s(toString());
192  try {
193  index = toInteger();
194  } catch (const ConversionException &) {
196  if (m_encoding == TagTextEncoding::Latin1) {
197  // no need to convert Latin-1 to UTF-8 (makes no difference in case of genre strings)
198  encoding = TagTextEncoding::Unspecified;
199  }
200  index = Id3Genres::indexFromString(toString(encoding));
201  }
202  break;
203  }
206  if (m_size != sizeof(int32)) {
207  throw ConversionException("The assigned data is of unappropriate size.");
208  }
209  index = static_cast<int>(*reinterpret_cast<int32 *>(m_ptr.get()));
210  break;
211  default:
212  throw ConversionException("It is not possible to convert assigned data to a number because of its incompatible type.");
213  }
214  if (Id3Genres::isIndexSupported(index)) {
215  return index;
216  }
217  throw ConversionException("The assigned number is not a valid standard genre index.");
218 }
219 
226 {
227  if (isEmpty()) {
228  return PositionInSet();
229  }
230  switch (m_type) {
231  case TagDataType::Text:
232  switch (m_encoding) {
236  return PositionInSet(string(m_ptr.get(), m_size));
239  u16string u16str(reinterpret_cast<char16_t *>(m_ptr.get()), m_size / 2);
240  ensureHostByteOrder(u16str, m_encoding);
241  return PositionInSet(u16str);
242  }
245  switch (m_size) {
246  case sizeof(int32):
247  return PositionInSet(*(reinterpret_cast<int32 *>(m_ptr.get())));
248  case 2 * sizeof(int32):
249  return PositionInSet(*(reinterpret_cast<int32 *>(m_ptr.get())), *(reinterpret_cast<int32 *>(m_ptr.get() + sizeof(int32))));
250  default:
251  throw ConversionException("The size of the assigned data is not appropriate.");
252  }
253  default:
254  throw ConversionException("Can not convert binary data/genre index/picture to \"position in set\".");
255  }
256 }
257 
264 {
265  if (isEmpty()) {
266  return TimeSpan();
267  }
268  switch (m_type) {
269  case TagDataType::Text:
270  return TimeSpan::fromString(string(m_ptr.get(), m_size));
273  switch (m_size) {
274  case sizeof(int32):
275  return TimeSpan(*(reinterpret_cast<int32 *>(m_ptr.get())));
276  case sizeof(int64):
277  return TimeSpan(*(reinterpret_cast<int64 *>(m_ptr.get())));
278  default:
279  throw ConversionException("The size of the assigned data is not appropriate.");
280  }
281  default:
282  throw ConversionException("Can not convert binary data/genre index/position in set/picture to time span.");
283  }
284 }
285 
291 DateTime TagValue::toDateTime() const
292 {
293  if (isEmpty()) {
294  return DateTime();
295  }
296  switch (m_type) {
297  case TagDataType::Text:
298  return DateTime::fromString(string(m_ptr.get(), m_size));
301  if (m_size == sizeof(int32)) {
302  return DateTime(*(reinterpret_cast<uint32 *>(m_ptr.get())));
303  } else if (m_size == sizeof(int64)) {
304  return DateTime(*(reinterpret_cast<uint64 *>(m_ptr.get())));
305  } else {
306  throw ConversionException("The assigned data is of unappropriate size.");
307  }
308  default:
309  throw ConversionException("Can not convert binary data/genre index/position in set/picture to date time.");
310  }
311 }
312 
316 pair<const char *, float> encodingParameter(TagTextEncoding tagTextEncoding)
317 {
318  switch (tagTextEncoding) {
320  return make_pair("ISO-8859-1", 1.0f);
322  return make_pair("UTF-8", 1.0f);
324  return make_pair("UTF-16LE", 2.0f);
326  return make_pair("UTF-16BE", 2.0f);
327  default:
328  return make_pair(nullptr, 0.0f);
329  }
330 }
331 
342 {
343  if (m_encoding == encoding) {
344  return;
345  }
346  if (type() == TagDataType::Text) {
347  StringData encodedData;
348  switch (encoding) {
350  // use pre-defined methods when encoding to UTF-8
351  switch (dataEncoding()) {
353  encodedData = convertLatin1ToUtf8(m_ptr.get(), m_size);
354  break;
356  encodedData = convertUtf16LEToUtf8(m_ptr.get(), m_size);
357  break;
359  encodedData = convertUtf16BEToUtf8(m_ptr.get(), m_size);
360  break;
361  default:;
362  }
363  break;
364  default: {
365  // otherwise, determine input and output parameter to use general covertString method
366  const auto inputParameter = encodingParameter(dataEncoding());
367  const auto outputParameter = encodingParameter(encoding);
368  encodedData
369  = convertString(inputParameter.first, outputParameter.first, m_ptr.get(), m_size, outputParameter.second / inputParameter.second);
370  }
371  }
372  // can't just move the encoded data because it needs to be deleted with free
373  m_ptr = make_unique<char[]>(m_size = encodedData.second);
374  copy(encodedData.first.get(), encodedData.first.get() + encodedData.second, m_ptr.get());
375  }
376  m_encoding = encoding;
377 }
378 
384 {
385  if (type() == TagDataType::Text && !tag->canEncodingBeUsed(dataEncoding())) {
387  }
388 }
389 
399 void TagValue::toString(string &result, TagTextEncoding encoding) const
400 {
401  if (isEmpty()) {
402  result.clear();
403  return;
404  }
405 
406  switch (m_type) {
407  case TagDataType::Text:
408  if (encoding == TagTextEncoding::Unspecified || dataEncoding() == TagTextEncoding::Unspecified || encoding == dataEncoding()) {
409  result.assign(m_ptr.get(), m_size);
410  } else {
411  StringData encodedData;
412  switch (encoding) {
414  // use pre-defined methods when encoding to UTF-8
415  switch (dataEncoding()) {
417  encodedData = convertLatin1ToUtf8(m_ptr.get(), m_size);
418  break;
420  encodedData = convertUtf16LEToUtf8(m_ptr.get(), m_size);
421  break;
423  encodedData = convertUtf16BEToUtf8(m_ptr.get(), m_size);
424  break;
425  default:;
426  }
427  break;
428  default: {
429  // otherwise, determine input and output parameter to use general covertString method
430  const auto inputParameter = encodingParameter(dataEncoding());
431  const auto outputParameter = encodingParameter(encoding);
432  encodedData
433  = convertString(inputParameter.first, outputParameter.first, m_ptr.get(), m_size, outputParameter.second / inputParameter.second);
434  }
435  }
436  result.assign(encodedData.first.get(), encodedData.second);
437  }
438  return;
440  result = ConversionUtilities::numberToString(toInteger());
441  break;
443  result = toPositionInSet().toString();
444  break;
446  if (const char *genreName = Id3Genres::stringFromIndex(toInteger())) {
447  result.assign(genreName);
448  break;
449  } else {
450  throw ConversionException("No string representation for the assigned standard genre index available.");
451  }
453  result = toTimeSpan().toString();
454  break;
456  result = toDateTime().toString();
457  break;
458  default:
459  throw ConversionException("Can not convert binary data/picture to string.");
460  }
462  auto encodedData = encoding == TagTextEncoding::Utf16LittleEndian ? convertUtf8ToUtf16LE(result.data(), result.size())
463  : convertUtf8ToUtf16BE(result.data(), result.size());
464  result.assign(encodedData.first.get(), encodedData.second);
465  }
466 }
467 
475 void TagValue::toWString(std::u16string &result, TagTextEncoding encoding) const
476 {
477  if (isEmpty()) {
478  result.clear();
479  return;
480  }
481 
482  string regularStrRes;
483  switch (m_type) {
484  case TagDataType::Text:
485  if (encoding == TagTextEncoding::Unspecified || encoding == dataEncoding()) {
486  result.assign(reinterpret_cast<const char16_t *>(m_ptr.get()), m_size / sizeof(char16_t));
487  } else {
488  StringData encodedData;
489  switch (encoding) {
491  // use pre-defined methods when encoding to UTF-8
492  switch (dataEncoding()) {
494  encodedData = convertLatin1ToUtf8(m_ptr.get(), m_size);
495  break;
497  encodedData = convertUtf16LEToUtf8(m_ptr.get(), m_size);
498  break;
500  encodedData = convertUtf16BEToUtf8(m_ptr.get(), m_size);
501  break;
502  default:;
503  }
504  break;
505  default: {
506  // otherwise, determine input and output parameter to use general covertString method
507  const auto inputParameter = encodingParameter(dataEncoding());
508  const auto outputParameter = encodingParameter(encoding);
509  encodedData
510  = convertString(inputParameter.first, outputParameter.first, m_ptr.get(), m_size, outputParameter.second / inputParameter.second);
511  }
512  }
513  result.assign(reinterpret_cast<const char16_t *>(encodedData.first.get()), encodedData.second / sizeof(char16_t));
514  }
515  return;
517  regularStrRes = ConversionUtilities::numberToString(toInteger());
518  break;
520  regularStrRes = toPositionInSet().toString();
521  break;
523  if (const char *genreName = Id3Genres::stringFromIndex(toInteger())) {
524  regularStrRes.assign(genreName);
525  break;
526  } else {
527  throw ConversionException("No string representation for the assigned standard genre index available.");
528  }
530  regularStrRes = toTimeSpan().toString();
531  break;
532  default:
533  throw ConversionException("Can not convert binary data/picture to string.");
534  }
536  auto encodedData = encoding == TagTextEncoding::Utf16LittleEndian ? convertUtf8ToUtf16LE(regularStrRes.data(), result.size())
537  : convertUtf8ToUtf16BE(regularStrRes.data(), result.size());
538  result.assign(reinterpret_cast<const char16_t *>(encodedData.first.get()), encodedData.second / sizeof(const char16_t));
539  }
540 }
541 
552 void TagValue::assignText(const char *text, std::size_t textSize, TagTextEncoding textEncoding, TagTextEncoding convertTo)
553 {
554  m_type = TagDataType::Text;
555  m_encoding = convertTo == TagTextEncoding::Unspecified ? textEncoding : convertTo;
556 
557  stripBom(text, textSize, textEncoding);
558  if (!textSize) {
559  m_size = 0;
560  m_ptr.reset();
561  return;
562  }
563 
564  if (convertTo == TagTextEncoding::Unspecified || textEncoding == convertTo) {
565  m_ptr = make_unique<char[]>(m_size = textSize);
566  copy(text, text + textSize, m_ptr.get());
567  return;
568  }
569 
570  StringData encodedData;
571  switch (textEncoding) {
573  // use pre-defined methods when encoding to UTF-8
574  switch (convertTo) {
576  encodedData = convertUtf8ToLatin1(text, textSize);
577  break;
579  encodedData = convertUtf8ToUtf16LE(text, textSize);
580  break;
582  encodedData = convertUtf8ToUtf16BE(text, textSize);
583  break;
584  default:;
585  }
586  break;
587  default: {
588  // otherwise, determine input and output parameter to use general covertString method
589  const auto inputParameter = encodingParameter(textEncoding);
590  const auto outputParameter = encodingParameter(convertTo);
591  encodedData = convertString(inputParameter.first, outputParameter.first, text, textSize, outputParameter.second / inputParameter.second);
592  }
593  }
594  // can't just move the encoded data because it needs to be deleted with free
595  m_ptr = make_unique<char[]>(m_size = encodedData.second);
596  copy(encodedData.first.get(), encodedData.first.get() + encodedData.second, m_ptr.get());
597 }
598 
603 void TagValue::assignInteger(int value)
604 {
605  m_size = sizeof(value);
606  m_ptr = make_unique<char[]>(m_size);
607  std::copy(reinterpret_cast<const char *>(&value), reinterpret_cast<const char *>(&value) + m_size, m_ptr.get());
608  m_type = TagDataType::Integer;
609  m_encoding = TagTextEncoding::Latin1;
610 }
611 
620 void TagValue::assignData(const char *data, size_t length, TagDataType type, TagTextEncoding encoding)
621 {
622  if (type == TagDataType::Text) {
623  stripBom(data, length, encoding);
624  }
625  if (length > m_size) {
626  m_ptr = make_unique<char[]>(length);
627  }
628  if (length) {
629  std::copy(data, data + length, m_ptr.get());
630  } else {
631  m_ptr.reset();
632  }
633  m_size = length;
634  m_type = type;
635  m_encoding = encoding;
636 }
637 
650 void TagValue::assignData(unique_ptr<char[]> &&data, size_t length, TagDataType type, TagTextEncoding encoding)
651 {
652  m_size = length;
653  m_type = type;
654  m_encoding = encoding;
655  m_ptr = move(data);
656 }
657 
661 void TagValue::stripBom(const char *&text, size_t &length, TagTextEncoding encoding)
662 {
663  switch (encoding) {
665  if ((length >= 3) && (ConversionUtilities::BE::toUInt24(text) == 0x00EFBBBF)) {
666  text += 3;
667  length -= 3;
668  }
669  break;
671  if ((length >= 2) && (ConversionUtilities::LE::toUInt16(text) == 0xFEFF)) {
672  text += 2;
673  length -= 2;
674  }
675  break;
677  if ((length >= 2) && (ConversionUtilities::BE::toUInt16(text) == 0xFEFF)) {
678  text += 2;
679  length -= 2;
680  }
681  break;
682  default:;
683  }
684 }
685 
690 void TagValue::ensureHostByteOrder(u16string &u16str, TagTextEncoding currentEncoding)
691 {
692  if (currentEncoding !=
693 #if defined(CONVERSION_UTILITIES_BYTE_ORDER_LITTLE_ENDIAN)
695 #elif defined(CONVERSION_UTILITIES_BYTE_ORDER_BIG_ENDIAN)
697 #else
698 #error "Host byte order not supported"
699 #endif
700  ) {
701  for (auto &c : u16str) {
702  c = swapOrder(static_cast<uint16>(c));
703  }
704  }
705 }
706 
711 {
712  static TagValue emptyTagValue;
713  return emptyTagValue;
714 }
715 
716 } // namespace TagParser
TagTextEncoding dataEncoding() const
Returns the data encoding.
Definition: tagvalue.h:518
StringType toString() const
Returns the string representation of the current PositionInSet.
virtual TagTextEncoding proposedTextEncoding() const
Returns the proposed text encoding.
Definition: tag.h:170
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:316
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:356
STL namespace.
TagDataType
Specifies the data type.
Definition: tagvalue.h:51
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:183
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
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:189
bool isEmpty() const
Returns an indication whether an value is assigned.
Definition: tagvalue.h:367
void clearMetadata()
Wipes assigned meta data.
Definition: tagvalue.cpp:132
ChronoUtilities::TimeSpan toTimeSpan() const
Converts the value of the current TagValue object to its equivalent TimeSpan representation.
Definition: tagvalue.cpp:263
static const TagValue & empty()
Returns an empty TagValue.
Definition: tagvalue.cpp:710
PositionInSet toPositionInSet() const
Converts the value of the current TagValue object to its equivalent PositionInSet representation...
Definition: tagvalue.cpp:225
bool operator==(const TagValue &other) const
Returns whether both instances are equal.
Definition: tagvalue.cpp:79
void convertDataEncoding(TagTextEncoding encoding)
Converts the currently assigned text value to the specified encoding.
Definition: tagvalue.cpp:341
TagDataType type() const
Returns the type of the assigned value.
Definition: tagvalue.h:331
ChronoUtilities::DateTime toDateTime() const
Converts the value of the current TagValue object to its equivalent DateTime representation.
Definition: tagvalue.cpp:291
int32 toInteger() const
Converts the value of the current TagValue object to its equivalent integer representation.
Definition: tagvalue.cpp:148
void assignInteger(int value)
Assigns the given integer value.
Definition: tagvalue.cpp:603
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:552
TagValue & operator=(const TagValue &other)
Assigns the value of another TagValue to the current instance.
Definition: tagvalue.cpp:50
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:343
static void stripBom(const char *&text, size_t &length, TagTextEncoding encoding)
Strips the byte order mask from the specified text.
Definition: tagvalue.cpp:661
void convertDataEncodingForTag(const Tag *tag)
Ensures the encoding of the currently assigned text value is supported by the specified tag...
Definition: tagvalue.cpp:383
void assignData(const char *data, size_t length, TagDataType type=TagDataType::Binary, TagTextEncoding encoding=TagTextEncoding::Latin1)
Assigns a copy of the given data.
Definition: tagvalue.cpp:620
TagTextEncoding
Specifies the text encoding.
Definition: tagvalue.h:22
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:690