Tag Parser 11.5.1
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
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";
52 return "date time expression";
53 default:
54 return "undefined";
55 }
56}
57
61pair<const char *, float> encodingParameter(TagTextEncoding tagTextEncoding)
62{
63 switch (tagTextEncoding) {
65 return make_pair("ISO-8859-1", 1.0f);
67 return make_pair("UTF-8", 1.0f);
69 return make_pair("UTF-16LE", 2.0f);
71 return make_pair("UTF-16BE", 2.0f);
72 default:
73 return make_pair(nullptr, 0.0f);
74 }
75}
76
133 : m_size(other.m_size)
134 , m_desc(other.m_desc)
135 , m_mimeType(other.m_mimeType)
136 , m_locale(other.m_locale)
137 , m_type(other.m_type)
138 , m_encoding(other.m_encoding)
139 , m_descEncoding(other.m_descEncoding)
140 , m_flags(TagValueFlags::None)
141{
142 if (!other.isEmpty()) {
143 m_ptr = make_unique<char[]>(m_size);
144 std::copy(other.m_ptr.get(), other.m_ptr.get() + other.m_size, m_ptr.get());
145 }
146}
147
152{
153 if (this == &other) {
154 return *this;
155 }
156 m_size = other.m_size;
157 m_type = other.m_type;
158 m_desc = other.m_desc;
159 m_mimeType = other.m_mimeType;
160 m_locale = other.m_locale;
161 m_flags = other.m_flags;
162 m_encoding = other.m_encoding;
163 m_descEncoding = other.m_descEncoding;
164 if (other.isEmpty()) {
165 m_ptr.reset();
166 } else {
167 m_ptr = make_unique<char[]>(m_size);
168 std::copy(other.m_ptr.get(), other.m_ptr.get() + other.m_size, m_ptr.get());
169 }
170 return *this;
171}
172
174TagTextEncoding pickUtfEncoding(TagTextEncoding encoding1, TagTextEncoding encoding2)
175{
176 switch (encoding1) {
180 return encoding1;
181 default:
182 switch (encoding2) {
186 return encoding2;
187 default:;
188 }
189 }
191}
193
216{
217 // check whether meta-data is equal (except description)
219 // check meta-data which always uses UTF-8 (everything but description)
220 if (m_mimeType != other.m_mimeType || m_locale != other.m_locale || m_flags != other.m_flags) {
221 return false;
222 }
223
224 // check description which might use different encodings
225 if (m_descEncoding == other.m_descEncoding || m_descEncoding == TagTextEncoding::Unspecified
226 || other.m_descEncoding == TagTextEncoding::Unspecified || m_desc.empty() || other.m_desc.empty()) {
227 if (!compareData(m_desc, other.m_desc, options & TagValueComparisionFlags::CaseInsensitive)) {
228 return false;
229 }
230 } else {
231 const auto utfEncodingToUse = pickUtfEncoding(m_descEncoding, other.m_descEncoding);
232 StringData str1, str2;
233 const char *data1, *data2;
234 size_t size1, size2;
235 if (m_descEncoding != utfEncodingToUse) {
236 const auto inputParameter = encodingParameter(m_descEncoding), outputParameter = encodingParameter(utfEncodingToUse);
237 str1 = convertString(
238 inputParameter.first, outputParameter.first, m_desc.data(), m_desc.size(), outputParameter.second / inputParameter.second);
239 data1 = str1.first.get();
240 size1 = str1.second;
241 } else {
242 data1 = m_desc.data();
243 size1 = m_desc.size();
244 }
245 if (other.m_descEncoding != utfEncodingToUse) {
246 const auto inputParameter = encodingParameter(other.m_descEncoding), outputParameter = encodingParameter(utfEncodingToUse);
247 str2 = convertString(inputParameter.first, outputParameter.first, other.m_desc.data(), other.m_desc.size(),
248 outputParameter.second / inputParameter.second);
249 data2 = str2.first.get();
250 size2 = str2.second;
251 } else {
252 data2 = other.m_desc.data();
253 size2 = other.m_desc.size();
254 }
255 if (!compareData(data1, size1, data2, size2, options & TagValueComparisionFlags::CaseInsensitive)) {
256 return false;
257 }
258 }
259 }
260
261 try {
262 // check for equality if both types are identical
263 if (m_type == other.m_type) {
264 switch (m_type) {
265 case TagDataType::Text: {
266 // compare raw data directly if the encoding is the same
267 if (m_size != other.m_size && m_encoding == other.m_encoding) {
268 return false;
269 }
270 if (m_encoding == other.m_encoding || m_encoding == TagTextEncoding::Unspecified
271 || other.m_encoding == TagTextEncoding::Unspecified) {
273 }
274
275 // compare UTF-8 or UTF-16 representation of strings avoiding unnecessary conversions
276 const auto utfEncodingToUse = pickUtfEncoding(m_encoding, other.m_encoding);
277 string str1, str2;
278 const char *data1, *data2;
279 size_t size1, size2;
280 if (m_encoding != utfEncodingToUse) {
281 str1 = toString(utfEncodingToUse);
282 data1 = str1.data();
283 size1 = str1.size();
284 } else {
285 data1 = m_ptr.get();
286 size1 = m_size;
287 }
288 if (other.m_encoding != utfEncodingToUse) {
289 str2 = other.toString(utfEncodingToUse);
290 data2 = str2.data();
291 size2 = str2.size();
292 } else {
293 data2 = other.m_ptr.get();
294 size2 = other.m_size;
295 }
296 return compareData(data1, size1, data2, size2, options & TagValueComparisionFlags::CaseInsensitive);
297 }
299 return toPositionInSet() == other.toPositionInSet();
301 return toStandardGenreIndex() == other.toStandardGenreIndex();
303 return toTimeSpan() == other.toTimeSpan();
305 return toDateTime() == other.toDateTime();
307 return toDateTimeExpression() == other.toDateTimeExpression();
311 return compareData(other);
312 default:;
313 }
314 }
315
316 // do not attempt implicit conversions for certain types
317 // TODO: Maybe it would actually make sense for some of these types (at least when the other type is
318 // string)?
319 for (const auto dataType : { m_type, other.m_type }) {
320 switch (dataType) {
327 return false;
328 default:;
329 }
330 }
331
332 // handle types where an implicit conversion to the specific type can be done
333 if (m_type == TagDataType::Integer || other.m_type == TagDataType::Integer) {
334 return toInteger() == other.toInteger();
335 } else if (m_type == TagDataType::UnsignedInteger || other.m_type == TagDataType::UnsignedInteger) {
336 return toUnsignedInteger() == other.toUnsignedInteger();
337 } else if (m_type == TagDataType::Popularity || other.m_type == TagDataType::Popularity) {
339 const auto lhs = toPopularity(), rhs = other.toPopularity();
340 return lhs.rating == rhs.rating && lhs.playCounter == rhs.playCounter && lhs.scale == rhs.scale
341 && compareData(lhs.user, rhs.user, true);
342 } else {
343 return toPopularity() == other.toPopularity();
344 }
345 }
346
347 // handle other types where an implicit conversion to string can be done by comparing the string representation
348 return compareData(toString(), other.toString(m_encoding), options & TagValueComparisionFlags::CaseInsensitive);
349
350 } catch (const ConversionException &) {
351 return false;
352 }
353}
354
362{
363 m_desc.clear();
364 m_mimeType.clear();
365 m_locale.clear();
366 m_flags = TagValueFlags::None;
367 m_encoding = TagTextEncoding::Latin1;
368 m_descEncoding = TagTextEncoding::Latin1;
369 m_type = TagDataType::Undefined;
370}
371
381{
382 switch (m_type) {
386 return std::string(tagDataTypeString(m_type));
387 default:
388 try {
389 return toString(TagTextEncoding::Utf8);
390 } catch (const ConversionException &e) {
391 return argsToString("invalid ", tagDataTypeString(m_type), ':', ' ', e.what());
392 }
393 }
394}
395
401std::int32_t TagValue::toInteger() const
402{
403 if (isEmpty()) {
404 return 0;
405 }
406 switch (m_type) {
408 switch (m_encoding) {
411 auto u16str = u16string(reinterpret_cast<char16_t *>(m_ptr.get()), m_size / 2);
412 ensureHostByteOrder(u16str, m_encoding);
413 return stringToNumber<std::int32_t>(u16str);
414 }
415 default:
416 return bufferToNumber<std::int32_t>(m_ptr.get(), m_size);
417 }
419 if (m_size == sizeof(PositionInSet)) {
420 return *reinterpret_cast<std::int32_t *>(m_ptr.get());
421 }
422 throw ConversionException("Can not convert assigned data to integer because the data size is not appropriate.");
425 if (m_size == sizeof(std::int32_t)) {
426 return *reinterpret_cast<std::int32_t *>(m_ptr.get());
427 }
428 throw ConversionException("Can not convert assigned data to integer because the data size is not appropriate.");
430 return static_cast<std::int32_t>(toPopularity().rating);
432 const auto unsignedInteger = toUnsignedInteger();
433 if (unsignedInteger > std::numeric_limits<std::int32_t>::max()) {
434 throw ConversionException(argsToString("Unsigned integer too big for conversion to integer."));
435 }
436 return static_cast<std::int32_t>(unsignedInteger);
437 }
438 default:
439 throw ConversionException(argsToString("Can not convert ", tagDataTypeString(m_type), " to integer."));
440 }
441}
442
443std::uint64_t TagValue::toUnsignedInteger() const
444{
445 if (isEmpty()) {
446 return 0;
447 }
448 switch (m_type) {
450 switch (m_encoding) {
453 auto u16str = u16string(reinterpret_cast<char16_t *>(m_ptr.get()), m_size / 2);
454 ensureHostByteOrder(u16str, m_encoding);
455 return stringToNumber<std::uint64_t>(u16str);
456 }
457 default:
458 return bufferToNumber<std::uint64_t>(m_ptr.get(), m_size);
459 }
463 const auto integer = toInteger();
464 if (integer < 0) {
465 throw ConversionException(argsToString("Can not convert negative value to unsigned integer."));
466 }
467 return static_cast<std::uint64_t>(integer);
468 }
470 return static_cast<std::uint64_t>(toPopularity().rating);
472 if (m_size == sizeof(std::uint64_t)) {
473 return *reinterpret_cast<std::uint64_t *>(m_ptr.get());
474 }
475 throw ConversionException("Can not convert assigned data to unsigned integer because the data size is not appropriate.");
476 default:
477 throw ConversionException(argsToString("Can not convert ", tagDataTypeString(m_type), " to integer."));
478 }
479}
480
487{
488 if (isEmpty()) {
490 }
491 int index = 0;
492 switch (m_type) {
493 case TagDataType::Text: {
494 try {
495 index = toInteger();
496 } catch (const ConversionException &) {
498 if (m_encoding == TagTextEncoding::Latin1) {
499 // no need to convert Latin-1 to UTF-8 (makes no difference in case of genre strings)
501 }
502 index = Id3Genres::indexFromString(toString(encoding));
503 }
504 break;
505 }
509 if (m_size == sizeof(std::int32_t)) {
510 index = static_cast<int>(*reinterpret_cast<std::int32_t *>(m_ptr.get()));
511 } else if (m_size == sizeof(std::uint64_t)) {
512 const auto unsignedInt = *reinterpret_cast<std::uint64_t *>(m_ptr.get());
513 if (unsignedInt <= std::numeric_limits<int>::max()) {
514 index = static_cast<int>(unsignedInt);
515 } else {
516 index = Id3Genres::genreCount();
517 }
518 } else {
519 throw ConversionException("The assigned index/integer is of unappropriate size.");
520 }
521 break;
522 default:
523 throw ConversionException(argsToString("Can not convert ", tagDataTypeString(m_type), " to genre index."));
524 }
526 throw ConversionException("The assigned number is not a valid standard genre index.");
527 }
528 return index;
529}
530
537{
538 if (isEmpty()) {
539 return PositionInSet();
540 }
541 switch (m_type) {
543 switch (m_encoding) {
546 auto u16str = u16string(reinterpret_cast<char16_t *>(m_ptr.get()), m_size / 2);
547 ensureHostByteOrder(u16str, m_encoding);
548 return PositionInSet(u16str);
549 }
550 default:
551 return PositionInSet(string(m_ptr.get(), m_size));
552 }
555 switch (m_size) {
556 case sizeof(std::int32_t):
557 return PositionInSet(*(reinterpret_cast<std::int32_t *>(m_ptr.get())));
558 case 2 * sizeof(std::int32_t):
559 return PositionInSet(
560 *(reinterpret_cast<std::int32_t *>(m_ptr.get())), *(reinterpret_cast<std::int32_t *>(m_ptr.get() + sizeof(std::int32_t))));
561 default:
562 throw ConversionException("The size of the assigned data is not appropriate.");
563 }
565 switch (m_size) {
566 case sizeof(std::uint64_t): {
567 const auto track = *(reinterpret_cast<std::uint64_t *>(m_ptr.get()));
568 if (track < std::numeric_limits<std::int32_t>::max()) {
569 return PositionInSet(static_cast<std::int32_t>(track));
570 }
571 }
572 default:;
573 }
574 throw ConversionException("The size of the assigned data is not appropriate.");
575 default:
576 throw ConversionException(argsToString("Can not convert ", tagDataTypeString(m_type), " to position in set."));
577 }
578}
579
586{
587 if (isEmpty()) {
588 return TimeSpan();
589 }
590 switch (m_type) {
592 return TimeSpan::fromString(toString(m_encoding == TagTextEncoding::Utf8 ? TagTextEncoding::Utf8 : TagTextEncoding::Latin1));
595 switch (m_size) {
596 case sizeof(std::int32_t):
597 return TimeSpan(*(reinterpret_cast<std::int32_t *>(m_ptr.get())));
598 case sizeof(std::int64_t):
599 return TimeSpan(*(reinterpret_cast<std::int64_t *>(m_ptr.get())));
600 default:
601 throw ConversionException("The size of the assigned data is not appropriate for conversion to time span.");
602 }
604 switch (m_size) {
605 case sizeof(std::uint64_t): {
606 const auto ticks = *(reinterpret_cast<std::uint64_t *>(m_ptr.get()));
607 if (ticks < std::numeric_limits<std::int64_t>::max()) {
608 return TimeSpan(static_cast<std::int64_t>(ticks));
609 }
610 }
611 default:;
612 }
613 throw ConversionException("The size of the assigned data is not appropriate.");
614 default:
615 throw ConversionException(argsToString("Can not convert ", tagDataTypeString(m_type), " to time span."));
616 }
617}
618
625{
626 if (isEmpty()) {
627 return DateTime();
628 }
629 switch (m_type) {
630 case TagDataType::Text: {
632 try {
633 return DateTime::fromIsoStringGmt(str.data());
634 } catch (const ConversionException &) {
635 return DateTime::fromString(str);
636 }
637 }
641 if (m_size == sizeof(std::int32_t)) {
642 return DateTime(*(reinterpret_cast<std::uint32_t *>(m_ptr.get())));
643 } else if (m_size == sizeof(std::uint64_t)) {
644 return DateTime(*(reinterpret_cast<std::uint64_t *>(m_ptr.get())));
645 } else {
646 throw ConversionException("The size of the assigned data is not appropriate for conversion to date time.");
647 }
649 return toDateTimeExpression().gmt();
650 default:
651 throw ConversionException(argsToString("Can not convert ", tagDataTypeString(m_type), " to date time."));
652 }
653}
654
660CppUtilities::DateTimeExpression TagParser::TagValue::toDateTimeExpression() const
661{
662 if (isEmpty()) {
663 return DateTimeExpression();
664 }
665 switch (m_type) {
666 case TagDataType::Text: {
667 const auto str = toString(m_encoding == TagTextEncoding::Utf8 ? TagTextEncoding::Utf8 : TagTextEncoding::Latin1);
668 try {
669 return DateTimeExpression::fromIsoString(str.data());
670 } catch (const ConversionException &) {
671 return DateTimeExpression::fromString(str.data());
672 }
673 }
677 return DateTimeExpression{ .value = toDateTime(), .delta = TimeSpan(), .parts = DateTimeParts::DateTime };
679 if (m_size == sizeof(DateTimeExpression)) {
680 return *reinterpret_cast<DateTimeExpression *>(m_ptr.get());
681 } else {
682 throw ConversionException("The size of the assigned data is not appropriate for conversion to date time expression.");
683 }
684 default:
685 throw ConversionException(argsToString("Can not convert ", tagDataTypeString(m_type), " to date time."));
686 }
687}
688
703{
704 auto popularity = Popularity();
705 if (isEmpty()) {
706 return popularity;
707 }
708 switch (m_type) {
710 popularity = Popularity::fromString(std::string_view(toString(TagTextEncoding::Utf8)));
711 break;
713 popularity.rating = static_cast<double>(toInteger());
714 break;
716 auto s = std::stringstream(std::ios_base::in | std::ios_base::out | std::ios_base::binary);
717 auto reader = BinaryReader(&s);
718 try {
719 s.exceptions(std::ios_base::failbit | std::ios_base::badbit);
720 s.rdbuf()->pubsetbuf(m_ptr.get(), static_cast<std::streamsize>(m_size));
721 popularity.user = reader.readLengthPrefixedString();
722 popularity.rating = reader.readFloat64LE();
723 popularity.playCounter = reader.readUInt64LE();
724 popularity.scale = static_cast<TagType>(reader.readUInt64LE());
725 } catch (const std::ios_base::failure &) {
726 throw ConversionException(argsToString("Assigned popularity is invalid"));
727 }
728 break;
729 }
731 popularity.rating = static_cast<double>(toUnsignedInteger());
732 break;
733 default:
734 throw ConversionException(argsToString("Can not convert ", tagDataTypeString(m_type), " to date time."));
735 }
736 return popularity;
737}
738
759{
760 auto popularity = toPopularity();
761 if (m_type == TagDataType::Text) {
762 popularity.scale = scale;
763 } else if (!popularity.scaleTo(scale)) {
764 throw ConversionException(argsToString("Assigned popularity cannot be scaled accordingly"));
765 }
766 return popularity;
767}
768
779{
780 if (m_encoding == encoding) {
781 return;
782 }
783 if (type() == TagDataType::Text) {
784 StringData encodedData;
785 switch (encoding) {
787 // use pre-defined methods when encoding to UTF-8
788 switch (dataEncoding()) {
790 encodedData = convertLatin1ToUtf8(m_ptr.get(), m_size);
791 break;
793 encodedData = convertUtf16LEToUtf8(m_ptr.get(), m_size);
794 break;
796 encodedData = convertUtf16BEToUtf8(m_ptr.get(), m_size);
797 break;
798 default:;
799 }
800 break;
801 default: {
802 // otherwise, determine input and output parameter to use general covertString method
803 const auto inputParameter = encodingParameter(dataEncoding());
804 const auto outputParameter = encodingParameter(encoding);
805 encodedData
806 = convertString(inputParameter.first, outputParameter.first, m_ptr.get(), m_size, outputParameter.second / inputParameter.second);
807 }
808 }
809 // can't just move the encoded data because it needs to be deleted with free
810 m_ptr = make_unique<char[]>(m_size = encodedData.second);
811 copy(encodedData.first.get(), encodedData.first.get() + encodedData.second, m_ptr.get());
812 }
813 m_encoding = encoding;
814}
815
821{
824 }
825}
826
831{
832 if (encoding == m_descEncoding) {
833 return;
834 }
835 if (m_desc.empty()) {
836 m_descEncoding = encoding;
837 return;
838 }
839 StringData encodedData;
840 switch (encoding) {
842 // use pre-defined methods when encoding to UTF-8
843 switch (descriptionEncoding()) {
845 encodedData = convertLatin1ToUtf8(m_desc.data(), m_desc.size());
846 break;
848 encodedData = convertUtf16LEToUtf8(m_desc.data(), m_desc.size());
849 break;
851 encodedData = convertUtf16BEToUtf8(m_desc.data(), m_desc.size());
852 break;
853 default:;
854 }
855 break;
856 default: {
857 // otherwise, determine input and output parameter to use general covertString method
858 const auto inputParameter = encodingParameter(m_descEncoding);
859 const auto outputParameter = encodingParameter(encoding);
860 encodedData = convertString(
861 inputParameter.first, outputParameter.first, m_desc.data(), m_desc.size(), outputParameter.second / inputParameter.second);
862 }
863 }
864 m_desc.assign(encodedData.first.get(), encodedData.second);
865 m_descEncoding = encoding;
866}
867
881void TagValue::toString(string &result, TagTextEncoding encoding) const
882{
883 if (isEmpty()) {
884 result.clear();
885 return;
886 }
887
888 switch (m_type) {
891 result.assign(m_ptr.get(), m_size);
892 } else {
893 StringData encodedData;
894 switch (encoding) {
896 // use pre-defined methods when encoding to UTF-8
897 switch (dataEncoding()) {
899 encodedData = convertLatin1ToUtf8(m_ptr.get(), m_size);
900 break;
902 encodedData = convertUtf16LEToUtf8(m_ptr.get(), m_size);
903 break;
905 encodedData = convertUtf16BEToUtf8(m_ptr.get(), m_size);
906 break;
907 default:;
908 }
909 break;
910 default: {
911 // otherwise, determine input and output parameter to use general covertString method
912 const auto inputParameter = encodingParameter(dataEncoding());
913 const auto outputParameter = encodingParameter(encoding);
914 encodedData
915 = convertString(inputParameter.first, outputParameter.first, m_ptr.get(), m_size, outputParameter.second / inputParameter.second);
916 }
917 }
918 result.assign(encodedData.first.get(), encodedData.second);
919 }
920 return;
922 result = numberToString(toInteger());
923 break;
925 result = toPositionInSet().toString();
926 break;
928 const auto genreIndex = toInteger();
929 if (Id3Genres::isEmptyGenre(genreIndex)) {
930 result.clear();
931 } else if (const auto genreName = Id3Genres::stringFromIndex(genreIndex); !genreName.empty()) {
932 result.assign(genreName);
933 } else {
934 throw ConversionException("No string representation for the assigned standard genre index available.");
935 }
936 break;
937 }
939 result = toTimeSpan().toString();
940 break;
942 result = toDateTime().toIsoString();
943 break;
945 result = toPopularity().toString();
946 break;
948 result = numberToString(toUnsignedInteger());
949 break;
951 result = toDateTimeExpression().toIsoString();
952 break;
953 default:
954 throw ConversionException(argsToString("Can not convert ", tagDataTypeString(m_type), " to string."));
955 }
957 auto encodedData = encoding == TagTextEncoding::Utf16LittleEndian ? convertUtf8ToUtf16LE(result.data(), result.size())
958 : convertUtf8ToUtf16BE(result.data(), result.size());
959 result.assign(encodedData.first.get(), encodedData.second);
960 }
961}
962
973void TagValue::toWString(std::u16string &result, TagTextEncoding encoding) const
974{
975 if (isEmpty()) {
976 result.clear();
977 return;
978 }
979
980 string regularStrRes;
981 switch (m_type) {
983 if (encoding == TagTextEncoding::Unspecified || encoding == dataEncoding()) {
984 result.assign(reinterpret_cast<const char16_t *>(m_ptr.get()), m_size / sizeof(char16_t));
985 } else {
986 StringData encodedData;
987 switch (encoding) {
989 // use pre-defined methods when encoding to UTF-8
990 switch (dataEncoding()) {
992 encodedData = convertLatin1ToUtf8(m_ptr.get(), m_size);
993 break;
995 encodedData = convertUtf16LEToUtf8(m_ptr.get(), m_size);
996 break;
998 encodedData = convertUtf16BEToUtf8(m_ptr.get(), m_size);
999 break;
1000 default:;
1001 }
1002 break;
1003 default: {
1004 // otherwise, determine input and output parameter to use general covertString method
1005 const auto inputParameter = encodingParameter(dataEncoding());
1006 const auto outputParameter = encodingParameter(encoding);
1007 encodedData
1008 = convertString(inputParameter.first, outputParameter.first, m_ptr.get(), m_size, outputParameter.second / inputParameter.second);
1009 }
1010 }
1011 result.assign(reinterpret_cast<const char16_t *>(encodedData.first.get()), encodedData.second / sizeof(char16_t));
1012 }
1013 return;
1015 regularStrRes = numberToString(toInteger());
1016 break;
1018 regularStrRes = toPositionInSet().toString();
1019 break;
1021 const auto genreIndex = toInteger();
1022 if (Id3Genres::isEmptyGenre(genreIndex)) {
1023 regularStrRes.clear();
1024 } else if (const auto genreName = Id3Genres::stringFromIndex(genreIndex); !genreName.empty()) {
1025 regularStrRes.assign(genreName);
1026 } else {
1027 throw ConversionException("No string representation for the assigned standard genre index available.");
1028 }
1029 break;
1030 }
1032 regularStrRes = toTimeSpan().toString();
1033 break;
1035 regularStrRes = toDateTime().toString(DateTimeOutputFormat::IsoOmittingDefaultComponents);
1036 break;
1038 regularStrRes = toPopularity().toString();
1039 break;
1041 regularStrRes = numberToString(toUnsignedInteger());
1042 break;
1044 regularStrRes = toDateTimeExpression().toIsoString();
1045 break;
1046 default:
1047 throw ConversionException(argsToString("Can not convert ", tagDataTypeString(m_type), " to string."));
1048 }
1050 auto encodedData = encoding == TagTextEncoding::Utf16LittleEndian ? convertUtf8ToUtf16LE(regularStrRes.data(), result.size())
1051 : convertUtf8ToUtf16BE(regularStrRes.data(), result.size());
1052 result.assign(reinterpret_cast<const char16_t *>(encodedData.first.get()), encodedData.second / sizeof(const char16_t));
1053 }
1054}
1055
1066void TagValue::assignText(const char *text, std::size_t textSize, TagTextEncoding textEncoding, TagTextEncoding convertTo)
1067{
1068 m_type = TagDataType::Text;
1069 m_encoding = convertTo == TagTextEncoding::Unspecified ? textEncoding : convertTo;
1070
1071 stripBom(text, textSize, textEncoding);
1072 if (!textSize) {
1073 m_size = 0;
1074 m_ptr.reset();
1075 return;
1076 }
1077
1078 if (convertTo == TagTextEncoding::Unspecified || textEncoding == convertTo) {
1079 m_ptr = make_unique<char[]>(m_size = textSize);
1080 copy(text, text + textSize, m_ptr.get());
1081 return;
1082 }
1083
1084 StringData encodedData;
1085 switch (textEncoding) {
1087 // use pre-defined methods when encoding to UTF-8
1088 switch (convertTo) {
1090 encodedData = convertUtf8ToLatin1(text, textSize);
1091 break;
1093 encodedData = convertUtf8ToUtf16LE(text, textSize);
1094 break;
1096 encodedData = convertUtf8ToUtf16BE(text, textSize);
1097 break;
1098 default:;
1099 }
1100 break;
1101 default: {
1102 // otherwise, determine input and output parameter to use general covertString method
1103 const auto inputParameter = encodingParameter(textEncoding);
1104 const auto outputParameter = encodingParameter(convertTo);
1105 encodedData = convertString(inputParameter.first, outputParameter.first, text, textSize, outputParameter.second / inputParameter.second);
1106 }
1107 }
1108 // can't just move the encoded data because it needs to be deleted with free
1109 m_ptr = make_unique<char[]>(m_size = encodedData.second);
1110 copy(encodedData.first.get(), encodedData.first.get() + encodedData.second, m_ptr.get());
1111}
1112
1118{
1119 m_size = sizeof(value);
1120 m_ptr = make_unique<char[]>(m_size);
1121 std::copy(reinterpret_cast<const char *>(&value), reinterpret_cast<const char *>(&value) + m_size, m_ptr.get());
1122 m_type = TagDataType::Integer;
1123 m_encoding = TagTextEncoding::Latin1;
1124}
1125
1130void TagValue::assignUnsignedInteger(std::uint64_t value)
1131{
1132 m_size = sizeof(value);
1133 m_ptr = make_unique<char[]>(m_size);
1134 std::copy(reinterpret_cast<const char *>(&value), reinterpret_cast<const char *>(&value) + m_size, m_ptr.get());
1136 m_encoding = TagTextEncoding::Latin1;
1137}
1138
1147void TagValue::assignData(const char *data, size_t length, TagDataType type, TagTextEncoding encoding)
1148{
1149 if (type == TagDataType::Text) {
1150 stripBom(data, length, encoding);
1151 }
1152 if (length > m_size) {
1153 m_ptr = make_unique<char[]>(length);
1154 }
1155 if (length) {
1156 std::copy(data, data + length, m_ptr.get());
1157 } else {
1158 m_ptr.reset();
1159 }
1160 m_size = length;
1161 m_type = type;
1162 m_encoding = encoding;
1163}
1164
1177void TagValue::assignData(unique_ptr<char[]> &&data, size_t length, TagDataType type, TagTextEncoding encoding)
1178{
1179 m_size = length;
1180 m_type = type;
1181 m_encoding = encoding;
1182 m_ptr = move(data);
1183}
1184
1189{
1190 auto s = std::stringstream(std::ios_base::in | std::ios_base::out | std::ios_base::binary);
1191 auto writer = BinaryWriter(&s);
1192 try {
1193 s.exceptions(std::ios_base::failbit | std::ios_base::badbit);
1194 writer.writeLengthPrefixedString(value.user);
1195 writer.writeFloat64LE(value.rating);
1196 writer.writeUInt64LE(value.playCounter);
1197 writer.writeUInt64LE(static_cast<std::uint64_t>(value.scale));
1198 auto size = static_cast<std::size_t>(s.tellp());
1199 auto ptr = std::make_unique<char[]>(size);
1200 s.read(ptr.get(), s.tellp());
1201 assignData(std::move(ptr), size, TagDataType::Popularity);
1202 } catch (const std::ios_base::failure &) {
1203 throw ConversionException("Unable to serialize specified Popularity");
1204 }
1205}
1206
1210void TagValue::stripBom(const char *&text, size_t &length, TagTextEncoding encoding)
1211{
1212 switch (encoding) {
1214 if ((length >= 3) && (BE::toUInt24(text) == 0x00EFBBBF)) {
1215 text += 3;
1216 length -= 3;
1217 }
1218 break;
1220 if ((length >= 2) && (LE::toUInt16(text) == 0xFEFF)) {
1221 text += 2;
1222 length -= 2;
1223 }
1224 break;
1226 if ((length >= 2) && (BE::toUInt16(text) == 0xFEFF)) {
1227 text += 2;
1228 length -= 2;
1229 }
1230 break;
1231 default:;
1232 }
1233}
1234
1239void TagValue::ensureHostByteOrder(u16string &u16str, TagTextEncoding currentEncoding)
1240{
1241 if (currentEncoding !=
1242#if defined(CONVERSION_UTILITIES_BYTE_ORDER_LITTLE_ENDIAN)
1244#elif defined(CONVERSION_UTILITIES_BYTE_ORDER_BIG_ENDIAN)
1246#else
1247#error "Host byte order not supported"
1248#endif
1249 ) {
1250 for (auto &c : u16str) {
1251 c = swapOrder(static_cast<std::uint16_t>(c));
1252 }
1253 }
1254}
1255
1259bool TagValue::compareData(const char *data1, std::size_t size1, const char *data2, std::size_t size2, bool ignoreCase)
1260{
1261 if (size1 != size2) {
1262 return false;
1263 }
1264 if (!size1) {
1265 return true;
1266 }
1267 if (ignoreCase) {
1268 for (auto i1 = data1, i2 = data2, end = data1 + size1; i1 != end; ++i1, ++i2) {
1269 if (CaseInsensitiveCharComparer::toLower(static_cast<unsigned char>(*i1))
1270 != CaseInsensitiveCharComparer::toLower(static_cast<unsigned char>(*i2))) {
1271 return false;
1272 }
1273 }
1274 } else {
1275 for (auto i1 = data1, i2 = data2, end = data1 + size1; i1 != end; ++i1, ++i2) {
1276 if (*i1 != *i2) {
1277 return false;
1278 }
1279 }
1280 }
1281 return true;
1282}
1283
1290{
1291 static TagValue emptyTagValue;
1292 return emptyTagValue;
1293}
1294
1308{
1309 if (scale == targetScale) {
1310 return true;
1311 }
1312
1313 // convert to generic scale first
1314 double genericRating;
1315 switch (scale) {
1317 genericRating = rating;
1318 break;
1320 genericRating = rating / (5.0 / 4.0) + 1.0;
1321 break;
1322 case TagType::Id3v2Tag:
1323 genericRating = rating < 1.0 ? 0.0 : ((rating - 1.0) / (254.0 / 4.0) + 1.0);
1324 break;
1327 genericRating = rating / 20.0;
1328 break;
1329 default:
1330 return false;
1331 }
1332
1333 // convert from the generic scale to the target scale
1334 switch (targetScale) {
1336 rating = genericRating;
1337 break;
1339 rating = (genericRating - 1.0) * (5.0 / 4.0);
1340 break;
1341 case TagType::Id3v2Tag:
1342 rating = genericRating < 1.0 ? 0.0 : ((genericRating - 1.0) * (254.0 / 4.0) + 1.0);
1343 break;
1346 rating = genericRating * 20.0;
1347 break;
1348 default:
1349 return false;
1350 }
1351
1352 scale = targetScale;
1353 return true;
1354}
1355
1360std::string Popularity::toString() const
1361{
1362 return isEmpty() ? std::string()
1363 : ((user.empty() && !playCounter) ? numberToString(rating) : (user % '|' % numberToString(rating) % '|' + playCounter));
1364}
1365
1373{
1374 return fromString(str, TagType::Unspecified);
1375}
1376
1384{
1385 const auto parts = splitStringSimple<std::vector<std::string_view>>(str, "|");
1386 auto res = Popularity({ .scale = scale });
1387 if (parts.empty()) {
1388 return res;
1389 } else if (parts.size() > 3) {
1390 throw ConversionException("Wrong format, expected \"rating\" or \"user|rating|play-counter\"");
1391 }
1392 // treat a single number as rating
1393 if (parts.size() == 1) {
1394 try {
1395 res.rating = stringToNumber<decltype(res.rating)>(parts.front());
1396 return res;
1397 } catch (const ConversionException &) {
1398 }
1399 }
1400 // otherwise, read user, rating and play counter
1401 res.user = parts.front();
1402 if (parts.size() > 1) {
1403 res.rating = stringToNumber<decltype(res.rating)>(parts[1]);
1404 }
1405 if (parts.size() > 2) {
1406 res.playCounter = stringToNumber<decltype(res.playCounter)>(parts[2]);
1407 }
1408 return res;
1409}
1410
1411} // 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:143
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:881
void clearMetadata()
Wipes assigned meta data.
Definition: tagvalue.cpp:361
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:1066
void assignInteger(int value)
Assigns the given integer value.
Definition: tagvalue.cpp:1117
bool compareTo(const TagValue &other, TagValueComparisionFlags options=TagValueComparisionFlags::None) const
Returns whether both instances are equal.
Definition: tagvalue.cpp:215
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:1239
CppUtilities::DateTime toDateTime() const
Converts the value of the current TagValue object to its equivalent DateTime representation (using th...
Definition: tagvalue.cpp:624
std::string toDisplayString() const
Returns a "display string" for the specified value.
Definition: tagvalue.cpp:380
CppUtilities::DateTimeExpression toDateTimeExpression() const
Converts the value of the current TagValue object to its equivalent DateTimeExpression representation...
Definition: tagvalue.cpp:660
TagTextEncoding dataEncoding() const
Returns the data encoding.
Definition: tagvalue.h:845
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:443
std::int32_t toInteger() const
Converts the value of the current TagValue object to its equivalent integer representation.
Definition: tagvalue.cpp:401
PositionInSet toPositionInSet() const
Converts the value of the current TagValue object to its equivalent PositionInSet representation.
Definition: tagvalue.cpp:536
TagDataType type() const
Returns the type of the assigned value.
Definition: tagvalue.h:560
void convertDataEncodingForTag(const Tag *tag)
Ensures the encoding of the currently assigned text value is supported by the specified tag.
Definition: tagvalue.cpp:820
void assignPopularity(const Popularity &value)
Assigns the specified popularity value.
Definition: tagvalue.cpp:1188
void assignUnsignedInteger(std::uint64_t value)
Assigns the given unsigned integer value.
Definition: tagvalue.cpp:1130
Popularity toPopularity() const
Converts the value of the current TagValue object to its equivalent Popularity representation.
Definition: tagvalue.cpp:702
std::string_view data() const
Returns the currently assigned raw data.
Definition: tagvalue.h:673
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:590
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:758
void convertDataEncoding(TagTextEncoding encoding)
Converts the currently assigned text value to the specified encoding.
Definition: tagvalue.cpp:778
TagTextEncoding descriptionEncoding() const
Returns the description encoding.
Definition: tagvalue.h:855
TagValue & operator=(const TagValue &other)
Assigns the value of another TagValue to the current instance.
Definition: tagvalue.cpp:151
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:577
static const TagValue & empty()
Returns a default-constructed TagValue where TagValue::isNull() and TagValue::isEmpty() both return t...
Definition: tagvalue.cpp:1289
static void stripBom(const char *&text, std::size_t &length, TagTextEncoding encoding)
Strips the byte order mask from the specified text.
Definition: tagvalue.cpp:1210
bool isEmpty() const
Returns whether no or an empty value is assigned.
Definition: tagvalue.h:617
void convertDescriptionEncoding(TagTextEncoding encoding)
Converts the assigned description to use the specified encoding.
Definition: tagvalue.cpp:830
int toStandardGenreIndex() const
Converts the value of the current TagValue object to its equivalent standard genre index.
Definition: tagvalue.cpp:486
CppUtilities::TimeSpan toTimeSpan() const
Converts the value of the current TagValue object to its equivalent TimeSpan representation.
Definition: tagvalue.cpp:585
TagValue()
Constructs an empty TagValue.
Definition: tagvalue.h:268
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:61
TagValueComparisionFlags
The TagValueComparisionOption enum specifies options for TagValue::compareTo().
Definition: tagvalue.h:137
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...
Definition: tagvalue.cpp:1372
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:1307
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:1360