Tag Parser  6.4.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/stringconversion.h>
8 #include <c++utilities/conversion/conversionexception.h>
9 
10 #include <algorithm>
11 #include <utility>
12 #include <cstring>
13 
14 using namespace std;
15 using namespace ConversionUtilities;
16 using namespace ChronoUtilities;
17 
18 namespace Media {
19 
31 TagValue::TagValue(const TagValue &other) :
32  m_size(other.m_size),
33  m_type(other.m_type),
34  m_desc(other.m_desc),
35  m_mimeType(other.m_mimeType),
36  m_lng(other.m_lng),
37  m_labeledAsReadonly(other.m_labeledAsReadonly),
38  m_encoding(other.m_encoding),
39  m_descEncoding(other.m_descEncoding)
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_lng = other.m_lng;
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)
82  || m_mimeType != other.m_mimeType || m_lng != other.m_lng || 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 
129 {}
130 
139 {
140  m_desc.clear();
141  m_mimeType.clear();
142  m_lng.clear();
143  m_labeledAsReadonly = false;
144  m_encoding = TagTextEncoding::Latin1;
145  m_type = TagDataType::Undefined;
146 }
147 
154 {
155  clearData();
156  clearMetadata();
157 }
158 
164 int32 TagValue::toInteger() const
165 {
166  if(!isEmpty()) {
167  switch(m_type) {
168  case TagDataType::Text:
169  switch(m_encoding) {
173  return ConversionUtilities::bufferToNumber<int32>(m_ptr.get(), m_size);
176  u16string u16str(reinterpret_cast<char16_t *>(m_ptr.get()), m_size / 2);
177  ensureHostByteOrder(u16str, m_encoding);
178  return ConversionUtilities::stringToNumber<int32>(u16str);
179  }
183  if(m_size == sizeof(int32)) {
184  return *reinterpret_cast<int32 *>(m_ptr.get());
185  } else {
186  throw ConversionException("Can not convert assigned data to integer because the data size is not appropriate.");
187  }
188  default:
189  throw ConversionException("Can not convert binary data/picture/time span/date time to integer.");
190  }
191  }
192  return 0;
193 }
194 
201 {
202  if(!isEmpty()) {
203  int index = 0;
204  switch(m_type) {
205  case TagDataType::Text: {
206  const string s(toString());
207  try {
208  index = toInteger();
209  } catch (const ConversionException &) {
211  if(m_encoding == TagTextEncoding::Latin1) {
212  // no need to convert Latin-1 to UTF-8 (makes no difference in case of genre strings)
213  encoding = TagTextEncoding::Unspecified;
214  }
215  index = Id3Genres::indexFromString(toString(encoding));
216  }
217  break;
220  if(m_size == sizeof(int32)) {
221  index = static_cast<int>(*reinterpret_cast<int32 *>(m_ptr.get()));
222  } else {
223  throw ConversionException("The assigned data is of unappropriate size.");
224  }
225  break;
226  default:
227  throw ConversionException("It is not possible to convert assigned data to a number because of its incompatible type.");
228  }
229  if(Id3Genres::isIndexSupported(index)) {
230  return index;
231  } else {
232  throw ConversionException("The assigned number is not a valid standard genre index.");
233  }
234  }
235  return 0;
236 }
237 
244 {
245  if(!isEmpty()) {
246  switch(m_type) {
247  case TagDataType::Text:
248  switch(m_encoding) {
252  return PositionInSet(string(m_ptr.get(), m_size));
255  u16string u16str(reinterpret_cast<char16_t *>(m_ptr.get()), m_size / 2);
256  ensureHostByteOrder(u16str, m_encoding);
257  return PositionInSet(u16str);
258  }
261  switch(m_size) {
262  case sizeof(int32):
263  return PositionInSet(*(reinterpret_cast<int32 *>(m_ptr.get())));
264  case 2 * sizeof(int32):
265  return PositionInSet(*(reinterpret_cast<int32 *>(m_ptr.get())), *(reinterpret_cast<int32 *>(m_ptr.get() + sizeof(int32))));
266  default:
267  throw ConversionException("The size of the assigned data is not appropriate.");
268  }
269  default:
270  throw ConversionException("Can not convert binary data/genre index/picture to \"position in set\".");
271  }
272  }
273  return PositionInSet();
274 }
275 
282 {
283  if(!isEmpty()) {
284  switch(m_type) {
285  case TagDataType::Text:
286  return TimeSpan::fromString(string(m_ptr.get(), m_size));
289  switch(m_size) {
290  case sizeof(int32):
291  return TimeSpan(*(reinterpret_cast<int32 *>(m_ptr.get())));
292  case sizeof(int64):
293  return TimeSpan(*(reinterpret_cast<int64 *>(m_ptr.get())));
294  default:
295  throw ConversionException("The size of the assigned data is not appropriate.");
296  }
297  default:
298  throw ConversionException("Can not convert binary data/genre index/position in set/picture to time span.");
299  }
300  }
301  return TimeSpan();
302 }
303 
310 {
311  if(!isEmpty()) {
312  switch(m_type) {
313  case TagDataType::Text:
314  return DateTime::fromString(string(m_ptr.get(), m_size));
317  if(m_size == sizeof(int32)) {
318  return DateTime(*(reinterpret_cast<int32 *>(m_ptr.get())));
319  } else if(m_size == sizeof(int64)) {
320  return DateTime(*(reinterpret_cast<int64 *>(m_ptr.get())));
321  } else {
322  throw ConversionException("The assigned data is of unappropriate size.");
323  }
324  default:
325  throw ConversionException("Can not convert binary data/genre index/position in set/picture to date time.");
326  }
327  }
328  return DateTime();
329 }
330 
334 pair<const char *, float> encodingParameter(TagTextEncoding tagTextEncoding)
335 {
336  switch(tagTextEncoding) {
338  return make_pair("ISO-8859-1", 1.0f);
340  return make_pair("UTF-8", 1.0f);
342  return make_pair("UTF-16LE", 2.0f);
344  return make_pair("UTF-16BE", 2.0f);
345  default:
346  return make_pair(nullptr, 0.0f);
347  }
348 }
349 
360 {
361  if(m_encoding != encoding) {
362  if(type() == TagDataType::Text) {
363  StringData encodedData;
364  switch(encoding) {
366  // use pre-defined methods when encoding to UTF-8
367  switch(dataEncoding()) {
369  encodedData = convertLatin1ToUtf8(m_ptr.get(), m_size);
370  break;
372  encodedData = convertUtf16LEToUtf8(m_ptr.get(), m_size);
373  break;
375  encodedData = convertUtf16BEToUtf8(m_ptr.get(), m_size);
376  break;
377  default:
378  ;
379  }
380  break;
381  default: {
382  // otherwise, determine input and output parameter to use general covertString method
383  const auto inputParameter = encodingParameter(dataEncoding());
384  const auto outputParameter = encodingParameter(encoding);
385  encodedData = convertString(inputParameter.first, outputParameter.first, m_ptr.get(), m_size, outputParameter.second / inputParameter.second);
386  }
387  }
388  // can't just move the encoded data because it needs to be deleted with free
389  m_ptr = make_unique<char []>(m_size = encodedData.second);
390  copy(encodedData.first.get(), encodedData.first.get() + encodedData.second, m_ptr.get());
391  }
392  m_encoding = encoding;
393  }
394 }
395 
401 {
404  }
405 }
406 
416 void TagValue::toString(string &result, TagTextEncoding encoding) const
417 {
418  if(!isEmpty()) {
419  switch(m_type) {
420  case TagDataType::Text:
422  result.assign(m_ptr.get(), m_size);
423  } else {
424  StringData encodedData;
425  switch(encoding) {
427  // use pre-defined methods when encoding to UTF-8
428  switch(dataEncoding()) {
430  encodedData = convertLatin1ToUtf8(m_ptr.get(), m_size);
431  break;
433  encodedData = convertUtf16LEToUtf8(m_ptr.get(), m_size);
434  break;
436  encodedData = convertUtf16BEToUtf8(m_ptr.get(), m_size);
437  break;
438  default:
439  ;
440  }
441  break;
442  default: {
443  // otherwise, determine input and output parameter to use general covertString method
444  const auto inputParameter = encodingParameter(dataEncoding());
445  const auto outputParameter = encodingParameter(encoding);
446  encodedData = convertString(inputParameter.first, outputParameter.first, m_ptr.get(), m_size, outputParameter.second / inputParameter.second);
447  }
448  }
449  result.assign(encodedData.first.get(), encodedData.second);
450  }
451  return;
453  result = ConversionUtilities::numberToString(toInteger());
454  break;
456  result = toPositionInSet().toString();
457  break;
459  if(const char *genreName = Id3Genres::stringFromIndex(toInteger())) {
460  result.assign(genreName);
461  break;
462  } else {
463  throw ConversionException("No string representation for the assigned standard genre index available.");
464  }
466  result = toTimeSpan().toString();
467  break;
469  result = toDateTime().toString();
470  break;
471  default:
472  throw ConversionException("Can not convert binary data/picture to string.");
473  }
475  auto encodedData = encoding == TagTextEncoding::Utf16LittleEndian
476  ? convertUtf8ToUtf16LE(result.data(), result.size())
477  : convertUtf8ToUtf16BE(result.data(), result.size());
478  result.assign(encodedData.first.get(), encodedData.second);
479  }
480  } else {
481  result.clear();
482  }
483 }
484 
492 void TagValue::toWString(std::u16string &result, TagTextEncoding encoding) const
493 {
494  if(!isEmpty()) {
495  string regularStrRes;
496  switch(m_type) {
497  case TagDataType::Text:
498  if(encoding == TagTextEncoding::Unspecified || encoding == dataEncoding()) {
499  result.assign(reinterpret_cast<const char16_t *>(m_ptr.get()), m_size / sizeof(char16_t));
500  } else {
501  StringData encodedData;
502  switch(encoding) {
504  // use pre-defined methods when encoding to UTF-8
505  switch(dataEncoding()) {
507  encodedData = convertLatin1ToUtf8(m_ptr.get(), m_size);
508  break;
510  encodedData = convertUtf16LEToUtf8(m_ptr.get(), m_size);
511  break;
513  encodedData = convertUtf16BEToUtf8(m_ptr.get(), m_size);
514  break;
515  default:
516  ;
517  }
518  break;
519  default: {
520  // otherwise, determine input and output parameter to use general covertString method
521  const auto inputParameter = encodingParameter(dataEncoding());
522  const auto outputParameter = encodingParameter(encoding);
523  encodedData = convertString(inputParameter.first, outputParameter.first, m_ptr.get(), m_size, outputParameter.second / inputParameter.second);
524  }
525  }
526  result.assign(reinterpret_cast<const char16_t *>(encodedData.first.get()), encodedData.second / sizeof(char16_t));
527  }
528  return;
530  regularStrRes = ConversionUtilities::numberToString(toInteger());
531  break;
533  regularStrRes = toPositionInSet().toString();
534  break;
536  if(const char *genreName = Id3Genres::stringFromIndex(toInteger())) {
537  regularStrRes.assign(genreName);
538  break;
539  } else {
540  throw ConversionException("No string representation for the assigned standard genre index available.");
541  }
543  regularStrRes = toTimeSpan().toString();
544  break;
545  default:
546  throw ConversionException("Can not convert binary data/picture to string.");
547  }
549  auto encodedData = encoding == TagTextEncoding::Utf16LittleEndian
550  ? convertUtf8ToUtf16LE(regularStrRes.data(), result.size())
551  : convertUtf8ToUtf16BE(regularStrRes.data(), result.size());
552  result.assign(reinterpret_cast<const char16_t *>(encodedData.first.get()), encodedData.second / sizeof(const char16_t));
553  }
554  } else {
555  result.clear();
556  }
557 }
558 
569 void TagValue::assignText(const char *text, std::size_t textSize, TagTextEncoding textEncoding, TagTextEncoding convertTo)
570 {
571  m_type = TagDataType::Text;
572  m_encoding = convertTo == TagTextEncoding::Unspecified ? textEncoding : convertTo;
573 
574  stripBom(text, textSize, textEncoding);
575  if(!textSize) {
576  m_size = 0;
577  m_ptr.reset();
578  return;
579  }
580 
581  if(convertTo == TagTextEncoding::Unspecified || textEncoding == convertTo) {
582  m_ptr = make_unique<char []>(m_size = textSize);
583  copy(text, text + textSize, m_ptr.get());
584  } else {
585  StringData encodedData;
586  switch(textEncoding) {
588  // use pre-defined methods when encoding to UTF-8
589  switch(convertTo) {
591  encodedData = convertUtf8ToLatin1(text, textSize);
592  break;
594  encodedData = convertUtf8ToUtf16LE(text, textSize);
595  break;
597  encodedData = convertUtf8ToUtf16BE(text, textSize);
598  break;
599  default:
600  ;
601  }
602  break;
603  default: {
604  // otherwise, determine input and output parameter to use general covertString method
605  const auto inputParameter = encodingParameter(textEncoding);
606  const auto outputParameter = encodingParameter(convertTo);
607  encodedData = convertString(inputParameter.first, outputParameter.first, text, textSize, outputParameter.second / inputParameter.second);
608  }
609  }
610  // can't just move the encoded data because it needs to be deleted with free
611  m_ptr = make_unique<char []>(m_size = encodedData.second);
612  copy(encodedData.first.get(), encodedData.first.get() + encodedData.second, m_ptr.get());
613  }
614 }
615 
620 void TagValue::assignInteger(int value)
621 {
622  m_size = sizeof(value);
623  m_ptr = make_unique<char []>(m_size);
624  std::copy(reinterpret_cast<const char *>(&value), reinterpret_cast<const char *>(&value) + m_size, m_ptr.get());
625  m_type = TagDataType::Integer;
626  m_encoding = TagTextEncoding::Latin1;
627 }
628 
635 {
636  assignInteger(index);
638 }
639 
648 void TagValue::assignData(const char *data, size_t length, TagDataType type, TagTextEncoding encoding)
649 {
650  if(type == TagDataType::Text) {
651  stripBom(data, length, encoding);
652  }
653  if(length > m_size) {
654  m_ptr = make_unique<char[]>(length);
655  }
656  if(length) {
657  std::copy(data, data + length, m_ptr.get());
658  } else {
659  m_ptr.reset();
660  }
661  m_size = length;
662  m_type = type;
663  m_encoding = encoding;
664 }
665 
678 void TagValue::assignData(unique_ptr<char[]> &&data, size_t length, TagDataType type, TagTextEncoding encoding)
679 {
680  m_size = length;
681  m_type = type;
682  m_encoding = encoding;
683  m_ptr = move(data);
684 }
685 
689 void TagValue::stripBom(const char *&text, size_t &length, TagTextEncoding encoding)
690 {
691  switch(encoding) {
693  if((length >= 3) && (ConversionUtilities::BE::toUInt24(text) == 0x00EFBBBF)) {
694  text += 3;
695  length -= 3;
696  }
697  break;
699  if((length >= 2) && (ConversionUtilities::LE::toUInt16(text) == 0xFEFF)) {
700  text += 2;
701  length -= 2;
702  }
703  break;
705  if((length >= 2) && (ConversionUtilities::BE::toUInt16(text) == 0xFEFF)) {
706  text += 2;
707  length -= 2;
708  }
709  break;
710  default:
711  ;
712  }
713 }
714 
719 void TagValue::ensureHostByteOrder(u16string &u16str, TagTextEncoding currentEncoding)
720 {
721  if(currentEncoding !=
722  #if defined(CONVERSION_UTILITIES_BYTE_ORDER_LITTLE_ENDIAN)
724  #elif defined(CONVERSION_UTILITIES_BYTE_ORDER_BIG_ENDIAN)
726  #else
727  # error "Host byte order not supported"
728  #endif
729  ) {
730  for(auto &c : u16str) {
731  c = swapOrder(static_cast<uint16>(c));
732  }
733  }
734 }
735 
740 {
741  static TagValue emptyTagValue;
742  return emptyTagValue;
743 }
744 
745 }
The TagValue class wraps values of different types.
Definition: tagvalue.h:64
bool isEmpty() const
Returns an indication whether an value is assigned.
Definition: tagvalue.h:344
int32 toInteger() const
Converts the value of the current TagValue object to its equivalent integer representation.
Definition: tagvalue.cpp:164
TagTextEncoding dataEncoding() const
Returns the data encoding.
Definition: tagvalue.h:479
int toStandardGenreIndex() const
Converts the value of the current TagValue object to its equivalent standard genre index...
Definition: tagvalue.cpp:200
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:569
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:333
static constexpr bool isIndexSupported(int index)
Returns an indication whether the specified numerical denotation is supported by this class...
Definition: id3genres.h:45
bool operator==(const TagValue &other) const
Returns whether both instances are equal.
Definition: tagvalue.cpp:79
static int indexFromString(const std::string &genre)
Returns the numerical denotation of the specified genre or -1 if genre is unknown.
Definition: id3genres.cpp:52
virtual TagTextEncoding proposedTextEncoding() const
Returns the proposed text encoding.
Definition: tag.h:183
STL namespace.
void assignStandardGenreIndex(int index)
Assigns the given standard genre index to be assigned.
Definition: tagvalue.cpp:634
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:202
void clearMetadata()
Wipes assigned meta data.
Definition: tagvalue.cpp:138
void clearDataAndMetadata()
Wipes assigned data including meta data.
Definition: tagvalue.cpp:153
TagTextEncoding
Specifies the text encoding.
Definition: tagvalue.h:22
void convertDataEncoding(TagTextEncoding encoding)
Converts the currently assigned text value to the specified encoding.
Definition: tagvalue.cpp:359
The PositionInSet class describes the position of an element in a set which consists of a certain num...
Definition: positioninset.h:20
TagValue & operator=(const TagValue &other)
Assigns the value of another TagValue to the current instance.
Definition: tagvalue.cpp:50
TagDataType type() const
Returns the type of the assigned value.
Definition: tagvalue.h:308
void clearData()
Clears the assigned data.
Definition: tagvalue.h:355
ChronoUtilities::DateTime toDateTime() const
Converts the value of the current TagValue object to its equivalent DateTime representation.
Definition: tagvalue.cpp:309
The Tag class is used to store, read and write tag information.
Definition: tag.h:98
static const char * stringFromIndex(int index)
Returns the genre name for the specified numerical denotation as C-style string.
Definition: id3genres.h:28
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:648
void assignInteger(int value)
Assigns the given integer value.
Definition: tagvalue.cpp:620
PositionInSet toPositionInSet() const
Converts the value of the current TagValue object to its equivalent PositionInSet representation...
Definition: tagvalue.cpp:243
TagDataType
Specifies the data type.
Definition: tagvalue.h:51
void convertDataEncodingForTag(const Tag *tag)
Ensures the encoding of the currently assigned text value is supported by the specified tag...
Definition: tagvalue.cpp:400
ChronoUtilities::TimeSpan toTimeSpan() const
Converts the value of the current TagValue object to its equivalent TimeSpan representation.
Definition: tagvalue.cpp:281
static const TagValue & empty()
Returns an empty TagValue.
Definition: tagvalue.cpp:739
Contains all classes and functions of the TagInfo library.
Definition: exceptions.h:9
StringType toString() const
Returns the string representation of the current PositionInSet.
~TagValue()
Destroys the TagValue.
Definition: tagvalue.cpp:128
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:320
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:334