Tag Parser  6.2.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/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 
77 bool TagValue::operator==(const TagValue &other) const
78 {
79  if(m_desc != other.m_desc || (!m_desc.empty() && m_descEncoding != other.m_descEncoding)
80  || m_mimeType != other.m_mimeType || m_lng != other.m_lng || m_labeledAsReadonly != other.m_labeledAsReadonly) {
81  return false;
82  }
83  if(m_type == other.m_type) {
84  switch(m_type) {
85  case TagDataType::Text:
86  if(m_size != other.m_size && m_encoding != other.m_encoding) {
87  return false;
88  }
89  return strncmp(m_ptr.get(), other.m_ptr.get(), m_size) == 0;
91  return toPositionInSet() == other.toPositionInSet();
93  return toInteger() == other.toInteger();
95  return toStandardGenreIndex() == other.toStandardGenreIndex();
97  return toTimeSpan() == other.toTimeSpan();
99  return toDateTime() == other.toDateTime();
101  case TagDataType::Binary:
103  if(m_size != other.m_size) {
104  return false;
105  }
106  return strncmp(m_ptr.get(), other.m_ptr.get(), m_size) == 0;
107  default:
108  return false;
109  }
110  } else {
111  // different types
112  try {
113  // try to convert both values to string
114  // if the string representations are equal, both values can also be considered equal
115  return toString() == other.toString();
116  } catch(const ConversionException &) {
117  return false;
118  }
119  }
120 }
121 
126 {}
127 
136 {
137  m_desc.clear();
138  m_mimeType.clear();
139  m_lng.clear();
140  m_labeledAsReadonly = false;
141  m_encoding = TagTextEncoding::Latin1;
142  m_type = TagDataType::Undefined;
143 }
144 
151 {
152  clearData();
153  clearMetadata();
154 }
155 
161 int32 TagValue::toInteger() const
162 {
163  if(!isEmpty()) {
164  switch(m_type) {
165  case TagDataType::Text:
166  return ConversionUtilities::stringToNumber<int32>(string(m_ptr.get(), m_size));
170  if(m_size == sizeof(int32)) {
171  auto res = *reinterpret_cast<int32 *>(m_ptr.get());
172  return res;
173  } else {
174  throw ConversionException("Can not convert assigned data to integer because the data size is not appropriate.");
175  }
176  break;
177  default:
178  throw ConversionException("Can not convert binary data/picture/time span/date time to integer.");
179  }
180  }
181  return 0;
182 }
183 
190 {
191  if(!isEmpty()) {
192  int index = 0;
193  switch(m_type) {
194  case TagDataType::Text: {
195  string s(m_ptr.get(), m_size);
196  try {
197  index = ConversionUtilities::stringToNumber<int32>(s);
198  } catch (ConversionException &) {
199  index = Id3Genres::indexFromString(s);
200  }
201  break;
204  if(m_size == sizeof(int)) {
205  index = *reinterpret_cast<int *>(m_ptr.get());
206  } else {
207  throw ConversionException("The assigned data is of unappropriate size.");
208  }
209  break;
210  default:
211  throw ConversionException("It is not possible to convert assigned data to a number because of its incompatible type.");
212  }
213  if(Id3Genres::isIndexSupported(index)) {
214  return index;
215  } else {
216  throw ConversionException("The assigned number is not a valid standard genre index.");
217  }
218  }
219  return 0;
220 }
221 
228 {
229  if(!isEmpty()) {
230  switch(m_type) {
231  case TagDataType::Text:
232  switch(m_encoding) {
236  return PositionInSet(string(m_ptr.get(), m_size));
239  // FIXME: Ensure endianness is correctly
240  return PositionInSet(u16string(reinterpret_cast<char16_t *>(m_ptr.get()), m_size / 2));
241  }
244  switch(m_size) {
245  case sizeof(int32):
246  return PositionInSet(*(reinterpret_cast<int32 *>(m_ptr.get())));
247  case 2 * sizeof(int32):
248  return PositionInSet(*(reinterpret_cast<int32 *>(m_ptr.get())), *(reinterpret_cast<int32 *>(m_ptr.get() + sizeof(int32))));
249  default:
250  throw ConversionException("The size of the assigned data is not appropriate.");
251  }
252  default:
253  throw ConversionException("Can not convert binary data/genre index/picture to \"position in set\".");
254  }
255  }
256  return PositionInSet();
257 }
258 
265 {
266  if(!isEmpty()) {
267  switch(m_type) {
268  case TagDataType::Text:
269  return TimeSpan::fromString(string(m_ptr.get(), m_size));
272  switch(m_size) {
273  case sizeof(int32):
274  return TimeSpan(*(reinterpret_cast<int32 *>(m_ptr.get())));
275  case sizeof(int64):
276  return TimeSpan(*(reinterpret_cast<int64 *>(m_ptr.get())));
277  default:
278  throw ConversionException("The size of the assigned data is not appropriate.");
279  }
280  default:
281  throw ConversionException("Can not convert binary data/genre index/position in set/picture to time span.");
282  }
283  }
284  return TimeSpan();
285 }
286 
293 {
294  if(!isEmpty()) {
295  switch(m_type) {
296  case TagDataType::Text:
297  return DateTime::fromString(string(m_ptr.get(), m_size));
300  if(m_size == sizeof(int32)) {
301  return DateTime(*(reinterpret_cast<int32 *>(m_ptr.get())));
302  } else if(m_size == sizeof(int64)) {
303  return DateTime(*(reinterpret_cast<int64 *>(m_ptr.get())));
304  } else {
305  throw ConversionException("The assigned data is of unappropriate size.");
306  }
307  default:
308  throw ConversionException("Can not convert binary data/genre index/position in set/picture to date time.");
309  }
310  }
311  return DateTime();
312 }
313 
317 pair<const char *, float> encodingParameter(TagTextEncoding tagTextEncoding)
318 {
319  switch(tagTextEncoding) {
321  return make_pair("ISO-8859-1", 1.0f);
323  return make_pair("UTF-8", 1.0f);
325  return make_pair("UTF-16LE", 2.0f);
327  return make_pair("UTF-16BE", 2.0f);
328  default:
329  return make_pair(nullptr, 0.0f);
330  }
331 }
332 
343 {
344  if(m_encoding != encoding) {
345  if(type() == TagDataType::Text) {
346  StringData encodedData;
347  switch(encoding) {
349  // use pre-defined methods when encoding to UTF-8
350  switch(dataEncoding()) {
352  encodedData = convertLatin1ToUtf8(m_ptr.get(), m_size);
353  break;
355  encodedData = convertUtf16LEToUtf8(m_ptr.get(), m_size);
356  break;
358  encodedData = convertUtf16BEToUtf8(m_ptr.get(), m_size);
359  break;
360  default:
361  ;
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 = convertString(inputParameter.first, outputParameter.first, m_ptr.get(), m_size, outputParameter.second / inputParameter.second);
369  }
370  }
371  // can't just move the encoded data because it needs to be deleted with free
372  m_ptr = make_unique<char []>(m_size = encodedData.second);
373  copy(encodedData.first.get(), encodedData.first.get() + encodedData.second, m_ptr.get());
374  }
375  m_encoding = encoding;
376  }
377 }
378 
384 {
387  }
388 }
389 
399 void TagValue::toString(string &result, TagTextEncoding encoding) const
400 {
401  if(!isEmpty()) {
402  switch(m_type) {
403  case TagDataType::Text:
405  result.assign(m_ptr.get(), m_size);
406  } else {
407  StringData encodedData;
408  switch(encoding) {
410  // use pre-defined methods when encoding to UTF-8
411  switch(dataEncoding()) {
413  encodedData = convertLatin1ToUtf8(m_ptr.get(), m_size);
414  break;
416  encodedData = convertUtf16LEToUtf8(m_ptr.get(), m_size);
417  break;
419  encodedData = convertUtf16BEToUtf8(m_ptr.get(), m_size);
420  break;
421  default:
422  ;
423  }
424  break;
425  default: {
426  // otherwise, determine input and output parameter to use general covertString method
427  const auto inputParameter = encodingParameter(dataEncoding());
428  const auto outputParameter = encodingParameter(encoding);
429  encodedData = convertString(inputParameter.first, outputParameter.first, m_ptr.get(), m_size, outputParameter.second / inputParameter.second);
430  }
431  }
432  result.assign(encodedData.first.get(), encodedData.second);
433  }
434  return;
436  result = ConversionUtilities::numberToString(toInteger());
437  break;
439  result = toPositionInSet().toString();
440  break;
442  if(const char *genreName = Id3Genres::stringFromIndex(toInteger())) {
443  result.assign(genreName);
444  break;
445  } else {
446  throw ConversionException("No string representation for the assigned standard genre index available.");
447  }
449  result = toTimeSpan().toString();
450  break;
451  default:
452  throw ConversionException("Can not convert binary data/picture to string.");
453  }
455  auto encodedData = encoding == TagTextEncoding::Utf16LittleEndian
456  ? convertUtf8ToUtf16LE(result.data(), result.size())
457  : convertUtf8ToUtf16BE(result.data(), result.size());
458  result.assign(encodedData.first.get(), encodedData.second);
459  }
460  } else {
461  result.clear();
462  }
463 }
464 
472 void TagValue::toWString(std::u16string &result, TagTextEncoding encoding) const
473 {
474  if(!isEmpty()) {
475  string regularStrRes;
476  switch(m_type) {
477  case TagDataType::Text:
478  if(encoding == TagTextEncoding::Unspecified || encoding == dataEncoding()) {
479  result.assign(reinterpret_cast<const char16_t *>(m_ptr.get()), m_size / sizeof(char16_t));
480  } else {
481  StringData encodedData;
482  switch(encoding) {
484  // use pre-defined methods when encoding to UTF-8
485  switch(dataEncoding()) {
487  encodedData = convertLatin1ToUtf8(m_ptr.get(), m_size);
488  break;
490  encodedData = convertUtf16LEToUtf8(m_ptr.get(), m_size);
491  break;
493  encodedData = convertUtf16BEToUtf8(m_ptr.get(), m_size);
494  break;
495  default:
496  ;
497  }
498  break;
499  default: {
500  // otherwise, determine input and output parameter to use general covertString method
501  const auto inputParameter = encodingParameter(dataEncoding());
502  const auto outputParameter = encodingParameter(encoding);
503  encodedData = convertString(inputParameter.first, outputParameter.first, m_ptr.get(), m_size, outputParameter.second / inputParameter.second);
504  }
505  }
506  result.assign(reinterpret_cast<const char16_t *>(encodedData.first.get()), encodedData.second / sizeof(char16_t));
507  }
508  return;
510  regularStrRes = ConversionUtilities::numberToString(toInteger());
511  break;
513  regularStrRes = toPositionInSet().toString();
514  break;
516  if(const char *genreName = Id3Genres::stringFromIndex(toInteger())) {
517  regularStrRes.assign(genreName);
518  break;
519  } else {
520  throw ConversionException("No string representation for the assigned standard genre index available.");
521  }
523  regularStrRes = toTimeSpan().toString();
524  break;
525  default:
526  throw ConversionException("Can not convert binary data/picture to string.");
527  }
529  auto encodedData = encoding == TagTextEncoding::Utf16LittleEndian
530  ? convertUtf8ToUtf16LE(regularStrRes.data(), result.size())
531  : convertUtf8ToUtf16BE(regularStrRes.data(), result.size());
532  result.assign(reinterpret_cast<const char16_t *>(encodedData.first.get()), encodedData.second / sizeof(const char16_t));
533  }
534  } else {
535  result.clear();
536  }
537 }
538 
548 void TagValue::assignText(const char *text, std::size_t textSize, TagTextEncoding textEncoding, TagTextEncoding convertTo)
549 {
550  m_type = TagDataType::Text;
551  m_encoding = convertTo == TagTextEncoding::Unspecified ? textEncoding : convertTo;
552  if(textSize) {
553  if(convertTo == TagTextEncoding::Unspecified || textEncoding == convertTo) {
554  m_ptr = make_unique<char []>(m_size = textSize);
555  copy(text, text + textSize, m_ptr.get());
556  } else {
557  StringData encodedData;
558  switch(textEncoding) {
560  // use pre-defined methods when encoding to UTF-8
561  switch(convertTo) {
563  encodedData = convertUtf8ToLatin1(text, textSize);
564  break;
566  encodedData = convertUtf8ToUtf16LE(text, textSize);
567  break;
569  encodedData = convertUtf8ToUtf16BE(text, textSize);
570  break;
571  default:
572  ;
573  }
574  break;
575  default: {
576  // otherwise, determine input and output parameter to use general covertString method
577  const auto inputParameter = encodingParameter(textEncoding);
578  const auto outputParameter = encodingParameter(convertTo);
579  encodedData = convertString(inputParameter.first, outputParameter.first, text, textSize, outputParameter.second / inputParameter.second);
580  }
581  }
582  // can't just move the encoded data because it needs to be deleted with free
583  m_ptr = make_unique<char []>(m_size = encodedData.second);
584  copy(encodedData.first.get(), encodedData.first.get() + encodedData.second, m_ptr.get());
585  }
586  } else {
587  m_size = 0;
588  m_ptr.reset();
589  }
590 }
591 
596 void TagValue::assignInteger(int value)
597 {
598  m_size = sizeof(value);
599  m_ptr = make_unique<char []>(m_size);
600  std::copy(reinterpret_cast<const char *>(&value), reinterpret_cast<const char *>(&value) + m_size, m_ptr.get());
601  m_type = TagDataType::Integer;
602  m_encoding = TagTextEncoding::Latin1;
603 }
604 
611 {
612  assignInteger(index);
614 }
615 
624 void TagValue::assignData(const char *data, size_t length, TagDataType type, TagTextEncoding encoding)
625 {
626  if(length > m_size) {
627  m_ptr = make_unique<char[]>(length);
628  }
629  if(length) {
630  std::copy(data, data + length, m_ptr.get());
631  } else {
632  m_ptr.reset();
633  }
634  m_size = length;
635  m_type = type;
636  m_encoding = encoding;
637 }
638 
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 
662 {
663  static TagValue emptyTagValue;
664  return emptyTagValue;
665 }
666 
667 }
The TagValue class wraps values of different types.
Definition: tagvalue.h:63
bool isEmpty() const
Returns an indication whether an value is assigned.
Definition: tagvalue.h:330
int32 toInteger() const
Converts the value of the current TagValue object to its equivalent integer representation.
Definition: tagvalue.cpp:161
TagTextEncoding dataEncoding() const
Returns the data encoding.
Definition: tagvalue.h:465
int toStandardGenreIndex() const
Converts the value of the current TagValue object to its equivalent standard genre index...
Definition: tagvalue.cpp:189
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:548
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:319
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:77
static int indexFromString(const std::string &genre)
Returns the numerical denotation of the specified genre.
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:610
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:135
void clearDataAndMetadata()
Wipes assigned data including meta data.
Definition: tagvalue.cpp:150
TagTextEncoding
Specifies the text encoding.
Definition: tagvalue.h:21
void convertDataEncoding(TagTextEncoding encoding)
Converts the currently assigned text value to the specified encoding.
Definition: tagvalue.cpp:342
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:294
void clearData()
Clears the assigned data.
Definition: tagvalue.h:341
ChronoUtilities::DateTime toDateTime() const
Converts the value of the current TagValue object to its equivalent DateTime representation.
Definition: tagvalue.cpp:292
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:624
void assignInteger(int value)
Assigns the given integer value.
Definition: tagvalue.cpp:596
PositionInSet toPositionInSet() const
Converts the value of the current TagValue object to its equivalent PositionInSet representation...
Definition: tagvalue.cpp:227
TagDataType
Specifies the data type.
Definition: tagvalue.h:50
void convertDataEncodingForTag(const Tag *tag)
Ensures the encoding of the currently assigned text value is supported by the specified tag...
Definition: tagvalue.cpp:383
ChronoUtilities::TimeSpan toTimeSpan() const
Converts the value of the current TagValue object to its equivalent TimeSpan representation.
Definition: tagvalue.cpp:264
static const TagValue & empty()
Returns an empty TagValue.
Definition: tagvalue.cpp:661
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:125
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:306
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:317