Tag Parser 12.4.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
519std::string TagParser::TagValue::toDisplayString() const
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 unsigned 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.
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
CppUtilities::DateTimeExpression toDateTimeExpression() const
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.
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:237
virtual TagTextEncoding proposedTextEncoding() const
Returns the proposed text encoding.
Definition tag.h:232
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.
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