Tag Parser 11.3.0
C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags
tagvalue.cpp
Go to the documentation of this file.
1#include "./tagvalue.h"
2
4#include "./tag.h"
5
6#include "./id3/id3genres.h"
7
8#include <c++utilities/conversion/binaryconversion.h>
9#include <c++utilities/conversion/conversionexception.h>
10#include <c++utilities/conversion/stringbuilder.h>
11#include <c++utilities/conversion/stringconversion.h>
12#include <c++utilities/io/binaryreader.h>
13#include <c++utilities/io/binarywriter.h>
14
15#include <algorithm>
16#include <cstring>
17#include <sstream>
18#include <utility>
19
20using namespace std;
21using namespace CppUtilities;
22
23namespace TagParser {
24
28std::string_view tagDataTypeString(TagDataType dataType)
29{
30 switch (dataType) {
32 return "text";
34 return "integer";
36 return "position in set";
38 return "genre index";
40 return "time span";
42 return "date time";
44 return "picture";
46 return "binary";
48 return "popularity";
50 return "unsigned integer";
51 default:
52 return "undefined";
53 }
54}
55
59pair<const char *, float> encodingParameter(TagTextEncoding tagTextEncoding)
60{
61 switch (tagTextEncoding) {
63 return make_pair("ISO-8859-1", 1.0f);
65 return make_pair("UTF-8", 1.0f);
67 return make_pair("UTF-16LE", 2.0f);
69 return make_pair("UTF-16BE", 2.0f);
70 default:
71 return make_pair(nullptr, 0.0f);
72 }
73}
74
104 : m_size(other.m_size)
105 , m_desc(other.m_desc)
106 , m_mimeType(other.m_mimeType)
107 , m_locale(other.m_locale)
108 , m_type(other.m_type)
109 , m_encoding(other.m_encoding)
110 , m_descEncoding(other.m_descEncoding)
111 , m_flags(TagValueFlags::None)
112{
113 if (!other.isEmpty()) {
114 m_ptr = make_unique<char[]>(m_size);
115 std::copy(other.m_ptr.get(), other.m_ptr.get() + other.m_size, m_ptr.get());
116 }
117}
118
123{
124 if (this == &other) {
125 return *this;
126 }
127 m_size = other.m_size;
128 m_type = other.m_type;
129 m_desc = other.m_desc;
130 m_mimeType = other.m_mimeType;
131 m_locale = other.m_locale;
132 m_flags = other.m_flags;
133 m_encoding = other.m_encoding;
134 m_descEncoding = other.m_descEncoding;
135 if (other.isEmpty()) {
136 m_ptr.reset();
137 } else {
138 m_ptr = make_unique<char[]>(m_size);
139 std::copy(other.m_ptr.get(), other.m_ptr.get() + other.m_size, m_ptr.get());
140 }
141 return *this;
142}
143
145TagTextEncoding pickUtfEncoding(TagTextEncoding encoding1, TagTextEncoding encoding2)
146{
147 switch (encoding1) {
151 return encoding1;
152 default:
153 switch (encoding2) {
157 return encoding2;
158 default:;
159 }
160 }
162}
164
189{
190 // check whether meta-data is equal (except description)
192 // check meta-data which always uses UTF-8 (everything but description)
193 if (m_mimeType != other.m_mimeType || m_locale != other.m_locale || m_flags != other.m_flags) {
194 return false;
195 }
196
197 // check description which might use different encodings
198 if (m_descEncoding == other.m_descEncoding || m_descEncoding == TagTextEncoding::Unspecified
199 || other.m_descEncoding == TagTextEncoding::Unspecified || m_desc.empty() || other.m_desc.empty()) {
200 if (!compareData(m_desc, other.m_desc, options & TagValueComparisionFlags::CaseInsensitive)) {
201 return false;
202 }
203 } else {
204 const auto utfEncodingToUse = pickUtfEncoding(m_descEncoding, other.m_descEncoding);
205 StringData str1, str2;
206 const char *data1, *data2;
207 size_t size1, size2;
208 if (m_descEncoding != utfEncodingToUse) {
209 const auto inputParameter = encodingParameter(m_descEncoding), outputParameter = encodingParameter(utfEncodingToUse);
210 str1 = convertString(
211 inputParameter.first, outputParameter.first, m_desc.data(), m_desc.size(), outputParameter.second / inputParameter.second);
212 data1 = str1.first.get();
213 size1 = str1.second;
214 } else {
215 data1 = m_desc.data();
216 size1 = m_desc.size();
217 }
218 if (other.m_descEncoding != utfEncodingToUse) {
219 const auto inputParameter = encodingParameter(other.m_descEncoding), outputParameter = encodingParameter(utfEncodingToUse);
220 str2 = convertString(inputParameter.first, outputParameter.first, other.m_desc.data(), other.m_desc.size(),
221 outputParameter.second / inputParameter.second);
222 data2 = str2.first.get();
223 size2 = str2.second;
224 } else {
225 data2 = other.m_desc.data();
226 size2 = other.m_desc.size();
227 }
228 if (!compareData(data1, size1, data2, size2, options & TagValueComparisionFlags::CaseInsensitive)) {
229 return false;
230 }
231 }
232 }
233
234 try {
235 // check for equality if both types are identical
236 if (m_type == other.m_type) {
237 switch (m_type) {
238 case TagDataType::Text: {
239 // compare raw data directly if the encoding is the same
240 if (m_size != other.m_size && m_encoding == other.m_encoding) {
241 return false;
242 }
243 if (m_encoding == other.m_encoding || m_encoding == TagTextEncoding::Unspecified
244 || other.m_encoding == TagTextEncoding::Unspecified) {
246 }
247
248 // compare UTF-8 or UTF-16 representation of strings avoiding unnecessary conversions
249 const auto utfEncodingToUse = pickUtfEncoding(m_encoding, other.m_encoding);
250 string str1, str2;
251 const char *data1, *data2;
252 size_t size1, size2;
253 if (m_encoding != utfEncodingToUse) {
254 str1 = toString(utfEncodingToUse);
255 data1 = str1.data();
256 size1 = str1.size();
257 } else {
258 data1 = m_ptr.get();
259 size1 = m_size;
260 }
261 if (other.m_encoding != utfEncodingToUse) {
262 str2 = other.toString(utfEncodingToUse);
263 data2 = str2.data();
264 size2 = str2.size();
265 } else {
266 data2 = other.m_ptr.get();
267 size2 = other.m_size;
268 }
269 return compareData(data1, size1, data2, size2, options & TagValueComparisionFlags::CaseInsensitive);
270 }
272 return toPositionInSet() == other.toPositionInSet();
274 return toStandardGenreIndex() == other.toStandardGenreIndex();
276 return toTimeSpan() == other.toTimeSpan();
278 return toDateTime() == other.toDateTime();
282 return compareData(other);
283 default:;
284 }
285 }
286
287 // do not attempt implicit conversions for certain types
288 // TODO: Maybe it would actually make sense for some of these types (at least when the other type is
289 // string)?
290 for (const auto dataType : { m_type, other.m_type }) {
291 switch (dataType) {
297 return false;
298 default:;
299 }
300 }
301
302 // handle types where an implicit conversion to the specific type can be done
303 if (m_type == TagDataType::Integer || other.m_type == TagDataType::Integer) {
304 return toInteger() == other.toInteger();
305 } else if (m_type == TagDataType::UnsignedInteger || other.m_type == TagDataType::UnsignedInteger) {
306 return toUnsignedInteger() == other.toUnsignedInteger();
307 } else if (m_type == TagDataType::Popularity || other.m_type == TagDataType::Popularity) {
309 const auto lhs = toPopularity(), rhs = other.toPopularity();
310 return lhs.rating == rhs.rating && lhs.playCounter == rhs.playCounter && lhs.scale == rhs.scale
311 && compareData(lhs.user, rhs.user, true);
312 } else {
313 return toPopularity() == other.toPopularity();
314 }
315 }
316
317 // handle other types where an implicit conversion to string can be done by comparing the string representation
318 return compareData(toString(), other.toString(m_encoding), options & TagValueComparisionFlags::CaseInsensitive);
319
320 } catch (const ConversionException &) {
321 return false;
322 }
323}
324
332{
333 m_desc.clear();
334 m_mimeType.clear();
335 m_locale.clear();
336 m_flags = TagValueFlags::None;
337 m_encoding = TagTextEncoding::Latin1;
338 m_descEncoding = TagTextEncoding::Latin1;
339 m_type = TagDataType::Undefined;
340}
341
351{
352 switch (m_type) {
356 return std::string(tagDataTypeString(m_type));
357 default:
358 try {
359 return toString(TagTextEncoding::Utf8);
360 } catch (const ConversionException &e) {
361 return argsToString("invalid ", tagDataTypeString(m_type), ':', ' ', e.what());
362 }
363 }
364}
365
371std::int32_t TagValue::toInteger() const
372{
373 if (isEmpty()) {
374 return 0;
375 }
376 switch (m_type) {
378 switch (m_encoding) {
381 auto u16str = u16string(reinterpret_cast<char16_t *>(m_ptr.get()), m_size / 2);
382 ensureHostByteOrder(u16str, m_encoding);
383 return stringToNumber<std::int32_t>(u16str);
384 }
385 default:
386 return bufferToNumber<std::int32_t>(m_ptr.get(), m_size);
387 }
389 if (m_size == sizeof(PositionInSet)) {
390 return *reinterpret_cast<std::int32_t *>(m_ptr.get());
391 }
392 throw ConversionException("Can not convert assigned data to integer because the data size is not appropriate.");
395 if (m_size == sizeof(std::int32_t)) {
396 return *reinterpret_cast<std::int32_t *>(m_ptr.get());
397 }
398 throw ConversionException("Can not convert assigned data to integer because the data size is not appropriate.");
400 return static_cast<std::int32_t>(toPopularity().rating);
402 const auto unsignedInteger = toUnsignedInteger();
403 if (unsignedInteger > std::numeric_limits<std::int32_t>::max()) {
404 throw ConversionException(argsToString("Unsigned integer too big for conversion to integer."));
405 }
406 return static_cast<std::int32_t>(unsignedInteger);
407 }
408 default:
409 throw ConversionException(argsToString("Can not convert ", tagDataTypeString(m_type), " to integer."));
410 }
411}
412
413std::uint64_t TagValue::toUnsignedInteger() const
414{
415 if (isEmpty()) {
416 return 0;
417 }
418 switch (m_type) {
420 switch (m_encoding) {
423 auto u16str = u16string(reinterpret_cast<char16_t *>(m_ptr.get()), m_size / 2);
424 ensureHostByteOrder(u16str, m_encoding);
425 return stringToNumber<std::uint64_t>(u16str);
426 }
427 default:
428 return bufferToNumber<std::uint64_t>(m_ptr.get(), m_size);
429 }
433 const auto integer = toInteger();
434 if (integer < 0) {
435 throw ConversionException(argsToString("Can not convert negative value to unsigned integer."));
436 }
437 return static_cast<std::uint64_t>(integer);
438 }
440 return static_cast<std::uint64_t>(toPopularity().rating);
442 if (m_size == sizeof(std::uint64_t)) {
443 return *reinterpret_cast<std::uint64_t *>(m_ptr.get());
444 }
445 throw ConversionException("Can not convert assigned data to unsigned integer because the data size is not appropriate.");
446 default:
447 throw ConversionException(argsToString("Can not convert ", tagDataTypeString(m_type), " to integer."));
448 }
449}
450
457{
458 if (isEmpty()) {
460 }
461 int index = 0;
462 switch (m_type) {
463 case TagDataType::Text: {
464 try {
465 index = toInteger();
466 } catch (const ConversionException &) {
468 if (m_encoding == TagTextEncoding::Latin1) {
469 // no need to convert Latin-1 to UTF-8 (makes no difference in case of genre strings)
471 }
472 index = Id3Genres::indexFromString(toString(encoding));
473 }
474 break;
475 }
479 if (m_size == sizeof(std::int32_t)) {
480 index = static_cast<int>(*reinterpret_cast<std::int32_t *>(m_ptr.get()));
481 } else if (m_size == sizeof(std::uint64_t)) {
482 const auto unsignedInt = *reinterpret_cast<std::uint64_t *>(m_ptr.get());
483 if (unsignedInt <= std::numeric_limits<int>::max()) {
484 index = static_cast<int>(unsignedInt);
485 } else {
486 index = Id3Genres::genreCount();
487 }
488 } else {
489 throw ConversionException("The assigned index/integer is of unappropriate size.");
490 }
491 break;
492 default:
493 throw ConversionException(argsToString("Can not convert ", tagDataTypeString(m_type), " to genre index."));
494 }
496 throw ConversionException("The assigned number is not a valid standard genre index.");
497 }
498 return index;
499}
500
507{
508 if (isEmpty()) {
509 return PositionInSet();
510 }
511 switch (m_type) {
513 switch (m_encoding) {
516 auto u16str = u16string(reinterpret_cast<char16_t *>(m_ptr.get()), m_size / 2);
517 ensureHostByteOrder(u16str, m_encoding);
518 return PositionInSet(u16str);
519 }
520 default:
521 return PositionInSet(string(m_ptr.get(), m_size));
522 }
525 switch (m_size) {
526 case sizeof(std::int32_t):
527 return PositionInSet(*(reinterpret_cast<std::int32_t *>(m_ptr.get())));
528 case 2 * sizeof(std::int32_t):
529 return PositionInSet(
530 *(reinterpret_cast<std::int32_t *>(m_ptr.get())), *(reinterpret_cast<std::int32_t *>(m_ptr.get() + sizeof(std::int32_t))));
531 default:
532 throw ConversionException("The size of the assigned data is not appropriate.");
533 }
535 switch (m_size) {
536 case sizeof(std::uint64_t): {
537 const auto track = *(reinterpret_cast<std::uint64_t *>(m_ptr.get()));
538 if (track < std::numeric_limits<std::int32_t>::max()) {
539 return PositionInSet(static_cast<std::int32_t>(track));
540 }
541 }
542 default:;
543 }
544 throw ConversionException("The size of the assigned data is not appropriate.");
545 default:
546 throw ConversionException(argsToString("Can not convert ", tagDataTypeString(m_type), " to position in set."));
547 }
548}
549
556{
557 if (isEmpty()) {
558 return TimeSpan();
559 }
560 switch (m_type) {
562 return TimeSpan::fromString(toString(m_encoding == TagTextEncoding::Utf8 ? TagTextEncoding::Utf8 : TagTextEncoding::Latin1));
565 switch (m_size) {
566 case sizeof(std::int32_t):
567 return TimeSpan(*(reinterpret_cast<std::int32_t *>(m_ptr.get())));
568 case sizeof(std::int64_t):
569 return TimeSpan(*(reinterpret_cast<std::int64_t *>(m_ptr.get())));
570 default:
571 throw ConversionException("The size of the assigned integer is not appropriate for conversion to time span.");
572 }
574 switch (m_size) {
575 case sizeof(std::uint64_t): {
576 const auto ticks = *(reinterpret_cast<std::uint64_t *>(m_ptr.get()));
577 if (ticks < std::numeric_limits<std::int64_t>::max()) {
578 return TimeSpan(static_cast<std::int64_t>(ticks));
579 }
580 }
581 default:;
582 }
583 throw ConversionException("The size of the assigned data is not appropriate.");
584 default:
585 throw ConversionException(argsToString("Can not convert ", tagDataTypeString(m_type), " to time span."));
586 }
587}
588
594DateTime TagValue::toDateTime() const
595{
596 if (isEmpty()) {
597 return DateTime();
598 }
599 switch (m_type) {
600 case TagDataType::Text: {
602 try {
603 return DateTime::fromIsoStringGmt(str.data());
604 } catch (const ConversionException &) {
605 return DateTime::fromString(str);
606 }
607 }
611 if (m_size == sizeof(std::int32_t)) {
612 return DateTime(*(reinterpret_cast<std::uint32_t *>(m_ptr.get())));
613 } else if (m_size == sizeof(std::uint64_t)) {
614 return DateTime(*(reinterpret_cast<std::uint64_t *>(m_ptr.get())));
615 } else {
616 throw ConversionException("The size of the assigned integer is not appropriate for conversion to date time.");
617 }
618 default:
619 throw ConversionException(argsToString("Can not convert ", tagDataTypeString(m_type), " to date time."));
620 }
621}
622
629{
630 auto popularity = Popularity();
631 if (isEmpty()) {
632 return popularity;
633 }
634 switch (m_type) {
636 popularity = Popularity::fromString(std::string_view(toString(TagTextEncoding::Utf8)));
637 break;
639 popularity.rating = static_cast<double>(toInteger());
640 break;
642 auto s = std::stringstream(std::ios_base::in | std::ios_base::out | std::ios_base::binary);
643 auto reader = BinaryReader(&s);
644 try {
645 s.exceptions(std::ios_base::failbit | std::ios_base::badbit);
646 s.rdbuf()->pubsetbuf(m_ptr.get(), static_cast<std::streamsize>(m_size));
647 popularity.user = reader.readLengthPrefixedString();
648 popularity.rating = reader.readFloat64LE();
649 popularity.playCounter = reader.readUInt64LE();
650 popularity.scale = static_cast<TagType>(reader.readUInt64LE());
651 } catch (const std::ios_base::failure &) {
652 throw ConversionException(argsToString("Assigned popularity is invalid"));
653 }
654 break;
655 }
657 popularity.rating = static_cast<double>(toUnsignedInteger());
658 break;
659 default:
660 throw ConversionException(argsToString("Can not convert ", tagDataTypeString(m_type), " to date time."));
661 }
662 return popularity;
663}
664
675{
676 if (m_encoding == encoding) {
677 return;
678 }
679 if (type() == TagDataType::Text) {
680 StringData encodedData;
681 switch (encoding) {
683 // use pre-defined methods when encoding to UTF-8
684 switch (dataEncoding()) {
686 encodedData = convertLatin1ToUtf8(m_ptr.get(), m_size);
687 break;
689 encodedData = convertUtf16LEToUtf8(m_ptr.get(), m_size);
690 break;
692 encodedData = convertUtf16BEToUtf8(m_ptr.get(), m_size);
693 break;
694 default:;
695 }
696 break;
697 default: {
698 // otherwise, determine input and output parameter to use general covertString method
699 const auto inputParameter = encodingParameter(dataEncoding());
700 const auto outputParameter = encodingParameter(encoding);
701 encodedData
702 = convertString(inputParameter.first, outputParameter.first, m_ptr.get(), m_size, outputParameter.second / inputParameter.second);
703 }
704 }
705 // can't just move the encoded data because it needs to be deleted with free
706 m_ptr = make_unique<char[]>(m_size = encodedData.second);
707 copy(encodedData.first.get(), encodedData.first.get() + encodedData.second, m_ptr.get());
708 }
709 m_encoding = encoding;
710}
711
717{
720 }
721}
722
727{
728 if (encoding == m_descEncoding) {
729 return;
730 }
731 if (m_desc.empty()) {
732 m_descEncoding = encoding;
733 return;
734 }
735 StringData encodedData;
736 switch (encoding) {
738 // use pre-defined methods when encoding to UTF-8
739 switch (descriptionEncoding()) {
741 encodedData = convertLatin1ToUtf8(m_desc.data(), m_desc.size());
742 break;
744 encodedData = convertUtf16LEToUtf8(m_desc.data(), m_desc.size());
745 break;
747 encodedData = convertUtf16BEToUtf8(m_desc.data(), m_desc.size());
748 break;
749 default:;
750 }
751 break;
752 default: {
753 // otherwise, determine input and output parameter to use general covertString method
754 const auto inputParameter = encodingParameter(m_descEncoding);
755 const auto outputParameter = encodingParameter(encoding);
756 encodedData = convertString(
757 inputParameter.first, outputParameter.first, m_desc.data(), m_desc.size(), outputParameter.second / inputParameter.second);
758 }
759 }
760 m_desc.assign(encodedData.first.get(), encodedData.second);
761 m_descEncoding = encoding;
762}
763
776void TagValue::toString(string &result, TagTextEncoding encoding) const
777{
778 if (isEmpty()) {
779 result.clear();
780 return;
781 }
782
783 switch (m_type) {
786 result.assign(m_ptr.get(), m_size);
787 } else {
788 StringData encodedData;
789 switch (encoding) {
791 // use pre-defined methods when encoding to UTF-8
792 switch (dataEncoding()) {
794 encodedData = convertLatin1ToUtf8(m_ptr.get(), m_size);
795 break;
797 encodedData = convertUtf16LEToUtf8(m_ptr.get(), m_size);
798 break;
800 encodedData = convertUtf16BEToUtf8(m_ptr.get(), m_size);
801 break;
802 default:;
803 }
804 break;
805 default: {
806 // otherwise, determine input and output parameter to use general covertString method
807 const auto inputParameter = encodingParameter(dataEncoding());
808 const auto outputParameter = encodingParameter(encoding);
809 encodedData
810 = convertString(inputParameter.first, outputParameter.first, m_ptr.get(), m_size, outputParameter.second / inputParameter.second);
811 }
812 }
813 result.assign(encodedData.first.get(), encodedData.second);
814 }
815 return;
817 result = numberToString(toInteger());
818 break;
820 result = toPositionInSet().toString();
821 break;
823 const auto genreIndex = toInteger();
824 if (Id3Genres::isEmptyGenre(genreIndex)) {
825 result.clear();
826 } else if (const auto genreName = Id3Genres::stringFromIndex(genreIndex); !genreName.empty()) {
827 result.assign(genreName);
828 } else {
829 throw ConversionException("No string representation for the assigned standard genre index available.");
830 }
831 break;
832 }
834 result = toTimeSpan().toString();
835 break;
837 result = toDateTime().toString(DateTimeOutputFormat::IsoOmittingDefaultComponents);
838 break;
840 result = toPopularity().toString();
841 break;
843 result = numberToString(toUnsignedInteger());
844 break;
845 default:
846 throw ConversionException(argsToString("Can not convert ", tagDataTypeString(m_type), " to string."));
847 }
849 auto encodedData = encoding == TagTextEncoding::Utf16LittleEndian ? convertUtf8ToUtf16LE(result.data(), result.size())
850 : convertUtf8ToUtf16BE(result.data(), result.size());
851 result.assign(encodedData.first.get(), encodedData.second);
852 }
853}
854
865void TagValue::toWString(std::u16string &result, TagTextEncoding encoding) const
866{
867 if (isEmpty()) {
868 result.clear();
869 return;
870 }
871
872 string regularStrRes;
873 switch (m_type) {
875 if (encoding == TagTextEncoding::Unspecified || encoding == dataEncoding()) {
876 result.assign(reinterpret_cast<const char16_t *>(m_ptr.get()), m_size / sizeof(char16_t));
877 } else {
878 StringData encodedData;
879 switch (encoding) {
881 // use pre-defined methods when encoding to UTF-8
882 switch (dataEncoding()) {
884 encodedData = convertLatin1ToUtf8(m_ptr.get(), m_size);
885 break;
887 encodedData = convertUtf16LEToUtf8(m_ptr.get(), m_size);
888 break;
890 encodedData = convertUtf16BEToUtf8(m_ptr.get(), m_size);
891 break;
892 default:;
893 }
894 break;
895 default: {
896 // otherwise, determine input and output parameter to use general covertString method
897 const auto inputParameter = encodingParameter(dataEncoding());
898 const auto outputParameter = encodingParameter(encoding);
899 encodedData
900 = convertString(inputParameter.first, outputParameter.first, m_ptr.get(), m_size, outputParameter.second / inputParameter.second);
901 }
902 }
903 result.assign(reinterpret_cast<const char16_t *>(encodedData.first.get()), encodedData.second / sizeof(char16_t));
904 }
905 return;
907 regularStrRes = numberToString(toInteger());
908 break;
910 regularStrRes = toPositionInSet().toString();
911 break;
913 const auto genreIndex = toInteger();
914 if (Id3Genres::isEmptyGenre(genreIndex)) {
915 regularStrRes.clear();
916 } else if (const auto genreName = Id3Genres::stringFromIndex(genreIndex); !genreName.empty()) {
917 regularStrRes.assign(genreName);
918 } else {
919 throw ConversionException("No string representation for the assigned standard genre index available.");
920 }
921 break;
922 }
924 regularStrRes = toTimeSpan().toString();
925 break;
927 regularStrRes = toDateTime().toString(DateTimeOutputFormat::IsoOmittingDefaultComponents);
928 break;
930 regularStrRes = toPopularity().toString();
931 break;
933 regularStrRes = numberToString(toUnsignedInteger());
934 break;
935 default:
936 throw ConversionException(argsToString("Can not convert ", tagDataTypeString(m_type), " to string."));
937 }
939 auto encodedData = encoding == TagTextEncoding::Utf16LittleEndian ? convertUtf8ToUtf16LE(regularStrRes.data(), result.size())
940 : convertUtf8ToUtf16BE(regularStrRes.data(), result.size());
941 result.assign(reinterpret_cast<const char16_t *>(encodedData.first.get()), encodedData.second / sizeof(const char16_t));
942 }
943}
944
955void TagValue::assignText(const char *text, std::size_t textSize, TagTextEncoding textEncoding, TagTextEncoding convertTo)
956{
957 m_type = TagDataType::Text;
958 m_encoding = convertTo == TagTextEncoding::Unspecified ? textEncoding : convertTo;
959
960 stripBom(text, textSize, textEncoding);
961 if (!textSize) {
962 m_size = 0;
963 m_ptr.reset();
964 return;
965 }
966
967 if (convertTo == TagTextEncoding::Unspecified || textEncoding == convertTo) {
968 m_ptr = make_unique<char[]>(m_size = textSize);
969 copy(text, text + textSize, m_ptr.get());
970 return;
971 }
972
973 StringData encodedData;
974 switch (textEncoding) {
976 // use pre-defined methods when encoding to UTF-8
977 switch (convertTo) {
979 encodedData = convertUtf8ToLatin1(text, textSize);
980 break;
982 encodedData = convertUtf8ToUtf16LE(text, textSize);
983 break;
985 encodedData = convertUtf8ToUtf16BE(text, textSize);
986 break;
987 default:;
988 }
989 break;
990 default: {
991 // otherwise, determine input and output parameter to use general covertString method
992 const auto inputParameter = encodingParameter(textEncoding);
993 const auto outputParameter = encodingParameter(convertTo);
994 encodedData = convertString(inputParameter.first, outputParameter.first, text, textSize, outputParameter.second / inputParameter.second);
995 }
996 }
997 // can't just move the encoded data because it needs to be deleted with free
998 m_ptr = make_unique<char[]>(m_size = encodedData.second);
999 copy(encodedData.first.get(), encodedData.first.get() + encodedData.second, m_ptr.get());
1000}
1001
1007{
1008 m_size = sizeof(value);
1009 m_ptr = make_unique<char[]>(m_size);
1010 std::copy(reinterpret_cast<const char *>(&value), reinterpret_cast<const char *>(&value) + m_size, m_ptr.get());
1011 m_type = TagDataType::Integer;
1012 m_encoding = TagTextEncoding::Latin1;
1013}
1014
1019void TagValue::assignUnsignedInteger(std::uint64_t value)
1020{
1021 m_size = sizeof(value);
1022 m_ptr = make_unique<char[]>(m_size);
1023 std::copy(reinterpret_cast<const char *>(&value), reinterpret_cast<const char *>(&value) + m_size, m_ptr.get());
1025 m_encoding = TagTextEncoding::Latin1;
1026}
1027
1036void TagValue::assignData(const char *data, size_t length, TagDataType type, TagTextEncoding encoding)
1037{
1038 if (type == TagDataType::Text) {
1039 stripBom(data, length, encoding);
1040 }
1041 if (length > m_size) {
1042 m_ptr = make_unique<char[]>(length);
1043 }
1044 if (length) {
1045 std::copy(data, data + length, m_ptr.get());
1046 } else {
1047 m_ptr.reset();
1048 }
1049 m_size = length;
1050 m_type = type;
1051 m_encoding = encoding;
1052}
1053
1066void TagValue::assignData(unique_ptr<char[]> &&data, size_t length, TagDataType type, TagTextEncoding encoding)
1067{
1068 m_size = length;
1069 m_type = type;
1070 m_encoding = encoding;
1071 m_ptr = move(data);
1072}
1073
1078{
1079 auto s = std::stringstream(std::ios_base::in | std::ios_base::out | std::ios_base::binary);
1080 auto writer = BinaryWriter(&s);
1081 try {
1082 s.exceptions(std::ios_base::failbit | std::ios_base::badbit);
1083 writer.writeLengthPrefixedString(value.user);
1084 writer.writeFloat64LE(value.rating);
1085 writer.writeUInt64LE(value.playCounter);
1086 writer.writeUInt64LE(static_cast<std::uint64_t>(value.scale));
1087 auto size = static_cast<std::size_t>(s.tellp());
1088 auto ptr = std::make_unique<char[]>(size);
1089 s.read(ptr.get(), s.tellp());
1090 assignData(std::move(ptr), size, TagDataType::Popularity);
1091 } catch (const std::ios_base::failure &) {
1092 throw ConversionException("Unable to serialize specified Popularity");
1093 }
1094}
1095
1099void TagValue::stripBom(const char *&text, size_t &length, TagTextEncoding encoding)
1100{
1101 switch (encoding) {
1103 if ((length >= 3) && (BE::toUInt24(text) == 0x00EFBBBF)) {
1104 text += 3;
1105 length -= 3;
1106 }
1107 break;
1109 if ((length >= 2) && (LE::toUInt16(text) == 0xFEFF)) {
1110 text += 2;
1111 length -= 2;
1112 }
1113 break;
1115 if ((length >= 2) && (BE::toUInt16(text) == 0xFEFF)) {
1116 text += 2;
1117 length -= 2;
1118 }
1119 break;
1120 default:;
1121 }
1122}
1123
1128void TagValue::ensureHostByteOrder(u16string &u16str, TagTextEncoding currentEncoding)
1129{
1130 if (currentEncoding !=
1131#if defined(CONVERSION_UTILITIES_BYTE_ORDER_LITTLE_ENDIAN)
1133#elif defined(CONVERSION_UTILITIES_BYTE_ORDER_BIG_ENDIAN)
1135#else
1136#error "Host byte order not supported"
1137#endif
1138 ) {
1139 for (auto &c : u16str) {
1140 c = swapOrder(static_cast<std::uint16_t>(c));
1141 }
1142 }
1143}
1144
1148bool TagValue::compareData(const char *data1, std::size_t size1, const char *data2, std::size_t size2, bool ignoreCase)
1149{
1150 if (size1 != size2) {
1151 return false;
1152 }
1153 if (!size1) {
1154 return true;
1155 }
1156 if (ignoreCase) {
1157 for (auto i1 = data1, i2 = data2, end = data1 + size1; i1 != end; ++i1, ++i2) {
1158 if (CaseInsensitiveCharComparer::toLower(static_cast<unsigned char>(*i1))
1159 != CaseInsensitiveCharComparer::toLower(static_cast<unsigned char>(*i2))) {
1160 return false;
1161 }
1162 }
1163 } else {
1164 for (auto i1 = data1, i2 = data2, end = data1 + size1; i1 != end; ++i1, ++i2) {
1165 if (*i1 != *i2) {
1166 return false;
1167 }
1168 }
1169 }
1170 return true;
1171}
1172
1179{
1180 static TagValue emptyTagValue;
1181 return emptyTagValue;
1182}
1183
1188std::string Popularity::toString() const
1189{
1190 return isEmpty() ? std::string()
1191 : ((user.empty() && !playCounter) ? numberToString(rating) : (user % '|' % numberToString(rating) % '|' + playCounter));
1192}
1193
1199{
1200 const auto parts = splitStringSimple<std::vector<std::string_view>>(str, "|");
1201 auto res = Popularity();
1202 if (parts.empty()) {
1203 return res;
1204 } else if (parts.size() > 3) {
1205 throw ConversionException("Wrong format, expected \"rating\" or \"user|rating|play-counter\"");
1206 }
1207 // treat a single number as rating
1208 if (parts.size() == 1) {
1209 try {
1210 res.rating = stringToNumber<decltype(res.rating)>(parts.front());
1211 return res;
1212 } catch (const ConversionException &) {
1213 }
1214 }
1215 // otherwise, read user, rating and play counter
1216 res.user = parts.front();
1217 if (parts.size() > 1) {
1218 res.rating = stringToNumber<decltype(res.rating)>(parts[1]);
1219 }
1220 if (parts.size() > 2) {
1221 res.playCounter = stringToNumber<decltype(res.playCounter)>(parts[2]);
1222 }
1223 return res;
1224}
1225
1226} // 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:130
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:848
void clearMetadata()
Wipes assigned meta data.
Definition: tagvalue.cpp:331
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:955
void assignInteger(int value)
Assigns the given integer value.
Definition: tagvalue.cpp:1006
bool compareTo(const TagValue &other, TagValueComparisionFlags options=TagValueComparisionFlags::None) const
Returns whether both instances are equal.
Definition: tagvalue.cpp:188
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:1128
CppUtilities::DateTime toDateTime() const
Converts the value of the current TagValue object to its equivalent DateTime representation.
Definition: tagvalue.cpp:594
std::string toDisplayString() const
Returns a "display string" for the specified value.
Definition: tagvalue.cpp:350
TagTextEncoding dataEncoding() const
Returns the data encoding.
Definition: tagvalue.h:812
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:413
std::int32_t toInteger() const
Converts the value of the current TagValue object to its equivalent integer representation.
Definition: tagvalue.cpp:371
PositionInSet toPositionInSet() const
Converts the value of the current TagValue object to its equivalent PositionInSet representation.
Definition: tagvalue.cpp:506
TagDataType type() const
Returns the type of the assigned value.
Definition: tagvalue.h:527
void convertDataEncodingForTag(const Tag *tag)
Ensures the encoding of the currently assigned text value is supported by the specified tag.
Definition: tagvalue.cpp:716
void assignPopularity(const Popularity &value)
Assigns the specified popularity value.
Definition: tagvalue.cpp:1077
void assignUnsignedInteger(std::uint64_t value)
Assigns the given unsigned integer value.
Definition: tagvalue.cpp:1019
Popularity toPopularity() const
Converts the value of the current TagValue object to its equivalent Popularity representation.
Definition: tagvalue.cpp:628
std::string_view data() const
Returns the currently assigned raw data.
Definition: tagvalue.h:640
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:557
void convertDataEncoding(TagTextEncoding encoding)
Converts the currently assigned text value to the specified encoding.
Definition: tagvalue.cpp:674
TagTextEncoding descriptionEncoding() const
Returns the description encoding.
Definition: tagvalue.h:822
TagValue & operator=(const TagValue &other)
Assigns the value of another TagValue to the current instance.
Definition: tagvalue.cpp:122
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:544
static const TagValue & empty()
Returns a default-constructed TagValue where TagValue::isNull() and TagValue::isEmpty() both return t...
Definition: tagvalue.cpp:1178
static void stripBom(const char *&text, std::size_t &length, TagTextEncoding encoding)
Strips the byte order mask from the specified text.
Definition: tagvalue.cpp:1099
bool isEmpty() const
Returns whether no or an empty value is assigned.
Definition: tagvalue.h:584
void convertDescriptionEncoding(TagTextEncoding encoding)
Converts the assigned description to use the specified encoding.
Definition: tagvalue.cpp:726
int toStandardGenreIndex() const
Converts the value of the current TagValue object to its equivalent standard genre index.
Definition: tagvalue.cpp:456
CppUtilities::TimeSpan toTimeSpan() const
Converts the value of the current TagValue object to its equivalent TimeSpan representation.
Definition: tagvalue.cpp:555
TagValue()
Constructs an empty TagValue.
Definition: tagvalue.h:251
The Tag class is used to store, read and write tag information.
Definition: tag.h:163
virtual TagTextEncoding proposedTextEncoding() const
Returns the proposed text encoding.
Definition: tag.h:215
virtual bool canEncodingBeUsed(TagTextEncoding encoding) const
Returns an indication whether the specified encoding can be used to provide string values for the tag...
Definition: tag.h:220
Contains all classes and functions of the TagInfo library.
Definition: aaccodebook.h:10
std::string_view tagDataTypeString(TagDataType dataType)
Returns the string representation of the specified dataType.
Definition: tagvalue.cpp:28
TagTextEncoding
Specifies the text encoding.
Definition: tagvalue.h:29
TagType
Specifies the tag type.
Definition: tagtype.h:11
pair< const char *, float > encodingParameter(TagTextEncoding tagTextEncoding)
Returns the encoding parameter (name of the character set and bytes per character) for the specified ...
Definition: tagvalue.cpp:59
TagValueComparisionFlags
The TagValueComparisionOption enum specifies options for TagValue::compareTo().
Definition: tagvalue.h:124
TagValueFlags
Specifies additional flags about the tag value.
Definition: tagvalue.h:43
TagDataType
Specifies the data type.
Definition: tagvalue.h:107
static constexpr unsigned char toLower(const unsigned char c)
std::string user
The user who gave the rating / played the file, e.g. identified by e-mail address.
Definition: tagvalue.h:74
bool isEmpty() const
Returns whether the Popularity is empty. The scale and zero-values don't count.
Definition: tagvalue.h:89
static Popularity fromString(std::string_view str)
Parses the popularity from str assuming the same format as toString() produces.
Definition: tagvalue.cpp:1198
std::uint64_t playCounter
Play counter specific to the user.
Definition: tagvalue.h:78
TagType scale
Specifies the scale used for rating by the tag defining that scale.
Definition: tagvalue.h:83
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:1188