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>
36 return "position in set";
50 return "unsigned integer";
52 return "date time expression";
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);
73 return make_pair(
nullptr, 0.0f);
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)
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());
153 if (
this == &other) {
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;
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());
220 if (m_mimeType != other.m_mimeType || m_locale != other.m_locale || m_flags != other.m_flags) {
231 const auto utfEncodingToUse = pickUtfEncoding(m_descEncoding, other.m_descEncoding);
232 StringData str1, str2;
233 const char *data1, *data2;
235 if (m_descEncoding != utfEncodingToUse) {
237 str1 = convertString(
238 inputParameter.first, outputParameter.first, m_desc.data(), m_desc.size(), outputParameter.second / inputParameter.second);
239 data1 = str1.first.get();
242 data1 = m_desc.data();
243 size1 = m_desc.size();
245 if (other.m_descEncoding != 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();
252 data2 = other.m_desc.data();
253 size2 = other.m_desc.size();
263 if (m_type == other.m_type) {
267 if (m_size != other.m_size && m_encoding == other.m_encoding) {
276 const auto utfEncodingToUse = pickUtfEncoding(m_encoding, other.m_encoding);
278 const char *data1, *data2;
280 if (m_encoding != utfEncodingToUse) {
288 if (other.m_encoding != utfEncodingToUse) {
289 str2 = other.
toString(utfEncodingToUse);
293 data2 = other.m_ptr.get();
294 size2 = other.m_size;
319 for (
const auto dataType : { m_type, other.m_type }) {
340 return lhs.
rating == rhs.rating && lhs.playCounter == rhs.playCounter && lhs.scale == rhs.scale
350 }
catch (
const ConversionException &) {
390 }
catch (
const ConversionException &e) {
408 switch (m_encoding) {
411 auto u16str = u16string(
reinterpret_cast<char16_t *
>(m_ptr.get()), m_size / 2);
413 return stringToNumber<std::int32_t>(u16str);
416 return bufferToNumber<std::int32_t>(m_ptr.get(), m_size);
420 return *
reinterpret_cast<std::int32_t *
>(m_ptr.get());
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());
428 throw ConversionException(
"Can not convert assigned data to integer because the data size is not appropriate.");
433 if (unsignedInteger > std::numeric_limits<std::int32_t>::max()) {
434 throw ConversionException(argsToString(
"Unsigned integer too big for conversion to integer."));
436 return static_cast<std::int32_t
>(unsignedInteger);
439 throw ConversionException(argsToString(
"Can not convert ",
tagDataTypeString(m_type),
" to integer."));
450 switch (m_encoding) {
453 auto u16str = u16string(
reinterpret_cast<char16_t *
>(m_ptr.get()), m_size / 2);
455 return stringToNumber<std::uint64_t>(u16str);
458 return bufferToNumber<std::uint64_t>(m_ptr.get(), m_size);
465 throw ConversionException(argsToString(
"Can not convert negative value to unsigned integer."));
467 return static_cast<std::uint64_t
>(integer);
472 if (m_size ==
sizeof(std::uint64_t)) {
473 return *
reinterpret_cast<std::uint64_t *
>(m_ptr.get());
475 throw ConversionException(
"Can not convert assigned data to unsigned integer because the data size is not appropriate.");
477 throw ConversionException(argsToString(
"Can not convert ",
tagDataTypeString(m_type),
" to integer."));
496 }
catch (
const ConversionException &) {
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);
519 throw ConversionException(
"The assigned index/integer is of unappropriate size.");
523 throw ConversionException(argsToString(
"Can not convert ",
tagDataTypeString(m_type),
" to genre index."));
526 throw ConversionException(
"The assigned number is not a valid standard genre index.");
543 switch (m_encoding) {
546 auto u16str = u16string(
reinterpret_cast<char16_t *
>(m_ptr.get()), m_size / 2);
556 case sizeof(std::int32_t):
557 return PositionInSet(*(
reinterpret_cast<std::int32_t *
>(m_ptr.get())));
558 case 2 *
sizeof(std::int32_t):
560 *(
reinterpret_cast<std::int32_t *
>(m_ptr.get())), *(
reinterpret_cast<std::int32_t *
>(m_ptr.get() +
sizeof(std::int32_t))));
562 throw ConversionException(
"The size of the assigned data is not appropriate.");
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()) {
574 throw ConversionException(
"The size of the assigned data is not appropriate.");
576 throw ConversionException(argsToString(
"Can not convert ",
tagDataTypeString(m_type),
" to position in set."));
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())));
601 throw ConversionException(
"The size of the assigned data is not appropriate for conversion to time span.");
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));
613 throw ConversionException(
"The size of the assigned data is not appropriate.");
615 throw ConversionException(argsToString(
"Can not convert ",
tagDataTypeString(m_type),
" to time span."));
633 return DateTime::fromIsoStringGmt(str.data());
634 }
catch (
const ConversionException &) {
635 return DateTime::fromString(str);
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())));
646 throw ConversionException(
"The size of the assigned data is not appropriate for conversion to date time.");
651 throw ConversionException(argsToString(
"Can not convert ",
tagDataTypeString(m_type),
" to date time."));
669 return DateTimeExpression::fromIsoString(str.data());
670 }
catch (
const ConversionException &) {
671 return DateTimeExpression::fromString(str.data());
682 throw ConversionException(
"The size of the assigned data is not appropriate for conversion to date time expression.");
685 throw ConversionException(argsToString(
"Can not convert ",
tagDataTypeString(m_type),
" to date time."));
713 popularity.rating =
static_cast<double>(
toInteger());
716 auto s = std::stringstream(std::ios_base::in | std::ios_base::out | std::ios_base::binary);
717 auto reader = BinaryReader(&s);
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"));
734 throw ConversionException(argsToString(
"Can not convert ",
tagDataTypeString(m_type),
" to date time."));
762 popularity.scale = scale;
763 }
else if (!popularity.scaleTo(scale)) {
764 throw ConversionException(argsToString(
"Assigned popularity cannot be scaled accordingly"));
780 if (m_encoding == encoding) {
784 StringData encodedData;
790 encodedData = convertLatin1ToUtf8(m_ptr.get(), m_size);
793 encodedData = convertUtf16LEToUtf8(m_ptr.get(), m_size);
796 encodedData = convertUtf16BEToUtf8(m_ptr.get(), m_size);
806 = convertString(inputParameter.first, outputParameter.first, m_ptr.get(), m_size, outputParameter.second / inputParameter.second);
810 m_ptr = make_unique<char[]>(m_size = encodedData.second);
811 copy(encodedData.first.get(), encodedData.first.get() + encodedData.second, m_ptr.get());
813 m_encoding = encoding;
832 if (encoding == m_descEncoding) {
835 if (m_desc.empty()) {
836 m_descEncoding = encoding;
839 StringData encodedData;
845 encodedData = convertLatin1ToUtf8(m_desc.data(), m_desc.size());
848 encodedData = convertUtf16LEToUtf8(m_desc.data(), m_desc.size());
851 encodedData = convertUtf16BEToUtf8(m_desc.data(), m_desc.size());
860 encodedData = convertString(
861 inputParameter.first, outputParameter.first, m_desc.data(), m_desc.size(), outputParameter.second / inputParameter.second);
864 m_desc.assign(encodedData.first.get(), encodedData.second);
865 m_descEncoding = encoding;
891 result.assign(m_ptr.get(), m_size);
893 StringData encodedData;
899 encodedData = convertLatin1ToUtf8(m_ptr.get(), m_size);
902 encodedData = convertUtf16LEToUtf8(m_ptr.get(), m_size);
905 encodedData = convertUtf16BEToUtf8(m_ptr.get(), m_size);
915 = convertString(inputParameter.first, outputParameter.first, m_ptr.get(), m_size, outputParameter.second / inputParameter.second);
918 result.assign(encodedData.first.get(), encodedData.second);
932 result.assign(genreName);
934 throw ConversionException(
"No string representation for the assigned standard genre index available.");
954 throw ConversionException(argsToString(
"Can not convert ",
tagDataTypeString(m_type),
" to string."));
958 : convertUtf8ToUtf16BE(result.data(), result.size());
959 result.assign(encodedData.first.get(), encodedData.second);
980 string regularStrRes;
984 result.assign(
reinterpret_cast<const char16_t *
>(m_ptr.get()), m_size /
sizeof(
char16_t));
986 StringData encodedData;
992 encodedData = convertLatin1ToUtf8(m_ptr.get(), m_size);
995 encodedData = convertUtf16LEToUtf8(m_ptr.get(), m_size);
998 encodedData = convertUtf16BEToUtf8(m_ptr.get(), m_size);
1008 = convertString(inputParameter.first, outputParameter.first, m_ptr.get(), m_size, outputParameter.second / inputParameter.second);
1011 result.assign(
reinterpret_cast<const char16_t *
>(encodedData.first.get()), encodedData.second /
sizeof(
char16_t));
1015 regularStrRes = numberToString(
toInteger());
1023 regularStrRes.clear();
1025 regularStrRes.assign(genreName);
1027 throw ConversionException(
"No string representation for the assigned standard genre index available.");
1035 regularStrRes =
toDateTime().toString(DateTimeOutputFormat::IsoOmittingDefaultComponents);
1047 throw ConversionException(argsToString(
"Can not convert ",
tagDataTypeString(m_type),
" to string."));
1051 : convertUtf8ToUtf16BE(regularStrRes.data(), result.size());
1052 result.assign(
reinterpret_cast<const char16_t *
>(encodedData.first.get()), encodedData.second /
sizeof(
const char16_t));
1071 stripBom(text, textSize, textEncoding);
1079 m_ptr = make_unique<char[]>(m_size = textSize);
1080 copy(text, text + textSize, m_ptr.get());
1084 StringData encodedData;
1085 switch (textEncoding) {
1088 switch (convertTo) {
1090 encodedData = convertUtf8ToLatin1(text, textSize);
1093 encodedData = convertUtf8ToUtf16LE(text, textSize);
1096 encodedData = convertUtf8ToUtf16BE(text, textSize);
1105 encodedData = convertString(inputParameter.first, outputParameter.first, text, textSize, outputParameter.second / inputParameter.second);
1109 m_ptr = make_unique<char[]>(m_size = encodedData.second);
1110 copy(encodedData.first.get(), encodedData.first.get() + encodedData.second, m_ptr.get());
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());
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());
1152 if (length > m_size) {
1153 m_ptr = make_unique<char[]>(length);
1156 std::copy(
data,
data + length, m_ptr.get());
1162 m_encoding = encoding;
1181 m_encoding = encoding;
1190 auto s = std::stringstream(std::ios_base::in | std::ios_base::out | std::ios_base::binary);
1191 auto writer = BinaryWriter(&s);
1193 s.exceptions(std::ios_base::failbit | std::ios_base::badbit);
1194 writer.writeLengthPrefixedString(value.
user);
1195 writer.writeFloat64LE(value.
rating);
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());
1202 }
catch (
const std::ios_base::failure &) {
1203 throw ConversionException(
"Unable to serialize specified Popularity");
1214 if ((length >= 3) && (BE::toUInt24(text) == 0x00EFBBBF)) {
1220 if ((length >= 2) && (LE::toUInt16(text) == 0xFEFF)) {
1226 if ((length >= 2) && (BE::toUInt16(text) == 0xFEFF)) {
1241 if (currentEncoding !=
1242#
if defined(CONVERSION_UTILITIES_BYTE_ORDER_LITTLE_ENDIAN)
1244#elif defined(CONVERSION_UTILITIES_BYTE_ORDER_BIG_ENDIAN)
1247#error
"Host byte order not supported"
1250 for (
auto &c : u16str) {
1251 c = swapOrder(
static_cast<std::uint16_t
>(c));
1261 if (size1 != size2) {
1268 for (
auto i1 = data1, i2 = data2, end = data1 + size1; i1 != end; ++i1, ++i2) {
1275 for (
auto i1 = data1, i2 = data2, end = data1 + size1; i1 != end; ++i1, ++i2) {
1292 return emptyTagValue;
1309 if (
scale == targetScale) {
1314 double genericRating;
1320 genericRating =
rating / (5.0 / 4.0) + 1.0;
1323 genericRating =
rating < 1.0 ? 0.0 : ((
rating - 1.0) / (254.0 / 4.0) + 1.0);
1327 genericRating =
rating / 20.0;
1334 switch (targetScale) {
1339 rating = (genericRating - 1.0) * (5.0 / 4.0);
1342 rating = genericRating < 1.0 ? 0.0 : ((genericRating - 1.0) * (254.0 / 4.0) + 1.0);
1346 rating = genericRating * 20.0;
1352 scale = targetScale;
1362 return isEmpty() ? std::string()
1385 const auto parts = splitStringSimple<std::vector<std::string_view>>(str,
"|");
1387 if (parts.empty()) {
1389 }
else if (parts.size() > 3) {
1390 throw ConversionException(
"Wrong format, expected \"rating\" or \"user|rating|play-counter\"");
1393 if (parts.size() == 1) {
1395 res.rating = stringToNumber<
decltype(res.rating)>(parts.front());
1397 }
catch (
const ConversionException &) {
1401 res.user = parts.front();
1402 if (parts.size() > 1) {
1403 res.rating = stringToNumber<
decltype(res.rating)>(parts[1]);
1405 if (parts.size() > 2) {
1406 res.playCounter = stringToNumber<
decltype(res.playCounter)>(parts[2]);
static constexpr bool isEmptyGenre(int index)
Returns whether the genre index indicates the genre field is not set at all.
static constexpr bool isIndexSupported(int index)
Returns an indication whether the specified numerical denotation is supported by this class.
static constexpr int emptyGenreIndex()
Returns the preferred genre index to indicate that no genre is set at all.
static constexpr int genreCount()
Returns the number of supported genres.
static int indexFromString(std::string_view genre)
Returns the numerical denotation of the specified genre or -1 if genre is unknown.
static std::string_view stringFromIndex(int index)
Returns the genre name for the specified numerical denotation as C-style string.
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.
void clearMetadata()
Wipes assigned meta data.
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.
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...
std::string toDisplayString() const
Returns a "display string" for the specified value.
CppUtilities::DateTimeExpression toDateTimeExpression() const
Converts the value of the current TagValue object to its equivalent DateTimeExpression representation...
TagTextEncoding dataEncoding() const
Returns the data encoding.
void assignData(const char *data, std::size_t length, TagDataType type=TagDataType::Binary, TagTextEncoding encoding=TagTextEncoding::Latin1)
std::uint64_t toUnsignedInteger() const
std::int32_t toInteger() const
Converts the value of the current TagValue object to its equivalent integer representation.
PositionInSet toPositionInSet() const
Converts the value of the current TagValue object to its equivalent PositionInSet representation.
TagDataType type() const
Returns the type of the assigned value.
void convertDataEncodingForTag(const Tag *tag)
Ensures the encoding of the currently assigned text value is supported by the specified tag.
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.
std::string_view data() const
Returns the currently assigned raw data.
std::u16string toWString(TagTextEncoding encoding=TagTextEncoding::Unspecified) const
Converts the value of the current TagValue object to its equivalent std::wstring representation.
Popularity toScaledPopularity(TagType scale=TagType::Unspecified) const
Converts the value of the current TagValue object to its equivalent Popularity representation using t...
void convertDataEncoding(TagTextEncoding encoding)
Converts the currently assigned text value to the specified encoding.
TagTextEncoding descriptionEncoding() const
Returns the description encoding.
TagValue & operator=(const TagValue &other)
Assigns the value of another TagValue to the current instance.
std::string toString(TagTextEncoding encoding=TagTextEncoding::Unspecified) const
Converts the value of the current TagValue object to its equivalent std::string representation.
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.
void convertDescriptionEncoding(TagTextEncoding encoding)
Converts the assigned description to use the specified encoding.
int toStandardGenreIndex() const
Converts the value of the current TagValue object to its equivalent standard genre index.
CppUtilities::TimeSpan toTimeSpan() const
Converts the value of the current TagValue object to its equivalent TimeSpan representation.
TagValue()
Constructs an empty TagValue.
The Tag class is used to store, read and write tag information.
virtual TagTextEncoding proposedTextEncoding() const
Returns the proposed text encoding.
virtual bool canEncodingBeUsed(TagTextEncoding encoding) const
Returns an indication whether the specified encoding can be used to provide string values for the tag...
Contains all classes and functions of the TagInfo library.
std::string_view tagDataTypeString(TagDataType dataType)
Returns the string representation of the specified dataType.
TagTextEncoding
Specifies the text encoding.
TagType
Specifies the tag type.
pair< const char *, float > encodingParameter(TagTextEncoding tagTextEncoding)
Returns the encoding parameter (name of the character set and bytes per character) for the specified ...
TagValueComparisionFlags
The TagValueComparisionOption enum specifies options for TagValue::compareTo().
TagValueFlags
Specifies additional flags about the tag value.
TagDataType
Specifies the data type.
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.
bool isEmpty() const
Returns whether the Popularity is empty. The scale and zero-values don't count.
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.
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.
double rating
The rating on a tag type specific scale.
std::string toString() const
Returns the popularity as string in the format "rating" if only a rating is present or in the format ...