Tag Parser 11.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
4#include "./tag.h"
5
6#include "./id3/id3genres.h"
7
8#include <c++utilities/conversion/binaryconversion.h>
9#include <c++utilities/conversion/conversionexception.h>
10#include <c++utilities/conversion/stringbuilder.h>
11#include <c++utilities/conversion/stringconversion.h>
12#include <c++utilities/io/binaryreader.h>
13#include <c++utilities/io/binarywriter.h>
14
15#include <algorithm>
16#include <cstring>
17#include <sstream>
18#include <utility>
19
20using namespace std;
21using namespace CppUtilities;
22
23namespace TagParser {
24
28std::string_view tagDataTypeString(TagDataType dataType)
29{
30 switch (dataType) {
32 return "text";
34 return "integer";
36 return "position in set";
38 return "genre index";
40 return "time span";
42 return "date time";
44 return "picture";
46 return "binary";
48 return "popularity";
50 return "unsigned integer";
51 default:
52 return "undefined";
53 }
54}
55
59pair<const char *, float> encodingParameter(TagTextEncoding tagTextEncoding)
60{
61 switch (tagTextEncoding) {
63 return make_pair("ISO-8859-1", 1.0f);
65 return make_pair("UTF-8", 1.0f);
67 return make_pair("UTF-16LE", 2.0f);
69 return make_pair("UTF-16BE", 2.0f);
70 default:
71 return make_pair(nullptr, 0.0f);
72 }
73}
74
124 : m_size(other.m_size)
125 , m_desc(other.m_desc)
126 , m_mimeType(other.m_mimeType)
127 , m_locale(other.m_locale)
128 , m_type(other.m_type)
129 , m_encoding(other.m_encoding)
130 , m_descEncoding(other.m_descEncoding)
131 , m_flags(TagValueFlags::None)
132{
133 if (!other.isEmpty()) {
134 m_ptr = make_unique<char[]>(m_size);
135 std::copy(other.m_ptr.get(), other.m_ptr.get() + other.m_size, m_ptr.get());
136 }
137}
138
143{
144 if (this == &other) {
145 return *this;
146 }
147 m_size = other.m_size;
148 m_type = other.m_type;
149 m_desc = other.m_desc;
150 m_mimeType = other.m_mimeType;
151 m_locale = other.m_locale;
152 m_flags = other.m_flags;
153 m_encoding = other.m_encoding;
154 m_descEncoding = other.m_descEncoding;
155 if (other.isEmpty()) {
156 m_ptr.reset();
157 } else {
158 m_ptr = make_unique<char[]>(m_size);
159 std::copy(other.m_ptr.get(), other.m_ptr.get() + other.m_size, m_ptr.get());
160 }
161 return *this;
162}
163
165TagTextEncoding pickUtfEncoding(TagTextEncoding encoding1, TagTextEncoding encoding2)
166{
167 switch (encoding1) {
171 return encoding1;
172 default:
173 switch (encoding2) {
177 return encoding2;
178 default:;
179 }
180 }
182}
184
209{
210 // check whether meta-data is equal (except description)
212 // check meta-data which always uses UTF-8 (everything but description)
213 if (m_mimeType != other.m_mimeType || m_locale != other.m_locale || m_flags != other.m_flags) {
214 return false;
215 }
216
217 // check description which might use different encodings
218 if (m_descEncoding == other.m_descEncoding || m_descEncoding == TagTextEncoding::Unspecified
219 || other.m_descEncoding == TagTextEncoding::Unspecified || m_desc.empty() || other.m_desc.empty()) {
220 if (!compareData(m_desc, other.m_desc, options & TagValueComparisionFlags::CaseInsensitive)) {
221 return false;
222 }
223 } else {
224 const auto utfEncodingToUse = pickUtfEncoding(m_descEncoding, other.m_descEncoding);
225 StringData str1, str2;
226 const char *data1, *data2;
227 size_t size1, size2;
228 if (m_descEncoding != utfEncodingToUse) {
229 const auto inputParameter = encodingParameter(m_descEncoding), outputParameter = encodingParameter(utfEncodingToUse);
230 str1 = convertString(
231 inputParameter.first, outputParameter.first, m_desc.data(), m_desc.size(), outputParameter.second / inputParameter.second);
232 data1 = str1.first.get();
233 size1 = str1.second;
234 } else {
235 data1 = m_desc.data();
236 size1 = m_desc.size();
237 }
238 if (other.m_descEncoding != utfEncodingToUse) {
239 const auto inputParameter = encodingParameter(other.m_descEncoding), outputParameter = encodingParameter(utfEncodingToUse);
240 str2 = convertString(inputParameter.first, outputParameter.first, other.m_desc.data(), other.m_desc.size(),
241 outputParameter.second / inputParameter.second);
242 data2 = str2.first.get();
243 size2 = str2.second;
244 } else {
245 data2 = other.m_desc.data();
246 size2 = other.m_desc.size();
247 }
248 if (!compareData(data1, size1, data2, size2, options & TagValueComparisionFlags::CaseInsensitive)) {
249 return false;
250 }
251 }
252 }
253
254 try {
255 // check for equality if both types are identical
256 if (m_type == other.m_type) {
257 switch (m_type) {
258 case TagDataType::Text: {
259 // compare raw data directly if the encoding is the same
260 if (m_size != other.m_size && m_encoding == other.m_encoding) {
261 return false;
262 }
263 if (m_encoding == other.m_encoding || m_encoding == TagTextEncoding::Unspecified
264 || other.m_encoding == TagTextEncoding::Unspecified) {
266 }
267
268 // compare UTF-8 or UTF-16 representation of strings avoiding unnecessary conversions
269 const auto utfEncodingToUse = pickUtfEncoding(m_encoding, other.m_encoding);
270 string str1, str2;
271 const char *data1, *data2;
272 size_t size1, size2;
273 if (m_encoding != utfEncodingToUse) {
274 str1 = toString(utfEncodingToUse);
275 data1 = str1.data();
276 size1 = str1.size();
277 } else {
278 data1 = m_ptr.get();
279 size1 = m_size;
280 }
281 if (other.m_encoding != utfEncodingToUse) {
282 str2 = other.toString(utfEncodingToUse);
283 data2 = str2.data();
284 size2 = str2.size();
285 } else {
286 data2 = other.m_ptr.get();
287 size2 = other.m_size;
288 }
289 return compareData(data1, size1, data2, size2, options & TagValueComparisionFlags::CaseInsensitive);
290 }
292 return toPositionInSet() == other.toPositionInSet();
294 return toStandardGenreIndex() == other.toStandardGenreIndex();
296 return toTimeSpan() == other.toTimeSpan();
298 return toDateTime() == other.toDateTime();
302 return compareData(other);
303 default:;
304 }
305 }
306
307 // do not attempt implicit conversions for certain types
308 // TODO: Maybe it would actually make sense for some of these types (at least when the other type is
309 // string)?
310 for (const auto dataType : { m_type, other.m_type }) {
311 switch (dataType) {
317 return false;
318 default:;
319 }
320 }
321
322 // handle types where an implicit conversion to the specific type can be done
323 if (m_type == TagDataType::Integer || other.m_type == TagDataType::Integer) {
324 return toInteger() == other.toInteger();
325 } else if (m_type == TagDataType::UnsignedInteger || other.m_type == TagDataType::UnsignedInteger) {
326 return toUnsignedInteger() == other.toUnsignedInteger();
327 } else if (m_type == TagDataType::Popularity || other.m_type == TagDataType::Popularity) {
329 const auto lhs = toPopularity(), rhs = other.toPopularity();
330 return lhs.rating == rhs.rating && lhs.playCounter == rhs.playCounter && lhs.scale == rhs.scale
331 && compareData(lhs.user, rhs.user, true);
332 } else {
333 return toPopularity() == other.toPopularity();
334 }
335 }
336
337 // handle other types where an implicit conversion to string can be done by comparing the string representation
338 return compareData(toString(), other.toString(m_encoding), options & TagValueComparisionFlags::CaseInsensitive);
339
340 } catch (const ConversionException &) {
341 return false;
342 }
343}
344
352{
353 m_desc.clear();
354 m_mimeType.clear();
355 m_locale.clear();
356 m_flags = TagValueFlags::None;
357 m_encoding = TagTextEncoding::Latin1;
358 m_descEncoding = TagTextEncoding::Latin1;
359 m_type = TagDataType::Undefined;
360}
361
371{
372 switch (m_type) {
376 return std::string(tagDataTypeString(m_type));
377 default:
378 try {
379 return toString(TagTextEncoding::Utf8);
380 } catch (const ConversionException &e) {
381 return argsToString("invalid ", tagDataTypeString(m_type), ':', ' ', e.what());
382 }
383 }
384}
385
391std::int32_t TagValue::toInteger() const
392{
393 if (isEmpty()) {
394 return 0;
395 }
396 switch (m_type) {
398 switch (m_encoding) {
401 auto u16str = u16string(reinterpret_cast<char16_t *>(m_ptr.get()), m_size / 2);
402 ensureHostByteOrder(u16str, m_encoding);
403 return stringToNumber<std::int32_t>(u16str);
404 }
405 default:
406 return bufferToNumber<std::int32_t>(m_ptr.get(), m_size);
407 }
409 if (m_size == sizeof(PositionInSet)) {
410 return *reinterpret_cast<std::int32_t *>(m_ptr.get());
411 }
412 throw ConversionException("Can not convert assigned data to integer because the data size is not appropriate.");
415 if (m_size == sizeof(std::int32_t)) {
416 return *reinterpret_cast<std::int32_t *>(m_ptr.get());
417 }
418 throw ConversionException("Can not convert assigned data to integer because the data size is not appropriate.");
420 return static_cast<std::int32_t>(toPopularity().rating);
422 const auto unsignedInteger = toUnsignedInteger();
423 if (unsignedInteger > std::numeric_limits<std::int32_t>::max()) {
424 throw ConversionException(argsToString("Unsigned integer too big for conversion to integer."));
425 }
426 return static_cast<std::int32_t>(unsignedInteger);
427 }
428 default:
429 throw ConversionException(argsToString("Can not convert ", tagDataTypeString(m_type), " to integer."));
430 }
431}
432
433std::uint64_t TagValue::toUnsignedInteger() const
434{
435 if (isEmpty()) {
436 return 0;
437 }
438 switch (m_type) {
440 switch (m_encoding) {
443 auto u16str = u16string(reinterpret_cast<char16_t *>(m_ptr.get()), m_size / 2);
444 ensureHostByteOrder(u16str, m_encoding);
445 return stringToNumber<std::uint64_t>(u16str);
446 }
447 default:
448 return bufferToNumber<std::uint64_t>(m_ptr.get(), m_size);
449 }
453 const auto integer = toInteger();
454 if (integer < 0) {
455 throw ConversionException(argsToString("Can not convert negative value to unsigned integer."));
456 }
457 return static_cast<std::uint64_t>(integer);
458 }
460 return static_cast<std::uint64_t>(toPopularity().rating);
462 if (m_size == sizeof(std::uint64_t)) {
463 return *reinterpret_cast<std::uint64_t *>(m_ptr.get());
464 }
465 throw ConversionException("Can not convert assigned data to unsigned integer because the data size is not appropriate.");
466 default:
467 throw ConversionException(argsToString("Can not convert ", tagDataTypeString(m_type), " to integer."));
468 }
469}
470
477{
478 if (isEmpty()) {
480 }
481 int index = 0;
482 switch (m_type) {
483 case TagDataType::Text: {
484 try {
485 index = toInteger();
486 } catch (const ConversionException &) {
488 if (m_encoding == TagTextEncoding::Latin1) {
489 // no need to convert Latin-1 to UTF-8 (makes no difference in case of genre strings)
491 }
492 index = Id3Genres::indexFromString(toString(encoding));
493 }
494 break;
495 }
499 if (m_size == sizeof(std::int32_t)) {
500 index = static_cast<int>(*reinterpret_cast<std::int32_t *>(m_ptr.get()));
501 } else if (m_size == sizeof(std::uint64_t)) {
502 const auto unsignedInt = *reinterpret_cast<std::uint64_t *>(m_ptr.get());
503 if (unsignedInt <= std::numeric_limits<int>::max()) {
504 index = static_cast<int>(unsignedInt);
505 } else {
506 index = Id3Genres::genreCount();
507 }
508 } else {
509 throw ConversionException("The assigned index/integer is of unappropriate size.");
510 }
511 break;
512 default:
513 throw ConversionException(argsToString("Can not convert ", tagDataTypeString(m_type), " to genre index."));
514 }
516 throw ConversionException("The assigned number is not a valid standard genre index.");
517 }
518 return index;
519}
520
527{
528 if (isEmpty()) {
529 return PositionInSet();
530 }
531 switch (m_type) {
533 switch (m_encoding) {
536 auto u16str = u16string(reinterpret_cast<char16_t *>(m_ptr.get()), m_size / 2);
537 ensureHostByteOrder(u16str, m_encoding);
538 return PositionInSet(u16str);
539 }
540 default:
541 return PositionInSet(string(m_ptr.get(), m_size));
542 }
545 switch (m_size) {
546 case sizeof(std::int32_t):
547 return PositionInSet(*(reinterpret_cast<std::int32_t *>(m_ptr.get())));
548 case 2 * sizeof(std::int32_t):
549 return PositionInSet(
550 *(reinterpret_cast<std::int32_t *>(m_ptr.get())), *(reinterpret_cast<std::int32_t *>(m_ptr.get() + sizeof(std::int32_t))));
551 default:
552 throw ConversionException("The size of the assigned data is not appropriate.");
553 }
555 switch (m_size) {
556 case sizeof(std::uint64_t): {
557 const auto track = *(reinterpret_cast<std::uint64_t *>(m_ptr.get()));
558 if (track < std::numeric_limits<std::int32_t>::max()) {
559 return PositionInSet(static_cast<std::int32_t>(track));
560 }
561 }
562 default:;
563 }
564 throw ConversionException("The size of the assigned data is not appropriate.");
565 default:
566 throw ConversionException(argsToString("Can not convert ", tagDataTypeString(m_type), " to position in set."));
567 }
568}
569
576{
577 if (isEmpty()) {
578 return TimeSpan();
579 }
580 switch (m_type) {
582 return TimeSpan::fromString(toString(m_encoding == TagTextEncoding::Utf8 ? TagTextEncoding::Utf8 : TagTextEncoding::Latin1));
585 switch (m_size) {
586 case sizeof(std::int32_t):
587 return TimeSpan(*(reinterpret_cast<std::int32_t *>(m_ptr.get())));
588 case sizeof(std::int64_t):
589 return TimeSpan(*(reinterpret_cast<std::int64_t *>(m_ptr.get())));
590 default:
591 throw ConversionException("The size of the assigned integer is not appropriate for conversion to time span.");
592 }
594 switch (m_size) {
595 case sizeof(std::uint64_t): {
596 const auto ticks = *(reinterpret_cast<std::uint64_t *>(m_ptr.get()));
597 if (ticks < std::numeric_limits<std::int64_t>::max()) {
598 return TimeSpan(static_cast<std::int64_t>(ticks));
599 }
600 }
601 default:;
602 }
603 throw ConversionException("The size of the assigned data is not appropriate.");
604 default:
605 throw ConversionException(argsToString("Can not convert ", tagDataTypeString(m_type), " to time span."));
606 }
607}
608
614DateTime TagValue::toDateTime() const
615{
616 if (isEmpty()) {
617 return DateTime();
618 }
619 switch (m_type) {
620 case TagDataType::Text: {
622 try {
623 return DateTime::fromIsoStringGmt(str.data());
624 } catch (const ConversionException &) {
625 return DateTime::fromString(str);
626 }
627 }
631 if (m_size == sizeof(std::int32_t)) {
632 return DateTime(*(reinterpret_cast<std::uint32_t *>(m_ptr.get())));
633 } else if (m_size == sizeof(std::uint64_t)) {
634 return DateTime(*(reinterpret_cast<std::uint64_t *>(m_ptr.get())));
635 } else {
636 throw ConversionException("The size of the assigned integer is not appropriate for conversion to date time.");
637 }
638 default:
639 throw ConversionException(argsToString("Can not convert ", tagDataTypeString(m_type), " to date time."));
640 }
641}
642
657{
658 auto popularity = Popularity();
659 if (isEmpty()) {
660 return popularity;
661 }
662 switch (m_type) {
664 popularity = Popularity::fromString(std::string_view(toString(TagTextEncoding::Utf8)));
665 break;
667 popularity.rating = static_cast<double>(toInteger());
668 break;
670 auto s = std::stringstream(std::ios_base::in | std::ios_base::out | std::ios_base::binary);
671 auto reader = BinaryReader(&s);
672 try {
673 s.exceptions(std::ios_base::failbit | std::ios_base::badbit);
674 s.rdbuf()->pubsetbuf(m_ptr.get(), static_cast<std::streamsize>(m_size));
675 popularity.user = reader.readLengthPrefixedString();
676 popularity.rating = reader.readFloat64LE();
677 popularity.playCounter = reader.readUInt64LE();
678 popularity.scale = static_cast<TagType>(reader.readUInt64LE());
679 } catch (const std::ios_base::failure &) {
680 throw ConversionException(argsToString("Assigned popularity is invalid"));
681 }
682 break;
683 }
685 popularity.rating = static_cast<double>(toUnsignedInteger());
686 break;
687 default:
688 throw ConversionException(argsToString("Can not convert ", tagDataTypeString(m_type), " to date time."));
689 }
690 return popularity;
691}
692
713{
714 auto popularity = toPopularity();
715 if (m_type == TagDataType::Text) {
716 popularity.scale = scale;
717 } else if (!popularity.scaleTo(scale)) {
718 throw ConversionException(argsToString("Assigned popularity cannot be scaled accordingly"));
719 }
720 return popularity;
721}
722
733{
734 if (m_encoding == encoding) {
735 return;
736 }
737 if (type() == TagDataType::Text) {
738 StringData encodedData;
739 switch (encoding) {
741 // use pre-defined methods when encoding to UTF-8
742 switch (dataEncoding()) {
744 encodedData = convertLatin1ToUtf8(m_ptr.get(), m_size);
745 break;
747 encodedData = convertUtf16LEToUtf8(m_ptr.get(), m_size);
748 break;
750 encodedData = convertUtf16BEToUtf8(m_ptr.get(), m_size);
751 break;
752 default:;
753 }
754 break;
755 default: {
756 // otherwise, determine input and output parameter to use general covertString method
757 const auto inputParameter = encodingParameter(dataEncoding());
758 const auto outputParameter = encodingParameter(encoding);
759 encodedData
760 = convertString(inputParameter.first, outputParameter.first, m_ptr.get(), m_size, outputParameter.second / inputParameter.second);
761 }
762 }
763 // can't just move the encoded data because it needs to be deleted with free
764 m_ptr = make_unique<char[]>(m_size = encodedData.second);
765 copy(encodedData.first.get(), encodedData.first.get() + encodedData.second, m_ptr.get());
766 }
767 m_encoding = encoding;
768}
769
775{
778 }
779}
780
785{
786 if (encoding == m_descEncoding) {
787 return;
788 }
789 if (m_desc.empty()) {
790 m_descEncoding = encoding;
791 return;
792 }
793 StringData encodedData;
794 switch (encoding) {
796 // use pre-defined methods when encoding to UTF-8
797 switch (descriptionEncoding()) {
799 encodedData = convertLatin1ToUtf8(m_desc.data(), m_desc.size());
800 break;
802 encodedData = convertUtf16LEToUtf8(m_desc.data(), m_desc.size());
803 break;
805 encodedData = convertUtf16BEToUtf8(m_desc.data(), m_desc.size());
806 break;
807 default:;
808 }
809 break;
810 default: {
811 // otherwise, determine input and output parameter to use general covertString method
812 const auto inputParameter = encodingParameter(m_descEncoding);
813 const auto outputParameter = encodingParameter(encoding);
814 encodedData = convertString(
815 inputParameter.first, outputParameter.first, m_desc.data(), m_desc.size(), outputParameter.second / inputParameter.second);
816 }
817 }
818 m_desc.assign(encodedData.first.get(), encodedData.second);
819 m_descEncoding = encoding;
820}
821
835void TagValue::toString(string &result, TagTextEncoding encoding) const
836{
837 if (isEmpty()) {
838 result.clear();
839 return;
840 }
841
842 switch (m_type) {
845 result.assign(m_ptr.get(), m_size);
846 } else {
847 StringData encodedData;
848 switch (encoding) {
850 // use pre-defined methods when encoding to UTF-8
851 switch (dataEncoding()) {
853 encodedData = convertLatin1ToUtf8(m_ptr.get(), m_size);
854 break;
856 encodedData = convertUtf16LEToUtf8(m_ptr.get(), m_size);
857 break;
859 encodedData = convertUtf16BEToUtf8(m_ptr.get(), m_size);
860 break;
861 default:;
862 }
863 break;
864 default: {
865 // otherwise, determine input and output parameter to use general covertString method
866 const auto inputParameter = encodingParameter(dataEncoding());
867 const auto outputParameter = encodingParameter(encoding);
868 encodedData
869 = convertString(inputParameter.first, outputParameter.first, m_ptr.get(), m_size, outputParameter.second / inputParameter.second);
870 }
871 }
872 result.assign(encodedData.first.get(), encodedData.second);
873 }
874 return;
876 result = numberToString(toInteger());
877 break;
879 result = toPositionInSet().toString();
880 break;
882 const auto genreIndex = toInteger();
883 if (Id3Genres::isEmptyGenre(genreIndex)) {
884 result.clear();
885 } else if (const auto genreName = Id3Genres::stringFromIndex(genreIndex); !genreName.empty()) {
886 result.assign(genreName);
887 } else {
888 throw ConversionException("No string representation for the assigned standard genre index available.");
889 }
890 break;
891 }
893 result = toTimeSpan().toString();
894 break;
896 result = toDateTime().toString(DateTimeOutputFormat::IsoOmittingDefaultComponents);
897 break;
899 result = toPopularity().toString();
900 break;
902 result = numberToString(toUnsignedInteger());
903 break;
904 default:
905 throw ConversionException(argsToString("Can not convert ", tagDataTypeString(m_type), " to string."));
906 }
908 auto encodedData = encoding == TagTextEncoding::Utf16LittleEndian ? convertUtf8ToUtf16LE(result.data(), result.size())
909 : convertUtf8ToUtf16BE(result.data(), result.size());
910 result.assign(encodedData.first.get(), encodedData.second);
911 }
912}
913
924void TagValue::toWString(std::u16string &result, TagTextEncoding encoding) const
925{
926 if (isEmpty()) {
927 result.clear();
928 return;
929 }
930
931 string regularStrRes;
932 switch (m_type) {
934 if (encoding == TagTextEncoding::Unspecified || encoding == dataEncoding()) {
935 result.assign(reinterpret_cast<const char16_t *>(m_ptr.get()), m_size / sizeof(char16_t));
936 } else {
937 StringData encodedData;
938 switch (encoding) {
940 // use pre-defined methods when encoding to UTF-8
941 switch (dataEncoding()) {
943 encodedData = convertLatin1ToUtf8(m_ptr.get(), m_size);
944 break;
946 encodedData = convertUtf16LEToUtf8(m_ptr.get(), m_size);
947 break;
949 encodedData = convertUtf16BEToUtf8(m_ptr.get(), m_size);
950 break;
951 default:;
952 }
953 break;
954 default: {
955 // otherwise, determine input and output parameter to use general covertString method
956 const auto inputParameter = encodingParameter(dataEncoding());
957 const auto outputParameter = encodingParameter(encoding);
958 encodedData
959 = convertString(inputParameter.first, outputParameter.first, m_ptr.get(), m_size, outputParameter.second / inputParameter.second);
960 }
961 }
962 result.assign(reinterpret_cast<const char16_t *>(encodedData.first.get()), encodedData.second / sizeof(char16_t));
963 }
964 return;
966 regularStrRes = numberToString(toInteger());
967 break;
969 regularStrRes = toPositionInSet().toString();
970 break;
972 const auto genreIndex = toInteger();
973 if (Id3Genres::isEmptyGenre(genreIndex)) {
974 regularStrRes.clear();
975 } else if (const auto genreName = Id3Genres::stringFromIndex(genreIndex); !genreName.empty()) {
976 regularStrRes.assign(genreName);
977 } else {
978 throw ConversionException("No string representation for the assigned standard genre index available.");
979 }
980 break;
981 }
983 regularStrRes = toTimeSpan().toString();
984 break;
986 regularStrRes = toDateTime().toString(DateTimeOutputFormat::IsoOmittingDefaultComponents);
987 break;
989 regularStrRes = toPopularity().toString();
990 break;
992 regularStrRes = numberToString(toUnsignedInteger());
993 break;
994 default:
995 throw ConversionException(argsToString("Can not convert ", tagDataTypeString(m_type), " to string."));
996 }
998 auto encodedData = encoding == TagTextEncoding::Utf16LittleEndian ? convertUtf8ToUtf16LE(regularStrRes.data(), result.size())
999 : convertUtf8ToUtf16BE(regularStrRes.data(), result.size());
1000 result.assign(reinterpret_cast<const char16_t *>(encodedData.first.get()), encodedData.second / sizeof(const char16_t));
1001 }
1002}
1003
1014void TagValue::assignText(const char *text, std::size_t textSize, TagTextEncoding textEncoding, TagTextEncoding convertTo)
1015{
1016 m_type = TagDataType::Text;
1017 m_encoding = convertTo == TagTextEncoding::Unspecified ? textEncoding : convertTo;
1018
1019 stripBom(text, textSize, textEncoding);
1020 if (!textSize) {
1021 m_size = 0;
1022 m_ptr.reset();
1023 return;
1024 }
1025
1026 if (convertTo == TagTextEncoding::Unspecified || textEncoding == convertTo) {
1027 m_ptr = make_unique<char[]>(m_size = textSize);
1028 copy(text, text + textSize, m_ptr.get());
1029 return;
1030 }
1031
1032 StringData encodedData;
1033 switch (textEncoding) {
1035 // use pre-defined methods when encoding to UTF-8
1036 switch (convertTo) {
1038 encodedData = convertUtf8ToLatin1(text, textSize);
1039 break;
1041 encodedData = convertUtf8ToUtf16LE(text, textSize);
1042 break;
1044 encodedData = convertUtf8ToUtf16BE(text, textSize);
1045 break;
1046 default:;
1047 }
1048 break;
1049 default: {
1050 // otherwise, determine input and output parameter to use general covertString method
1051 const auto inputParameter = encodingParameter(textEncoding);
1052 const auto outputParameter = encodingParameter(convertTo);
1053 encodedData = convertString(inputParameter.first, outputParameter.first, text, textSize, outputParameter.second / inputParameter.second);
1054 }
1055 }
1056 // can't just move the encoded data because it needs to be deleted with free
1057 m_ptr = make_unique<char[]>(m_size = encodedData.second);
1058 copy(encodedData.first.get(), encodedData.first.get() + encodedData.second, m_ptr.get());
1059}
1060
1066{
1067 m_size = sizeof(value);
1068 m_ptr = make_unique<char[]>(m_size);
1069 std::copy(reinterpret_cast<const char *>(&value), reinterpret_cast<const char *>(&value) + m_size, m_ptr.get());
1070 m_type = TagDataType::Integer;
1071 m_encoding = TagTextEncoding::Latin1;
1072}
1073
1078void TagValue::assignUnsignedInteger(std::uint64_t value)
1079{
1080 m_size = sizeof(value);
1081 m_ptr = make_unique<char[]>(m_size);
1082 std::copy(reinterpret_cast<const char *>(&value), reinterpret_cast<const char *>(&value) + m_size, m_ptr.get());
1084 m_encoding = TagTextEncoding::Latin1;
1085}
1086
1095void TagValue::assignData(const char *data, size_t length, TagDataType type, TagTextEncoding encoding)
1096{
1097 if (type == TagDataType::Text) {
1098 stripBom(data, length, encoding);
1099 }
1100 if (length > m_size) {
1101 m_ptr = make_unique<char[]>(length);
1102 }
1103 if (length) {
1104 std::copy(data, data + length, m_ptr.get());
1105 } else {
1106 m_ptr.reset();
1107 }
1108 m_size = length;
1109 m_type = type;
1110 m_encoding = encoding;
1111}
1112
1125void TagValue::assignData(unique_ptr<char[]> &&data, size_t length, TagDataType type, TagTextEncoding encoding)
1126{
1127 m_size = length;
1128 m_type = type;
1129 m_encoding = encoding;
1130 m_ptr = move(data);
1131}
1132
1137{
1138 auto s = std::stringstream(std::ios_base::in | std::ios_base::out | std::ios_base::binary);
1139 auto writer = BinaryWriter(&s);
1140 try {
1141 s.exceptions(std::ios_base::failbit | std::ios_base::badbit);
1142 writer.writeLengthPrefixedString(value.user);
1143 writer.writeFloat64LE(value.rating);
1144 writer.writeUInt64LE(value.playCounter);
1145 writer.writeUInt64LE(static_cast<std::uint64_t>(value.scale));
1146 auto size = static_cast<std::size_t>(s.tellp());
1147 auto ptr = std::make_unique<char[]>(size);
1148 s.read(ptr.get(), s.tellp());
1149 assignData(std::move(ptr), size, TagDataType::Popularity);
1150 } catch (const std::ios_base::failure &) {
1151 throw ConversionException("Unable to serialize specified Popularity");
1152 }
1153}
1154
1158void TagValue::stripBom(const char *&text, size_t &length, TagTextEncoding encoding)
1159{
1160 switch (encoding) {
1162 if ((length >= 3) && (BE::toUInt24(text) == 0x00EFBBBF)) {
1163 text += 3;
1164 length -= 3;
1165 }
1166 break;
1168 if ((length >= 2) && (LE::toUInt16(text) == 0xFEFF)) {
1169 text += 2;
1170 length -= 2;
1171 }
1172 break;
1174 if ((length >= 2) && (BE::toUInt16(text) == 0xFEFF)) {
1175 text += 2;
1176 length -= 2;
1177 }
1178 break;
1179 default:;
1180 }
1181}
1182
1187void TagValue::ensureHostByteOrder(u16string &u16str, TagTextEncoding currentEncoding)
1188{
1189 if (currentEncoding !=
1190#if defined(CONVERSION_UTILITIES_BYTE_ORDER_LITTLE_ENDIAN)
1192#elif defined(CONVERSION_UTILITIES_BYTE_ORDER_BIG_ENDIAN)
1194#else
1195#error "Host byte order not supported"
1196#endif
1197 ) {
1198 for (auto &c : u16str) {
1199 c = swapOrder(static_cast<std::uint16_t>(c));
1200 }
1201 }
1202}
1203
1207bool TagValue::compareData(const char *data1, std::size_t size1, const char *data2, std::size_t size2, bool ignoreCase)
1208{
1209 if (size1 != size2) {
1210 return false;
1211 }
1212 if (!size1) {
1213 return true;
1214 }
1215 if (ignoreCase) {
1216 for (auto i1 = data1, i2 = data2, end = data1 + size1; i1 != end; ++i1, ++i2) {
1217 if (CaseInsensitiveCharComparer::toLower(static_cast<unsigned char>(*i1))
1218 != CaseInsensitiveCharComparer::toLower(static_cast<unsigned char>(*i2))) {
1219 return false;
1220 }
1221 }
1222 } else {
1223 for (auto i1 = data1, i2 = data2, end = data1 + size1; i1 != end; ++i1, ++i2) {
1224 if (*i1 != *i2) {
1225 return false;
1226 }
1227 }
1228 }
1229 return true;
1230}
1231
1238{
1239 static TagValue emptyTagValue;
1240 return emptyTagValue;
1241}
1242
1256{
1257 if (scale == targetScale) {
1258 return true;
1259 }
1260
1261 // convert to generic scale first
1262 double genericRating;
1263 switch (scale) {
1265 genericRating = rating;
1266 break;
1268 genericRating = rating / (5.0 / 4.0) + 1.0;
1269 break;
1270 case TagType::Id3v2Tag:
1271 genericRating = rating < 1.0 ? 0.0 : ((rating - 1.0) / (254.0 / 4.0) + 1.0);
1272 break;
1275 genericRating = rating / 20.0;
1276 break;
1277 default:
1278 return false;
1279 }
1280
1281 // convert from the generic scale to the target scale
1282 switch (targetScale) {
1284 rating = genericRating;
1285 break;
1287 rating = (genericRating - 1.0) * (5.0 / 4.0);
1288 break;
1289 case TagType::Id3v2Tag:
1290 rating = genericRating < 1.0 ? 0.0 : ((genericRating - 1.0) * (254.0 / 4.0) + 1.0);
1291 break;
1294 rating = genericRating * 20.0;
1295 break;
1296 default:
1297 return false;
1298 }
1299
1300 scale = targetScale;
1301 return true;
1302}
1303
1308std::string Popularity::toString() const
1309{
1310 return isEmpty() ? std::string()
1311 : ((user.empty() && !playCounter) ? numberToString(rating) : (user % '|' % numberToString(rating) % '|' + playCounter));
1312}
1313
1321{
1322 return fromString(str, TagType::Unspecified);
1323}
1324
1332{
1333 const auto parts = splitStringSimple<std::vector<std::string_view>>(str, "|");
1334 auto res = Popularity({ .scale = scale });
1335 if (parts.empty()) {
1336 return res;
1337 } else if (parts.size() > 3) {
1338 throw ConversionException("Wrong format, expected \"rating\" or \"user|rating|play-counter\"");
1339 }
1340 // treat a single number as rating
1341 if (parts.size() == 1) {
1342 try {
1343 res.rating = stringToNumber<decltype(res.rating)>(parts.front());
1344 return res;
1345 } catch (const ConversionException &) {
1346 }
1347 }
1348 // otherwise, read user, rating and play counter
1349 res.user = parts.front();
1350 if (parts.size() > 1) {
1351 res.rating = stringToNumber<decltype(res.rating)>(parts[1]);
1352 }
1353 if (parts.size() > 2) {
1354 res.playCounter = stringToNumber<decltype(res.playCounter)>(parts[2]);
1355 }
1356 return res;
1357}
1358
1359} // namespace TagParser
static constexpr bool isEmptyGenre(int index)
Returns whether the genre index indicates the genre field is not set at all.
Definition: id3genres.h:53
static constexpr bool isIndexSupported(int index)
Returns an indication whether the specified numerical denotation is supported by this class.
Definition: id3genres.h:62
static constexpr int emptyGenreIndex()
Returns the preferred genre index to indicate that no genre is set at all.
Definition: id3genres.h:45
static constexpr int genreCount()
Returns the number of supported genres.
Definition: id3genres.h:35
static int indexFromString(std::string_view genre)
Returns the numerical denotation of the specified genre or -1 if genre is unknown.
Definition: id3genres.cpp:42
static std::string_view stringFromIndex(int index)
Returns the genre name for the specified numerical denotation as C-style string.
Definition: id3genres.h:27
The PositionInSet class describes the position of an element in a set which consists of a certain num...
Definition: positioninset.h:21
StringType toString() const
Returns the string representation of the current PositionInSet.
The TagValue class wraps values of different types.
Definition: tagvalue.h:142
bool compareData(const TagValue &other, bool ignoreCase=false) const
Returns whether the raw data of the current instance equals the raw data of other.
Definition: tagvalue.h:861
void clearMetadata()
Wipes assigned meta data.
Definition: tagvalue.cpp:351
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:1014
void assignInteger(int value)
Assigns the given integer value.
Definition: tagvalue.cpp:1065
bool compareTo(const TagValue &other, TagValueComparisionFlags options=TagValueComparisionFlags::None) const
Returns whether both instances are equal.
Definition: tagvalue.cpp:208
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:1187
CppUtilities::DateTime toDateTime() const
Converts the value of the current TagValue object to its equivalent DateTime representation.
Definition: tagvalue.cpp:614
std::string toDisplayString() const
Returns a "display string" for the specified value.
Definition: tagvalue.cpp:370
TagTextEncoding dataEncoding() const
Returns the data encoding.
Definition: tagvalue.h:825
void assignData(const char *data, std::size_t length, TagDataType type=TagDataType::Binary, TagTextEncoding encoding=TagTextEncoding::Latin1)
std::uint64_t toUnsignedInteger() const
Definition: tagvalue.cpp:433
std::int32_t toInteger() const
Converts the value of the current TagValue object to its equivalent integer representation.
Definition: tagvalue.cpp:391
PositionInSet toPositionInSet() const
Converts the value of the current TagValue object to its equivalent PositionInSet representation.
Definition: tagvalue.cpp:526
TagDataType type() const
Returns the type of the assigned value.
Definition: tagvalue.h:540
void convertDataEncodingForTag(const Tag *tag)
Ensures the encoding of the currently assigned text value is supported by the specified tag.
Definition: tagvalue.cpp:774
void assignPopularity(const Popularity &value)
Assigns the specified popularity value.
Definition: tagvalue.cpp:1136
void assignUnsignedInteger(std::uint64_t value)
Assigns the given unsigned integer value.
Definition: tagvalue.cpp:1078
Popularity toPopularity() const
Converts the value of the current TagValue object to its equivalent Popularity representation.
Definition: tagvalue.cpp:656
std::string_view data() const
Returns the currently assigned raw data.
Definition: tagvalue.h:653
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:570
Popularity toScaledPopularity(TagType scale=TagType::Unspecified) const
Converts the value of the current TagValue object to its equivalent Popularity representation using t...
Definition: tagvalue.cpp:712
void convertDataEncoding(TagTextEncoding encoding)
Converts the currently assigned text value to the specified encoding.
Definition: tagvalue.cpp:732
TagTextEncoding descriptionEncoding() const
Returns the description encoding.
Definition: tagvalue.h:835
TagValue & operator=(const TagValue &other)
Assigns the value of another TagValue to the current instance.
Definition: tagvalue.cpp:142
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:557
static const TagValue & empty()
Returns a default-constructed TagValue where TagValue::isNull() and TagValue::isEmpty() both return t...
Definition: tagvalue.cpp:1237
static void stripBom(const char *&text, std::size_t &length, TagTextEncoding encoding)
Strips the byte order mask from the specified text.
Definition: tagvalue.cpp:1158
bool isEmpty() const
Returns whether no or an empty value is assigned.
Definition: tagvalue.h:597
void convertDescriptionEncoding(TagTextEncoding encoding)
Converts the assigned description to use the specified encoding.
Definition: tagvalue.cpp:784
int toStandardGenreIndex() const
Converts the value of the current TagValue object to its equivalent standard genre index.
Definition: tagvalue.cpp:476
CppUtilities::TimeSpan toTimeSpan() const
Converts the value of the current TagValue object to its equivalent TimeSpan representation.
Definition: tagvalue.cpp:575
TagValue()
Constructs an empty TagValue.
Definition: tagvalue.h:264
The Tag class is used to store, read and write tag information.
Definition: tag.h:163
virtual TagTextEncoding proposedTextEncoding() const
Returns the proposed text encoding.
Definition: tag.h:215
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:220
Contains all classes and functions of the TagInfo library.
Definition: aaccodebook.h:10
std::string_view tagDataTypeString(TagDataType dataType)
Returns the string representation of the specified dataType.
Definition: tagvalue.cpp:28
TagTextEncoding
Specifies the text encoding.
Definition: tagvalue.h:29
TagType
Specifies the tag type.
Definition: tagtype.h:11
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:59
TagValueComparisionFlags
The TagValueComparisionOption enum specifies options for TagValue::compareTo().
Definition: tagvalue.h:136
TagValueFlags
Specifies additional flags about the tag value.
Definition: tagvalue.h:43
TagDataType
Specifies the data type.
Definition: tagvalue.h:119
static constexpr unsigned char toLower(const unsigned char c)
std::string user
The user who gave the rating / played the file, e.g. identified by e-mail address.
Definition: tagvalue.h:74
bool isEmpty() const
Returns whether the Popularity is empty. The scale and zero-values don't count.
Definition: tagvalue.h:91
static Popularity fromString(std::string_view str)
Parses the popularity from str assuming the same format as toString() produces and sets TagType::Unsp...
Definition: tagvalue.cpp:1320
std::uint64_t playCounter
Play counter specific to the user.
Definition: tagvalue.h:78
bool scaleTo(TagType targetScale)
Scales the rating from the current scale to targetScale.
Definition: tagvalue.cpp:1255
TagType scale
Specifies the scale used for rating by the tag defining that scale.
Definition: tagvalue.h:82
double rating
The rating on a tag type specific scale.
Definition: tagvalue.h:76
std::string toString() const
Returns the popularity as string in the format "rating" if only a rating is present or in the format ...
Definition: tagvalue.cpp:1308