Tag Parser 12.1.0
C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags
Loading...
Searching...
No Matches
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
27
31std::string_view tagDataTypeString(TagDataType dataType)
32{
33 switch (dataType) {
35 return "text";
37 return "integer";
39 return "position in set";
41 return "genre index";
43 return "time span";
45 return "date time";
47 return "picture";
49 return "binary";
51 return "popularity";
53 return "unsigned integer";
55 return "date time expression";
56 default:
57 return "undefined";
58 }
59}
60
64pair<const char *, float> encodingParameter(TagTextEncoding tagTextEncoding)
65{
66 switch (tagTextEncoding) {
68 return make_pair("ISO-8859-1", 1.0f);
70 return make_pair("UTF-8", 1.0f);
72 return make_pair("UTF-16LE", 2.0f);
74 return make_pair("UTF-16BE", 2.0f);
75 default:
76 return make_pair(nullptr, 0.0f);
77 }
78}
79
136 : m_size(other.m_size)
137 , m_desc(other.m_desc)
138 , m_mimeType(other.m_mimeType)
139 , m_locale(other.m_locale)
140 , m_type(other.m_type)
141 , m_encoding(other.m_encoding)
142 , m_descEncoding(other.m_descEncoding)
143 , m_flags(TagValueFlags::None)
144{
145 if (!other.isEmpty()) {
146 m_ptr = make_unique<char[]>(m_size);
147 std::copy(other.m_ptr.get(), other.m_ptr.get() + other.m_size, m_ptr.get());
148 }
149}
150
151TagValue::TagValue(TagValue &&other) = default;
152
157 : m_size(0)
158 , m_type(TagDataType::Undefined)
159 , m_encoding(TagTextEncoding::Latin1)
160 , m_descEncoding(TagTextEncoding::Latin1)
161 , m_flags(TagValueFlags::None)
162{
163}
164
175TagValue::TagValue(const char *text, std::size_t textSize, TagTextEncoding textEncoding, TagTextEncoding convertTo)
176 : m_descEncoding(TagTextEncoding::Latin1)
177 , m_flags(TagValueFlags::None)
178{
179 assignText(text, textSize, textEncoding, convertTo);
180}
181
191TagValue::TagValue(const char *text, TagTextEncoding textEncoding, TagTextEncoding convertTo)
192{
193 assignText(text, std::strlen(text), textEncoding, convertTo);
194}
195
205TagValue::TagValue(const std::string &text, TagTextEncoding textEncoding, TagTextEncoding convertTo)
206 : m_descEncoding(TagTextEncoding::Latin1)
207 , m_flags(TagValueFlags::None)
208{
209 assignText(text, textEncoding, convertTo);
210}
211
221TagValue::TagValue(std::string_view text, TagTextEncoding textEncoding, TagTextEncoding convertTo)
222 : m_descEncoding(TagTextEncoding::Latin1)
223 , m_flags(TagValueFlags::None)
224{
225 assignText(text, textEncoding, convertTo);
226}
227
234
245TagValue::TagValue(const char *data, std::size_t length, TagDataType type, TagTextEncoding encoding)
246 : m_size(length)
247 , m_type(type)
248 , m_encoding(encoding)
249 , m_descEncoding(TagTextEncoding::Latin1)
250 , m_flags(TagValueFlags::None)
251{
252 if (length) {
253 if (type == TagDataType::Text) {
254 stripBom(data, m_size, encoding);
255 }
256 m_ptr = std::make_unique<char[]>(m_size);
257 std::copy(data, data + m_size, m_ptr.get());
258 }
259}
260
273TagValue::TagValue(std::unique_ptr<char[]> &&data, std::size_t length, TagDataType type, TagTextEncoding encoding)
274 : m_size(length)
275 , m_type(type)
276 , m_encoding(encoding)
277 , m_descEncoding(TagTextEncoding::Latin1)
278 , m_flags(TagValueFlags::None)
279{
280 if (length) {
281 m_ptr = std::move(data);
282 }
283}
284
289{
290 if (this == &other) {
291 return *this;
292 }
293 m_size = other.m_size;
294 m_type = other.m_type;
295 m_desc = other.m_desc;
296 m_mimeType = other.m_mimeType;
297 m_locale = other.m_locale;
298 m_flags = other.m_flags;
299 m_encoding = other.m_encoding;
300 m_descEncoding = other.m_descEncoding;
301 if (other.isEmpty()) {
302 m_ptr.reset();
303 } else {
304 m_ptr = make_unique<char[]>(m_size);
305 std::copy(other.m_ptr.get(), other.m_ptr.get() + other.m_size, m_ptr.get());
306 }
307 return *this;
308}
309
310TagValue &TagValue::operator=(TagValue &&other) = default;
311
313TagTextEncoding pickUtfEncoding(TagTextEncoding encoding1, TagTextEncoding encoding2)
314{
315 switch (encoding1) {
319 return encoding1;
320 default:
321 switch (encoding2) {
325 return encoding2;
326 default:;
327 }
328 }
330}
332
355{
356 // check whether meta-data is equal (except description)
358 // check meta-data which always uses UTF-8 (everything but description)
359 if (m_mimeType != other.m_mimeType || m_locale != other.m_locale || m_flags != other.m_flags) {
360 return false;
361 }
362
363 // check description which might use different encodings
364 if (m_descEncoding == other.m_descEncoding || m_descEncoding == TagTextEncoding::Unspecified
365 || other.m_descEncoding == TagTextEncoding::Unspecified || m_desc.empty() || other.m_desc.empty()) {
366 if (!compareData(m_desc, other.m_desc, options & TagValueComparisionFlags::CaseInsensitive)) {
367 return false;
368 }
369 } else {
370 const auto utfEncodingToUse = pickUtfEncoding(m_descEncoding, other.m_descEncoding);
371 StringData str1, str2;
372 const char *data1, *data2;
373 size_t size1, size2;
374 if (m_descEncoding != utfEncodingToUse) {
375 const auto inputParameter = encodingParameter(m_descEncoding), outputParameter = encodingParameter(utfEncodingToUse);
376 str1 = convertString(
377 inputParameter.first, outputParameter.first, m_desc.data(), m_desc.size(), outputParameter.second / inputParameter.second);
378 data1 = str1.first.get();
379 size1 = str1.second;
380 } else {
381 data1 = m_desc.data();
382 size1 = m_desc.size();
383 }
384 if (other.m_descEncoding != utfEncodingToUse) {
385 const auto inputParameter = encodingParameter(other.m_descEncoding), outputParameter = encodingParameter(utfEncodingToUse);
386 str2 = convertString(inputParameter.first, outputParameter.first, other.m_desc.data(), other.m_desc.size(),
387 outputParameter.second / inputParameter.second);
388 data2 = str2.first.get();
389 size2 = str2.second;
390 } else {
391 data2 = other.m_desc.data();
392 size2 = other.m_desc.size();
393 }
394 if (!compareData(data1, size1, data2, size2, options & TagValueComparisionFlags::CaseInsensitive)) {
395 return false;
396 }
397 }
398 }
399
400 try {
401 // check for equality if both types are identical
402 if (m_type == other.m_type) {
403 switch (m_type) {
404 case TagDataType::Text: {
405 // compare raw data directly if the encoding is the same
406 if (m_size != other.m_size && m_encoding == other.m_encoding) {
407 return false;
408 }
409 if (m_encoding == other.m_encoding || m_encoding == TagTextEncoding::Unspecified
410 || other.m_encoding == TagTextEncoding::Unspecified) {
412 }
413
414 // compare UTF-8 or UTF-16 representation of strings avoiding unnecessary conversions
415 const auto utfEncodingToUse = pickUtfEncoding(m_encoding, other.m_encoding);
416 string str1, str2;
417 const char *data1, *data2;
418 size_t size1, size2;
419 if (m_encoding != utfEncodingToUse) {
420 str1 = toString(utfEncodingToUse);
421 data1 = str1.data();
422 size1 = str1.size();
423 } else {
424 data1 = m_ptr.get();
425 size1 = m_size;
426 }
427 if (other.m_encoding != utfEncodingToUse) {
428 str2 = other.toString(utfEncodingToUse);
429 data2 = str2.data();
430 size2 = str2.size();
431 } else {
432 data2 = other.m_ptr.get();
433 size2 = other.m_size;
434 }
435 return compareData(data1, size1, data2, size2, options & TagValueComparisionFlags::CaseInsensitive);
436 }
438 return toPositionInSet() == other.toPositionInSet();
440 return toStandardGenreIndex() == other.toStandardGenreIndex();
442 return toTimeSpan() == other.toTimeSpan();
444 return toDateTime() == other.toDateTime();
446 return toDateTimeExpression() == other.toDateTimeExpression();
450 return compareData(other);
451 default:;
452 }
453 }
454
455 // do not attempt implicit conversions for certain types
456 // TODO: Maybe it would actually make sense for some of these types (at least when the other type is
457 // string)?
458 for (const auto dataType : { m_type, other.m_type }) {
459 switch (dataType) {
466 return false;
467 default:;
468 }
469 }
470
471 // handle types where an implicit conversion to the specific type can be done
472 if (m_type == TagDataType::Integer || other.m_type == TagDataType::Integer) {
473 return toInteger() == other.toInteger();
474 } else if (m_type == TagDataType::UnsignedInteger || other.m_type == TagDataType::UnsignedInteger) {
475 return toUnsignedInteger() == other.toUnsignedInteger();
476 } else if (m_type == TagDataType::Popularity || other.m_type == TagDataType::Popularity) {
478 const auto lhs = toPopularity(), rhs = other.toPopularity();
479 return lhs.rating == rhs.rating && lhs.playCounter == rhs.playCounter && lhs.scale == rhs.scale
480 && compareData(lhs.user, rhs.user, true);
481 } else {
482 return toPopularity() == other.toPopularity();
483 }
484 }
485
486 // handle other types where an implicit conversion to string can be done by comparing the string representation
487 return compareData(toString(), other.toString(m_encoding), options & TagValueComparisionFlags::CaseInsensitive);
488
489 } catch (const ConversionException &) {
490 return false;
491 }
492}
493
501{
502 m_desc.clear();
503 m_mimeType.clear();
504 m_locale.clear();
505 m_flags = TagValueFlags::None;
506 m_encoding = TagTextEncoding::Latin1;
507 m_descEncoding = TagTextEncoding::Latin1;
508 m_type = TagDataType::Undefined;
509}
510
520{
521 switch (m_type) {
525 return std::string(tagDataTypeString(m_type));
526 default:
527 try {
528 return toString(TagTextEncoding::Utf8);
529 } catch (const ConversionException &e) {
530 return argsToString("invalid ", tagDataTypeString(m_type), ':', ' ', e.what());
531 }
532 }
533}
534
540std::int32_t TagValue::toInteger() const
541{
542 if (isEmpty()) {
543 return 0;
544 }
545 switch (m_type) {
547 switch (m_encoding) {
550 auto u16str = u16string(reinterpret_cast<char16_t *>(m_ptr.get()), m_size / 2);
551 ensureHostByteOrder(u16str, m_encoding);
552 return stringToNumber<std::int32_t>(u16str);
553 }
554 default:
555 return bufferToNumber<std::int32_t>(m_ptr.get(), m_size);
556 }
558 if (m_size == sizeof(PositionInSet)) {
559 return *reinterpret_cast<std::int32_t *>(m_ptr.get());
560 }
561 throw ConversionException("Can not convert assigned data to integer because the data size is not appropriate.");
564 if (m_size == sizeof(std::int32_t)) {
565 return *reinterpret_cast<std::int32_t *>(m_ptr.get());
566 }
567 throw ConversionException("Can not convert assigned data to integer because the data size is not appropriate.");
569 return static_cast<std::int32_t>(toPopularity().rating);
571 const auto unsignedInteger = toUnsignedInteger();
572 if (unsignedInteger > std::numeric_limits<std::int32_t>::max()) {
573 throw ConversionException(argsToString("Unsigned integer too big for conversion to integer."));
574 }
575 return static_cast<std::int32_t>(unsignedInteger);
576 }
577 default:
578 throw ConversionException(argsToString("Can not convert ", tagDataTypeString(m_type), " to integer."));
579 }
580}
581
582std::uint64_t TagValue::toUnsignedInteger() const
583{
584 if (isEmpty()) {
585 return 0;
586 }
587 switch (m_type) {
589 switch (m_encoding) {
592 auto u16str = u16string(reinterpret_cast<char16_t *>(m_ptr.get()), m_size / 2);
593 ensureHostByteOrder(u16str, m_encoding);
594 return stringToNumber<std::uint64_t>(u16str);
595 }
596 default:
597 return bufferToNumber<std::uint64_t>(m_ptr.get(), m_size);
598 }
602 const auto integer = toInteger();
603 if (integer < 0) {
604 throw ConversionException(argsToString("Can not convert negative value to unsigned integer."));
605 }
606 return static_cast<std::uint64_t>(integer);
607 }
609 return static_cast<std::uint64_t>(toPopularity().rating);
611 if (m_size == sizeof(std::uint64_t)) {
612 return *reinterpret_cast<std::uint64_t *>(m_ptr.get());
613 }
614 throw ConversionException("Can not convert assigned data to unsigned integer because the data size is not appropriate.");
615 default:
616 throw ConversionException(argsToString("Can not convert ", tagDataTypeString(m_type), " to integer."));
617 }
618}
619
626{
627 if (isEmpty()) {
629 }
630 int index = 0;
631 switch (m_type) {
632 case TagDataType::Text: {
633 try {
634 index = toInteger();
635 } catch (const ConversionException &) {
637 if (m_encoding == TagTextEncoding::Latin1) {
638 // no need to convert Latin-1 to UTF-8 (makes no difference in case of genre strings)
640 }
641 index = Id3Genres::indexFromString(toString(encoding));
642 }
643 break;
644 }
648 if (m_size == sizeof(std::int32_t)) {
649 index = static_cast<int>(*reinterpret_cast<std::int32_t *>(m_ptr.get()));
650 } else if (m_size == sizeof(std::uint64_t)) {
651 const auto unsignedInt = *reinterpret_cast<std::uint64_t *>(m_ptr.get());
652 if (unsignedInt <= std::numeric_limits<int>::max()) {
653 index = static_cast<int>(unsignedInt);
654 } else {
655 index = Id3Genres::genreCount();
656 }
657 } else {
658 throw ConversionException("The assigned index/integer is of unappropriate size.");
659 }
660 break;
661 default:
662 throw ConversionException(argsToString("Can not convert ", tagDataTypeString(m_type), " to genre index."));
663 }
665 throw ConversionException("The assigned number is not a valid standard genre index.");
666 }
667 return index;
668}
669
676{
677 if (isEmpty()) {
678 return PositionInSet();
679 }
680 switch (m_type) {
682 switch (m_encoding) {
685 auto u16str = u16string(reinterpret_cast<char16_t *>(m_ptr.get()), m_size / 2);
686 ensureHostByteOrder(u16str, m_encoding);
687 return PositionInSet(u16str);
688 }
689 default:
690 return PositionInSet(string(m_ptr.get(), m_size));
691 }
694 switch (m_size) {
695 case sizeof(std::int32_t):
696 return PositionInSet(*(reinterpret_cast<std::int32_t *>(m_ptr.get())));
697 case 2 * sizeof(std::int32_t):
698 return PositionInSet(
699 *(reinterpret_cast<std::int32_t *>(m_ptr.get())), *(reinterpret_cast<std::int32_t *>(m_ptr.get() + sizeof(std::int32_t))));
700 default:
701 throw ConversionException("The size of the assigned data is not appropriate.");
702 }
704 switch (m_size) {
705 case sizeof(std::uint64_t): {
706 const auto track = *(reinterpret_cast<std::uint64_t *>(m_ptr.get()));
707 if (track < std::numeric_limits<std::int32_t>::max()) {
708 return PositionInSet(static_cast<std::int32_t>(track));
709 }
710 }
711 default:;
712 }
713 throw ConversionException("The size of the assigned data is not appropriate.");
714 default:
715 throw ConversionException(argsToString("Can not convert ", tagDataTypeString(m_type), " to position in set."));
716 }
717}
718
725{
726 if (isEmpty()) {
727 return TimeSpan();
728 }
729 switch (m_type) {
731 return TimeSpan::fromString(toString(m_encoding == TagTextEncoding::Utf8 ? TagTextEncoding::Utf8 : TagTextEncoding::Latin1));
734 switch (m_size) {
735 case sizeof(std::int32_t):
736 return TimeSpan(*(reinterpret_cast<std::int32_t *>(m_ptr.get())));
737 case sizeof(std::int64_t):
738 return TimeSpan(*(reinterpret_cast<std::int64_t *>(m_ptr.get())));
739 default:
740 throw ConversionException("The size of the assigned data is not appropriate for conversion to time span.");
741 }
743 switch (m_size) {
744 case sizeof(std::uint64_t): {
745 const auto ticks = *(reinterpret_cast<std::uint64_t *>(m_ptr.get()));
746 if (ticks < static_cast<std::uint64_t>(std::numeric_limits<std::int64_t>::max())) {
747 return TimeSpan(static_cast<std::int64_t>(ticks));
748 }
749 }
750 default:;
751 }
752 throw ConversionException("The size of the assigned data is not appropriate.");
753 default:
754 throw ConversionException(argsToString("Can not convert ", tagDataTypeString(m_type), " to time span."));
755 }
756}
757
764{
765 if (isEmpty()) {
766 return DateTime();
767 }
768 switch (m_type) {
769 case TagDataType::Text: {
771 try {
772 return DateTime::fromIsoStringGmt(str.data());
773 } catch (const ConversionException &) {
774 return DateTime::fromString(str);
775 }
776 }
780 if (m_size == sizeof(std::int32_t)) {
781 return DateTime(*(reinterpret_cast<std::uint32_t *>(m_ptr.get())));
782 } else if (m_size == sizeof(std::uint64_t)) {
783 return DateTime(*(reinterpret_cast<std::uint64_t *>(m_ptr.get())));
784 } else {
785 throw ConversionException("The size of the assigned data is not appropriate for conversion to date time.");
786 }
788 return toDateTimeExpression().gmt();
789 default:
790 throw ConversionException(argsToString("Can not convert ", tagDataTypeString(m_type), " to date time."));
791 }
792}
793
799CppUtilities::DateTimeExpression TagParser::TagValue::toDateTimeExpression() const
800{
801 if (isEmpty()) {
802 return DateTimeExpression();
803 }
804 switch (m_type) {
805 case TagDataType::Text: {
806 const auto str = toString(m_encoding == TagTextEncoding::Utf8 ? TagTextEncoding::Utf8 : TagTextEncoding::Latin1);
807 try {
808 return DateTimeExpression::fromIsoString(str.data());
809 } catch (const ConversionException &) {
810 return DateTimeExpression::fromString(str.data());
811 }
812 }
816 return DateTimeExpression{ .value = toDateTime(), .delta = TimeSpan(), .parts = DateTimeParts::DateTime };
818 if (m_size == sizeof(DateTimeExpression)) {
819 return *reinterpret_cast<DateTimeExpression *>(m_ptr.get());
820 } else {
821 throw ConversionException("The size of the assigned data is not appropriate for conversion to date time expression.");
822 }
823 default:
824 throw ConversionException(argsToString("Can not convert ", tagDataTypeString(m_type), " to date time."));
825 }
826}
827
842{
843 auto popularity = Popularity();
844 if (isEmpty()) {
845 return popularity;
846 }
847 switch (m_type) {
849 popularity = Popularity::fromString(std::string_view(toString(TagTextEncoding::Utf8)));
850 break;
852 popularity.rating = static_cast<double>(toInteger());
853 break;
855 auto s = std::stringstream(std::ios_base::in | std::ios_base::out | std::ios_base::binary);
856 auto reader = BinaryReader(&s);
857 try {
858 s.exceptions(std::ios_base::failbit | std::ios_base::badbit);
859#if defined(__GLIBCXX__) && !defined(_LIBCPP_VERSION)
860 s.rdbuf()->pubsetbuf(m_ptr.get(), static_cast<std::streamsize>(m_size));
861#else
862 s.write(m_ptr.get(), static_cast<std::streamsize>(m_size));
863#endif
864 popularity.user = reader.readLengthPrefixedString();
865 popularity.rating = reader.readFloat64LE();
866 popularity.playCounter = reader.readUInt64LE();
867 popularity.scale = static_cast<TagType>(reader.readUInt64LE());
868 } catch (const std::ios_base::failure &) {
869 throw ConversionException(argsToString("Assigned popularity is invalid"));
870 }
871 break;
872 }
874 popularity.rating = static_cast<double>(toUnsignedInteger());
875 break;
876 default:
877 throw ConversionException(argsToString("Can not convert ", tagDataTypeString(m_type), " to date time."));
878 }
879 return popularity;
880}
881
902{
903 auto popularity = toPopularity();
904 if (m_type == TagDataType::Text) {
905 popularity.scale = scale;
906 } else if (!popularity.scaleTo(scale)) {
907 throw ConversionException(argsToString("Assigned popularity cannot be scaled accordingly"));
908 }
909 return popularity;
910}
911
922{
923 if (m_encoding == encoding) {
924 return;
925 }
926 if (type() == TagDataType::Text) {
927 StringData encodedData;
928 switch (encoding) {
930 // use pre-defined methods when encoding to UTF-8
931 switch (dataEncoding()) {
933 encodedData = convertLatin1ToUtf8(m_ptr.get(), m_size);
934 break;
936 encodedData = convertUtf16LEToUtf8(m_ptr.get(), m_size);
937 break;
939 encodedData = convertUtf16BEToUtf8(m_ptr.get(), m_size);
940 break;
941 default:;
942 }
943 break;
944 default: {
945 // otherwise, determine input and output parameter to use general covertString method
946 const auto inputParameter = encodingParameter(dataEncoding());
947 const auto outputParameter = encodingParameter(encoding);
948 encodedData
949 = convertString(inputParameter.first, outputParameter.first, m_ptr.get(), m_size, outputParameter.second / inputParameter.second);
950 }
951 }
952 // can't just move the encoded data because it needs to be deleted with free
953 m_ptr = make_unique<char[]>(m_size = encodedData.second);
954 copy(encodedData.first.get(), encodedData.first.get() + encodedData.second, m_ptr.get());
955 }
956 m_encoding = encoding;
957}
958
969
974{
975 if (encoding == m_descEncoding) {
976 return;
977 }
978 if (m_desc.empty()) {
979 m_descEncoding = encoding;
980 return;
981 }
982 StringData encodedData;
983 switch (encoding) {
985 // use pre-defined methods when encoding to UTF-8
986 switch (descriptionEncoding()) {
988 encodedData = convertLatin1ToUtf8(m_desc.data(), m_desc.size());
989 break;
991 encodedData = convertUtf16LEToUtf8(m_desc.data(), m_desc.size());
992 break;
994 encodedData = convertUtf16BEToUtf8(m_desc.data(), m_desc.size());
995 break;
996 default:;
997 }
998 break;
999 default: {
1000 // otherwise, determine input and output parameter to use general covertString method
1001 const auto inputParameter = encodingParameter(m_descEncoding);
1002 const auto outputParameter = encodingParameter(encoding);
1003 encodedData = convertString(
1004 inputParameter.first, outputParameter.first, m_desc.data(), m_desc.size(), outputParameter.second / inputParameter.second);
1005 }
1006 }
1007 m_desc.assign(encodedData.first.get(), encodedData.second);
1008 m_descEncoding = encoding;
1009}
1010
1024void TagValue::toString(string &result, TagTextEncoding encoding) const
1025{
1026 if (isEmpty()) {
1027 result.clear();
1028 return;
1029 }
1030
1031 switch (m_type) {
1032 case TagDataType::Text:
1034 result.assign(m_ptr.get(), m_size);
1035 } else {
1036 StringData encodedData;
1037 switch (encoding) {
1039 // use pre-defined methods when encoding to UTF-8
1040 switch (dataEncoding()) {
1042 encodedData = convertLatin1ToUtf8(m_ptr.get(), m_size);
1043 break;
1045 encodedData = convertUtf16LEToUtf8(m_ptr.get(), m_size);
1046 break;
1048 encodedData = convertUtf16BEToUtf8(m_ptr.get(), m_size);
1049 break;
1050 default:;
1051 }
1052 break;
1053 default: {
1054 // otherwise, determine input and output parameter to use general covertString method
1055 const auto inputParameter = encodingParameter(dataEncoding());
1056 const auto outputParameter = encodingParameter(encoding);
1057 encodedData
1058 = convertString(inputParameter.first, outputParameter.first, m_ptr.get(), m_size, outputParameter.second / inputParameter.second);
1059 }
1060 }
1061 result.assign(encodedData.first.get(), encodedData.second);
1062 }
1063 return;
1065 result = numberToString(toInteger());
1066 break;
1068 result = toPositionInSet().toString();
1069 break;
1071 const auto genreIndex = toInteger();
1072 if (Id3Genres::isEmptyGenre(genreIndex)) {
1073 result.clear();
1074 } else if (const auto genreName = Id3Genres::stringFromIndex(genreIndex); !genreName.empty()) {
1075 result.assign(genreName);
1076 } else {
1077 throw ConversionException("No string representation for the assigned standard genre index available.");
1078 }
1079 break;
1080 }
1082 result = toTimeSpan().toString();
1083 break;
1085 result = toDateTime().toIsoString();
1086 break;
1088 result = toPopularity().toString();
1089 break;
1091 result = numberToString(toUnsignedInteger());
1092 break;
1094 result = toDateTimeExpression().toIsoString();
1095 break;
1096 default:
1097 throw ConversionException(argsToString("Can not convert ", tagDataTypeString(m_type), " to string."));
1098 }
1100 auto encodedData = encoding == TagTextEncoding::Utf16LittleEndian ? convertUtf8ToUtf16LE(result.data(), result.size())
1101 : convertUtf8ToUtf16BE(result.data(), result.size());
1102 result.assign(encodedData.first.get(), encodedData.second);
1103 }
1104}
1105
1116void TagValue::toWString(std::u16string &result, TagTextEncoding encoding) const
1117{
1118 if (isEmpty()) {
1119 result.clear();
1120 return;
1121 }
1122
1123 string regularStrRes;
1124 switch (m_type) {
1125 case TagDataType::Text:
1126 if (encoding == TagTextEncoding::Unspecified || encoding == dataEncoding()) {
1127 result.assign(reinterpret_cast<const char16_t *>(m_ptr.get()), m_size / sizeof(char16_t));
1128 } else {
1129 StringData encodedData;
1130 switch (encoding) {
1132 // use pre-defined methods when encoding to UTF-8
1133 switch (dataEncoding()) {
1135 encodedData = convertLatin1ToUtf8(m_ptr.get(), m_size);
1136 break;
1138 encodedData = convertUtf16LEToUtf8(m_ptr.get(), m_size);
1139 break;
1141 encodedData = convertUtf16BEToUtf8(m_ptr.get(), m_size);
1142 break;
1143 default:;
1144 }
1145 break;
1146 default: {
1147 // otherwise, determine input and output parameter to use general covertString method
1148 const auto inputParameter = encodingParameter(dataEncoding());
1149 const auto outputParameter = encodingParameter(encoding);
1150 encodedData
1151 = convertString(inputParameter.first, outputParameter.first, m_ptr.get(), m_size, outputParameter.second / inputParameter.second);
1152 }
1153 }
1154 result.assign(reinterpret_cast<const char16_t *>(encodedData.first.get()), encodedData.second / sizeof(char16_t));
1155 }
1156 return;
1158 regularStrRes = numberToString(toInteger());
1159 break;
1161 regularStrRes = toPositionInSet().toString();
1162 break;
1164 const auto genreIndex = toInteger();
1165 if (Id3Genres::isEmptyGenre(genreIndex)) {
1166 regularStrRes.clear();
1167 } else if (const auto genreName = Id3Genres::stringFromIndex(genreIndex); !genreName.empty()) {
1168 regularStrRes.assign(genreName);
1169 } else {
1170 throw ConversionException("No string representation for the assigned standard genre index available.");
1171 }
1172 break;
1173 }
1175 regularStrRes = toTimeSpan().toString();
1176 break;
1178 regularStrRes = toDateTime().toString(DateTimeOutputFormat::IsoOmittingDefaultComponents);
1179 break;
1181 regularStrRes = toPopularity().toString();
1182 break;
1184 regularStrRes = numberToString(toUnsignedInteger());
1185 break;
1187 regularStrRes = toDateTimeExpression().toIsoString();
1188 break;
1189 default:
1190 throw ConversionException(argsToString("Can not convert ", tagDataTypeString(m_type), " to string."));
1191 }
1193 auto encodedData = encoding == TagTextEncoding::Utf16LittleEndian ? convertUtf8ToUtf16LE(regularStrRes.data(), result.size())
1194 : convertUtf8ToUtf16BE(regularStrRes.data(), result.size());
1195 result.assign(reinterpret_cast<const char16_t *>(encodedData.first.get()), encodedData.second / sizeof(const char16_t));
1196 }
1197}
1198
1209void TagValue::assignText(const char *text, std::size_t textSize, TagTextEncoding textEncoding, TagTextEncoding convertTo)
1210{
1211 m_type = TagDataType::Text;
1212 m_encoding = convertTo == TagTextEncoding::Unspecified ? textEncoding : convertTo;
1213
1214 stripBom(text, textSize, textEncoding);
1215 if (!textSize) {
1216 m_size = 0;
1217 m_ptr.reset();
1218 return;
1219 }
1220
1221 if (convertTo == TagTextEncoding::Unspecified || textEncoding == convertTo) {
1222 m_ptr = make_unique<char[]>(m_size = textSize);
1223 copy(text, text + textSize, m_ptr.get());
1224 return;
1225 }
1226
1227 StringData encodedData;
1228 switch (textEncoding) {
1230 // use pre-defined methods when encoding to UTF-8
1231 switch (convertTo) {
1233 encodedData = convertUtf8ToLatin1(text, textSize);
1234 break;
1236 encodedData = convertUtf8ToUtf16LE(text, textSize);
1237 break;
1239 encodedData = convertUtf8ToUtf16BE(text, textSize);
1240 break;
1241 default:;
1242 }
1243 break;
1244 default: {
1245 // otherwise, determine input and output parameter to use general covertString method
1246 const auto inputParameter = encodingParameter(textEncoding);
1247 const auto outputParameter = encodingParameter(convertTo);
1248 encodedData = convertString(inputParameter.first, outputParameter.first, text, textSize, outputParameter.second / inputParameter.second);
1249 }
1250 }
1251 // can't just move the encoded data because it needs to be deleted with free
1252 m_ptr = make_unique<char[]>(m_size = encodedData.second);
1253 copy(encodedData.first.get(), encodedData.first.get() + encodedData.second, m_ptr.get());
1254}
1255
1261{
1262 m_size = sizeof(value);
1263 m_ptr = make_unique<char[]>(m_size);
1264 std::copy(reinterpret_cast<const char *>(&value), reinterpret_cast<const char *>(&value) + m_size, m_ptr.get());
1265 m_type = TagDataType::Integer;
1266 m_encoding = TagTextEncoding::Latin1;
1267}
1268
1273void TagValue::assignUnsignedInteger(std::uint64_t value)
1274{
1275 m_size = sizeof(value);
1276 m_ptr = make_unique<char[]>(m_size);
1277 std::copy(reinterpret_cast<const char *>(&value), reinterpret_cast<const char *>(&value) + m_size, m_ptr.get());
1279 m_encoding = TagTextEncoding::Latin1;
1280}
1281
1290void TagValue::assignData(const char *data, size_t length, TagDataType type, TagTextEncoding encoding)
1291{
1292 if (type == TagDataType::Text) {
1293 stripBom(data, length, encoding);
1294 }
1295 if (length > m_size) {
1296 m_ptr = make_unique<char[]>(length);
1297 }
1298 if (length) {
1299 std::copy(data, data + length, m_ptr.get());
1300 } else {
1301 m_ptr.reset();
1302 }
1303 m_size = length;
1304 m_type = type;
1305 m_encoding = encoding;
1306}
1307
1320void TagValue::assignData(unique_ptr<char[]> &&data, size_t length, TagDataType type, TagTextEncoding encoding)
1321{
1322 m_size = length;
1323 m_type = type;
1324 m_encoding = encoding;
1325 m_ptr = std::move(data);
1326}
1327
1332{
1333 auto s = std::stringstream(std::ios_base::in | std::ios_base::out | std::ios_base::binary);
1334 auto writer = BinaryWriter(&s);
1335 try {
1336 s.exceptions(std::ios_base::failbit | std::ios_base::badbit);
1337 writer.writeLengthPrefixedString(value.user);
1338 writer.writeFloat64LE(value.rating);
1339 writer.writeUInt64LE(value.playCounter);
1340 writer.writeUInt64LE(static_cast<std::uint64_t>(value.scale));
1341 auto size = static_cast<std::size_t>(s.tellp());
1342 auto ptr = std::make_unique<char[]>(size);
1343 s.read(ptr.get(), s.tellp());
1344 assignData(std::move(ptr), size, TagDataType::Popularity);
1345 } catch (const std::ios_base::failure &) {
1346 throw ConversionException("Unable to serialize specified Popularity");
1347 }
1348}
1349
1353void TagValue::stripBom(const char *&text, size_t &length, TagTextEncoding encoding)
1354{
1355 switch (encoding) {
1357 if ((length >= 3) && (BE::toUInt24(text) == 0x00EFBBBF)) {
1358 text += 3;
1359 length -= 3;
1360 }
1361 break;
1363 if ((length >= 2) && (LE::toInt<std::uint16_t>(text) == 0xFEFF)) {
1364 text += 2;
1365 length -= 2;
1366 }
1367 break;
1369 if ((length >= 2) && (BE::toInt<std::uint16_t>(text) == 0xFEFF)) {
1370 text += 2;
1371 length -= 2;
1372 }
1373 break;
1374 default:;
1375 }
1376}
1377
1382void TagValue::ensureHostByteOrder(u16string &u16str, TagTextEncoding currentEncoding)
1383{
1384 if (currentEncoding !=
1385#if defined(CONVERSION_UTILITIES_BYTE_ORDER_LITTLE_ENDIAN)
1387#elif defined(CONVERSION_UTILITIES_BYTE_ORDER_BIG_ENDIAN)
1389#else
1390#error "Host byte order not supported"
1391#endif
1392 ) {
1393 for (auto &c : u16str) {
1394 c = swapOrder(static_cast<std::uint16_t>(c));
1395 }
1396 }
1397}
1398
1402bool TagValue::compareData(const char *data1, std::size_t size1, const char *data2, std::size_t size2, bool ignoreCase)
1403{
1404 if (size1 != size2) {
1405 return false;
1406 }
1407 if (!size1) {
1408 return true;
1409 }
1410 if (ignoreCase) {
1411 for (auto i1 = data1, i2 = data2, end = data1 + size1; i1 != end; ++i1, ++i2) {
1412 if (CaseInsensitiveCharComparer::toLower(static_cast<unsigned char>(*i1))
1413 != CaseInsensitiveCharComparer::toLower(static_cast<unsigned char>(*i2))) {
1414 return false;
1415 }
1416 }
1417 } else {
1418 for (auto i1 = data1, i2 = data2, end = data1 + size1; i1 != end; ++i1, ++i2) {
1419 if (*i1 != *i2) {
1420 return false;
1421 }
1422 }
1423 }
1424 return true;
1425}
1426
1433{
1434 static TagValue emptyTagValue;
1435 return emptyTagValue;
1436}
1437
1451{
1452 if (scale == targetScale) {
1453 return true;
1454 }
1455
1456 // convert to generic scale first
1457 double genericRating;
1458 switch (scale) {
1460 genericRating = rating;
1461 break;
1463 genericRating = rating / (5.0 / 4.0) + 1.0;
1464 break;
1465 case TagType::Id3v2Tag:
1466 genericRating = rating < 1.0 ? 0.0 : ((rating - 1.0) / (254.0 / 4.0) + 1.0);
1467 break;
1470 genericRating = rating / 20.0;
1471 break;
1472 default:
1473 return false;
1474 }
1475
1476 // convert from the generic scale to the target scale
1477 switch (targetScale) {
1479 rating = genericRating;
1480 break;
1482 rating = (genericRating - 1.0) * (5.0 / 4.0);
1483 break;
1484 case TagType::Id3v2Tag:
1485 rating = genericRating < 1.0 ? 0.0 : ((genericRating - 1.0) * (254.0 / 4.0) + 1.0);
1486 break;
1489 rating = genericRating * 20.0;
1490 break;
1491 default:
1492 return false;
1493 }
1494
1495 scale = targetScale;
1496 return true;
1497}
1498
1503std::string Popularity::toString() const
1504{
1505 return isEmpty() ? std::string()
1506 : ((user.empty() && !playCounter) ? numberToString(rating) : (user % '|' % numberToString(rating) % '|' + playCounter));
1507}
1508
1516{
1517 return fromString(str, TagType::Unspecified);
1518}
1519
1527{
1528 const auto parts = splitStringSimple<std::vector<std::string_view>>(str, "|");
1529 auto res = Popularity({ .scale = scale });
1530 if (parts.empty()) {
1531 return res;
1532 } else if (parts.size() > 3) {
1533 throw ConversionException("Wrong format, expected \"rating\" or \"user|rating|play-counter\"");
1534 }
1535 // treat a single number as rating
1536 if (parts.size() == 1) {
1537 try {
1538 res.rating = stringToNumber<decltype(res.rating)>(parts.front());
1539 return res;
1540 } catch (const ConversionException &) {
1541 }
1542 }
1543 // otherwise, read user, rating and play counter
1544 res.user = parts.front();
1545 if (parts.size() > 1) {
1546 res.rating = stringToNumber<decltype(res.rating)>(parts[1]);
1547 }
1548 if (parts.size() > 2) {
1549 res.playCounter = stringToNumber<decltype(res.playCounter)>(parts[2]);
1550 }
1551 return res;
1552}
1553
1554} // 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...
StringType toString() const
Returns the string representation of the current PositionInSet.
The TagValue class wraps values of different types.
Definition tagvalue.h:147
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:754
void clearMetadata()
Wipes assigned meta data.
Definition tagvalue.cpp:500
void assignText(const char *text, std::size_t textSize, TagTextEncoding textEncoding=TagTextEncoding::Latin1, TagTextEncoding convertTo=TagTextEncoding::Unspecified)
Assigns a copy of the given text.
void assignInteger(int value)
Assigns the given integer value.
bool compareTo(const TagValue &other, TagValueComparisionFlags options=TagValueComparisionFlags::None) const
Returns whether both instances are equal.
Definition tagvalue.cpp:354
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.
CppUtilities::DateTime toDateTime() const
Converts the value of the current TagValue object to its equivalent DateTime representation (using th...
Definition tagvalue.cpp:763
std::string toDisplayString() const
Returns a "display string" for the specified value.
Definition tagvalue.cpp:519
CppUtilities::DateTimeExpression toDateTimeExpression() const
Converts the value of the current TagValue object to its equivalent DateTimeExpression representation...
Definition tagvalue.cpp:799
TagTextEncoding dataEncoding() const
Returns the data encoding.
Definition tagvalue.h:718
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:582
std::int32_t toInteger() const
Converts the value of the current TagValue object to its equivalent integer representation.
Definition tagvalue.cpp:540
PositionInSet toPositionInSet() const
Converts the value of the current TagValue object to its equivalent PositionInSet representation.
Definition tagvalue.cpp:675
TagDataType type() const
Returns the type of the assigned value.
Definition tagvalue.h:433
void convertDataEncodingForTag(const Tag *tag)
Ensures the encoding of the currently assigned text value is supported by the specified tag.
Definition tagvalue.cpp:963
void assignPopularity(const Popularity &value)
Assigns the specified popularity value.
void assignUnsignedInteger(std::uint64_t value)
Assigns the given unsigned integer value.
Popularity toPopularity() const
Converts the value of the current TagValue object to its equivalent Popularity representation.
Definition tagvalue.cpp:841
~TagValue()
Destroys the TagValue.
Definition tagvalue.cpp:231
std::string_view data() const
Returns the currently assigned raw data.
Definition tagvalue.h:546
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:463
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:901
void convertDataEncoding(TagTextEncoding encoding)
Converts the currently assigned text value to the specified encoding.
Definition tagvalue.cpp:921
TagTextEncoding descriptionEncoding() const
Returns the description encoding.
Definition tagvalue.h:728
TagValue & operator=(const TagValue &other)
Assigns the value of another TagValue to the current instance.
Definition tagvalue.cpp:288
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:450
static const TagValue & empty()
Returns a default-constructed TagValue where TagValue::isNull() and TagValue::isEmpty() both return t...
static void stripBom(const char *&text, std::size_t &length, TagTextEncoding encoding)
Strips the byte order mask from the specified text.
bool isEmpty() const
Returns whether no or an empty value is assigned.
Definition tagvalue.h:490
void convertDescriptionEncoding(TagTextEncoding encoding)
Converts the assigned description to use the specified encoding.
Definition tagvalue.cpp:973
int toStandardGenreIndex() const
Converts the value of the current TagValue object to its equivalent standard genre index.
Definition tagvalue.cpp:625
CppUtilities::TimeSpan toTimeSpan() const
Converts the value of the current TagValue object to its equivalent TimeSpan representation.
Definition tagvalue.cpp:724
TagValue()
Constructs an empty TagValue.
Definition tagvalue.cpp:156
The Tag class is used to store, read and write tag information.
Definition tag.h:166
virtual TagTextEncoding proposedTextEncoding() const
Returns the proposed text encoding.
Definition tag.h:219
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:224
Contains all classes and functions of the TagInfo library.
Definition aaccodebook.h:10
TAG_PARSER_EXPORT std::string_view tagDataTypeString(TagDataType dataType)
Returns the string representation of the specified dataType.
Definition tagvalue.cpp:31
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:64
TagValueComparisionFlags
The TagValueComparisionOption enum specifies options for TagValue::compareTo().
Definition tagvalue.h:139
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)
The Popularity class contains a value for ID3v2's "Popularimeter" field.
Definition tagvalue.h:72
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...
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.
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 ...
The TagValuePrivate struct contains private fields of the TagValue class.
Definition tagvalue.cpp:26