Tag Parser  6.3.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 
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  // don't consider differently encoded text values equal
88  return false;
89  }
90  return strncmp(m_ptr.get(), other.m_ptr.get(), m_size) == 0;
92  return toPositionInSet() == other.toPositionInSet();
94  return toInteger() == other.toInteger();
96  return toStandardGenreIndex() == other.toStandardGenreIndex();
98  return toTimeSpan() == other.toTimeSpan();
100  return toDateTime() == other.toDateTime();
102  case TagDataType::Binary:
104  if(m_size != other.m_size) {
105  return false;
106  }
107  return strncmp(m_ptr.get(), other.m_ptr.get(), m_size) == 0;
108  default:
109  return false;
110  }
111  } else {
112  // different types
113  try {
114  // try to convert both values to string
115  // if the string representations are equal, both values can also be considered equal
116  return toString() == other.toString();
117  } catch(const ConversionException &) {
118  return false;
119  }
120  }
121 }
122 
127 {}
128 
137 {
138  m_desc.clear();
139  m_mimeType.clear();
140  m_lng.clear();
141  m_labeledAsReadonly = false;
142  m_encoding = TagTextEncoding::Latin1;
143  m_type = TagDataType::Undefined;
144 }
145 
152 {
153  clearData();
154  clearMetadata();
155 }
156 
162 int32 TagValue::toInteger() const
163 {
164  if(!isEmpty()) {
165  switch(m_type) {
166  case TagDataType::Text:
167  return ConversionUtilities::stringToNumber<int32>(string(m_ptr.get(), m_size));
171  if(m_size == sizeof(int32)) {
172  auto res = *reinterpret_cast<int32 *>(m_ptr.get());
173  return res;
174  } else {
175  throw ConversionException("Can not convert assigned data to integer because the data size is not appropriate.");
176  }
177  break;
178  default:
179  throw ConversionException("Can not convert binary data/picture/time span/date time to integer.");
180  }
181  }
182  return 0;
183 }
184 
191 {
192  if(!isEmpty()) {
193  int index = 0;
194  switch(m_type) {
195  case TagDataType::Text: {
196  string s(m_ptr.get(), m_size);
197  try {
198  index = ConversionUtilities::stringToNumber<int32>(s);
199  } catch (ConversionException &) {
200  index = Id3Genres::indexFromString(s);
201  }
202  break;
205  if(m_size == sizeof(int)) {
206  index = *reinterpret_cast<int *>(m_ptr.get());
207  } else {
208  throw ConversionException("The assigned data is of unappropriate size.");
209  }
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  } else {
217  throw ConversionException("The assigned number is not a valid standard genre index.");
218  }
219  }
220  return 0;
221 }
222 
229 {
230  if(!isEmpty()) {
231  switch(m_type) {
232  case TagDataType::Text:
233  switch(m_encoding) {
237  return PositionInSet(string(m_ptr.get(), m_size));
240  // FIXME: Ensure endianness is correctly
241  return PositionInSet(u16string(reinterpret_cast<char16_t *>(m_ptr.get()), m_size / 2));
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  return PositionInSet();
258 }
259 
266 {
267  if(!isEmpty()) {
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  return TimeSpan();
286 }
287 
294 {
295  if(!isEmpty()) {
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<int32 *>(m_ptr.get())));
303  } else if(m_size == sizeof(int64)) {
304  return DateTime(*(reinterpret_cast<int64 *>(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  return DateTime();
313 }
314 
318 pair<const char *, float> encodingParameter(TagTextEncoding tagTextEncoding)
319 {
320  switch(tagTextEncoding) {
322  return make_pair("ISO-8859-1", 1.0f);
324  return make_pair("UTF-8", 1.0f);
326  return make_pair("UTF-16LE", 2.0f);
328  return make_pair("UTF-16BE", 2.0f);
329  default:
330  return make_pair(nullptr, 0.0f);
331  }
332 }
333 
344 {
345  if(m_encoding != encoding) {
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  }
364  break;
365  default: {
366  // otherwise, determine input and output parameter to use general covertString method
367  const auto inputParameter = encodingParameter(dataEncoding());
368  const auto outputParameter = encodingParameter(encoding);
369  encodedData = 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 }
379 
385 {
388  }
389 }
390 
400 void TagValue::toString(string &result, TagTextEncoding encoding) const
401 {
402  if(!isEmpty()) {
403  switch(m_type) {
404  case TagDataType::Text:
406  result.assign(m_ptr.get(), m_size);
407  } else {
408  StringData encodedData;
409  switch(encoding) {
411  // use pre-defined methods when encoding to UTF-8
412  switch(dataEncoding()) {
414  encodedData = convertLatin1ToUtf8(m_ptr.get(), m_size);
415  break;
417  encodedData = convertUtf16LEToUtf8(m_ptr.get(), m_size);
418  break;
420  encodedData = convertUtf16BEToUtf8(m_ptr.get(), m_size);
421  break;
422  default:
423  ;
424  }
425  break;
426  default: {
427  // otherwise, determine input and output parameter to use general covertString method
428  const auto inputParameter = encodingParameter(dataEncoding());
429  const auto outputParameter = encodingParameter(encoding);
430  encodedData = convertString(inputParameter.first, outputParameter.first, m_ptr.get(), m_size, outputParameter.second / inputParameter.second);
431  }
432  }
433  result.assign(encodedData.first.get(), encodedData.second);
434  }
435  return;
437  result = ConversionUtilities::numberToString(toInteger());
438  break;
440  result = toPositionInSet().toString();
441  break;
443  if(const char *genreName = Id3Genres::stringFromIndex(toInteger())) {
444  result.assign(genreName);
445  break;
446  } else {
447  throw ConversionException("No string representation for the assigned standard genre index available.");
448  }
450  result = toTimeSpan().toString();
451  break;
452  default:
453  throw ConversionException("Can not convert binary data/picture to string.");
454  }
456  auto encodedData = encoding == TagTextEncoding::Utf16LittleEndian
457  ? convertUtf8ToUtf16LE(result.data(), result.size())
458  : convertUtf8ToUtf16BE(result.data(), result.size());
459  result.assign(encodedData.first.get(), encodedData.second);
460  }
461  } else {
462  result.clear();
463  }
464 }
465 
473 void TagValue::toWString(std::u16string &result, TagTextEncoding encoding) const
474 {
475  if(!isEmpty()) {
476  string regularStrRes;
477  switch(m_type) {
478  case TagDataType::Text:
479  if(encoding == TagTextEncoding::Unspecified || encoding == dataEncoding()) {
480  result.assign(reinterpret_cast<const char16_t *>(m_ptr.get()), m_size / sizeof(char16_t));
481  } else {
482  StringData encodedData;
483  switch(encoding) {
485  // use pre-defined methods when encoding to UTF-8
486  switch(dataEncoding()) {
488  encodedData = convertLatin1ToUtf8(m_ptr.get(), m_size);
489  break;
491  encodedData = convertUtf16LEToUtf8(m_ptr.get(), m_size);
492  break;
494  encodedData = convertUtf16BEToUtf8(m_ptr.get(), m_size);
495  break;
496  default:
497  ;
498  }
499  break;
500  default: {
501  // otherwise, determine input and output parameter to use general covertString method
502  const auto inputParameter = encodingParameter(dataEncoding());
503  const auto outputParameter = encodingParameter(encoding);
504  encodedData = convertString(inputParameter.first, outputParameter.first, m_ptr.get(), m_size, outputParameter.second / inputParameter.second);
505  }
506  }
507  result.assign(reinterpret_cast<const char16_t *>(encodedData.first.get()), encodedData.second / sizeof(char16_t));
508  }
509  return;
511  regularStrRes = ConversionUtilities::numberToString(toInteger());
512  break;
514  regularStrRes = toPositionInSet().toString();
515  break;
517  if(const char *genreName = Id3Genres::stringFromIndex(toInteger())) {
518  regularStrRes.assign(genreName);
519  break;
520  } else {
521  throw ConversionException("No string representation for the assigned standard genre index available.");
522  }
524  regularStrRes = toTimeSpan().toString();
525  break;
526  default:
527  throw ConversionException("Can not convert binary data/picture to string.");
528  }
530  auto encodedData = encoding == TagTextEncoding::Utf16LittleEndian
531  ? convertUtf8ToUtf16LE(regularStrRes.data(), result.size())
532  : convertUtf8ToUtf16BE(regularStrRes.data(), result.size());
533  result.assign(reinterpret_cast<const char16_t *>(encodedData.first.get()), encodedData.second / sizeof(const char16_t));
534  }
535  } else {
536  result.clear();
537  }
538 }
539 
550 void TagValue::assignText(const char *text, std::size_t textSize, TagTextEncoding textEncoding, TagTextEncoding convertTo)
551 {
552  m_type = TagDataType::Text;
553  m_encoding = convertTo == TagTextEncoding::Unspecified ? textEncoding : convertTo;
554 
555  stripBom(text, textSize, textEncoding);
556  if(!textSize) {
557  m_size = 0;
558  m_ptr.reset();
559  return;
560  }
561 
562  if(convertTo == TagTextEncoding::Unspecified || textEncoding == convertTo) {
563  m_ptr = make_unique<char []>(m_size = textSize);
564  copy(text, text + textSize, m_ptr.get());
565  } else {
566  StringData encodedData;
567  switch(textEncoding) {
569  // use pre-defined methods when encoding to UTF-8
570  switch(convertTo) {
572  encodedData = convertUtf8ToLatin1(text, textSize);
573  break;
575  encodedData = convertUtf8ToUtf16LE(text, textSize);
576  break;
578  encodedData = convertUtf8ToUtf16BE(text, textSize);
579  break;
580  default:
581  ;
582  }
583  break;
584  default: {
585  // otherwise, determine input and output parameter to use general covertString method
586  const auto inputParameter = encodingParameter(textEncoding);
587  const auto outputParameter = encodingParameter(convertTo);
588  encodedData = convertString(inputParameter.first, outputParameter.first, text, textSize, outputParameter.second / inputParameter.second);
589  }
590  }
591  // can't just move the encoded data because it needs to be deleted with free
592  m_ptr = make_unique<char []>(m_size = encodedData.second);
593  copy(encodedData.first.get(), encodedData.first.get() + encodedData.second, m_ptr.get());
594  }
595 }
596 
601 void TagValue::assignInteger(int value)
602 {
603  m_size = sizeof(value);
604  m_ptr = make_unique<char []>(m_size);
605  std::copy(reinterpret_cast<const char *>(&value), reinterpret_cast<const char *>(&value) + m_size, m_ptr.get());
606  m_type = TagDataType::Integer;
607  m_encoding = TagTextEncoding::Latin1;
608 }
609 
616 {
617  assignInteger(index);
619 }
620 
629 void TagValue::assignData(const char *data, size_t length, TagDataType type, TagTextEncoding encoding)
630 {
631  if(type == TagDataType::Text) {
632  stripBom(data, length, encoding);
633  }
634  if(length > m_size) {
635  m_ptr = make_unique<char[]>(length);
636  }
637  if(length) {
638  std::copy(data, data + length, m_ptr.get());
639  } else {
640  m_ptr.reset();
641  }
642  m_size = length;
643  m_type = type;
644  m_encoding = encoding;
645 }
646 
659 void TagValue::assignData(unique_ptr<char[]> &&data, size_t length, TagDataType type, TagTextEncoding encoding)
660 {
661  m_size = length;
662  m_type = type;
663  m_encoding = encoding;
664  m_ptr = move(data);
665 }
666 
670 void TagValue::stripBom(const char *&text, size_t &length, TagTextEncoding encoding)
671 {
672  switch(encoding) {
674  if((length >= 3) && (ConversionUtilities::BE::toUInt24(text) == 0x00EFBBBF)) {
675  text += 3;
676  length -= 3;
677  }
678  break;
680  if((length >= 2) && (ConversionUtilities::LE::toUInt16(text) == 0xFEFF)) {
681  text += 2;
682  length -= 2;
683  }
684  break;
686  if((length >= 2) && (ConversionUtilities::BE::toUInt16(text) == 0xFEFF)) {
687  text += 2;
688  length -= 2;
689  }
690  break;
691  default:
692  ;
693  }
694 }
695 
700 {
701  static TagValue emptyTagValue;
702  return emptyTagValue;
703 }
704 
705 }
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:340
int32 toInteger() const
Converts the value of the current TagValue object to its equivalent integer representation.
Definition: tagvalue.cpp:162
TagTextEncoding dataEncoding() const
Returns the data encoding.
Definition: tagvalue.h:475
int toStandardGenreIndex() const
Converts the value of the current TagValue object to its equivalent standard genre index...
Definition: tagvalue.cpp:190
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:550
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:329
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:615
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:136
void clearDataAndMetadata()
Wipes assigned data including meta data.
Definition: tagvalue.cpp:151
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:343
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:304
void clearData()
Clears the assigned data.
Definition: tagvalue.h:351
ChronoUtilities::DateTime toDateTime() const
Converts the value of the current TagValue object to its equivalent DateTime representation.
Definition: tagvalue.cpp:293
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:629
void assignInteger(int value)
Assigns the given integer value.
Definition: tagvalue.cpp:601
PositionInSet toPositionInSet() const
Converts the value of the current TagValue object to its equivalent PositionInSet representation...
Definition: tagvalue.cpp:228
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:384
ChronoUtilities::TimeSpan toTimeSpan() const
Converts the value of the current TagValue object to its equivalent TimeSpan representation.
Definition: tagvalue.cpp:265
static const TagValue & empty()
Returns an empty TagValue.
Definition: tagvalue.cpp:699
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:126
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:316
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:318