5 #include "../diagnostics.h"
6 #include "../exceptions.h"
8 #include <c++utilities/conversion/stringbuilder.h>
9 #include <c++utilities/conversion/stringconversion.h>
24 namespace Id3v2TextEncodingBytes {
25 enum Id3v2TextEncodingByte : std::uint8_t { Ascii, Utf16WithBom, Utf16BigEndianWithoutBom,
Utf8 };
40 Id3v2Frame::Id3v2Frame()
71 for (
auto c : denotation) {
82 if (c >=
'0' && c <=
'9') {
95 if (c >=
'0' && c <=
'9') {
96 index = index * 10 + c -
'0';
111 return string(get<0>(substr), get<1>(substr));
119 u16string res(
reinterpret_cast<u16string::const_pointer
>(get<0>(substr)), get<1>(substr) / 2);
136 static const string defaultContext(
"parsing ID3v2 frame");
143 setId(reader.readUInt24BE());
144 if (
id() & 0xFFFF0000u) {
153 context =
"parsing " %
idToString() +
" frame";
156 m_dataSize = reader.readUInt24BE();
157 m_totalSize = m_dataSize + 6;
158 if (m_totalSize > maximalSize) {
159 diag.emplace_back(DiagLevel::Warning,
"The frame is truncated and will be ignored.", context);
170 setId(reader.readUInt32BE());
171 if (
id() & 0xFF000000u) {
180 context =
"parsing " %
idToString() +
" frame";
183 m_dataSize =
version >= 4 ? reader.readSynchsafeUInt32BE() : reader.readUInt32BE();
184 m_totalSize = m_dataSize + 10;
185 if (m_totalSize > maximalSize) {
186 diag.emplace_back(DiagLevel::Warning,
"The frame is truncated and will be ignored.", context);
191 m_flag = reader.readUInt16BE();
195 diag.emplace_back(DiagLevel::Critical,
"Encrypted frames aren't supported.", context);
202 diag.emplace_back(DiagLevel::Warning,
203 argsToString(
"The frame is only supported in ID3v2.4 and newer but the tag's version is ID3v2.",
version,
'.'), context);
205 diag.emplace_back(DiagLevel::Warning,
206 argsToString(
"The frame is only supported in ID3v2.3 and older but the tag's version is ID3v2.",
version,
'.'), context);
210 if (m_dataSize <= 0) {
211 diag.emplace_back(DiagLevel::Warning,
"The frame size is 0.", context);
216 unique_ptr<char[]> buffer;
220 uLongf decompressedSize =
version >= 4 ? reader.readSynchsafeUInt32BE() : reader.readUInt32BE();
221 if (decompressedSize < m_dataSize) {
222 diag.emplace_back(DiagLevel::Critical,
"The decompressed size is smaller than the compressed size.", context);
225 const auto bufferCompressed = make_unique<char[]>(m_dataSize);
226 reader.read(bufferCompressed.get(), m_dataSize);
227 buffer = make_unique<char[]>(decompressedSize);
229 uncompress(
reinterpret_cast<Bytef *
>(buffer.get()), &decompressedSize,
reinterpret_cast<Bytef *
>(bufferCompressed.get()), m_dataSize)) {
231 diag.emplace_back(DiagLevel::Critical,
"Decompressing failed. The source buffer was too small.", context);
234 diag.emplace_back(DiagLevel::Critical,
"Decompressing failed. The destination buffer was too small.", context);
237 diag.emplace_back(DiagLevel::Critical,
"Decompressing failed. The input data was corrupted or incomplete.", context);
242 diag.emplace_back(DiagLevel::Critical,
"Decompressing failed (unknown reason).", context);
246 diag.emplace_back(DiagLevel::Critical,
"The decompressed data exceeds the maximum supported frame size.", context);
249 m_dataSize =
static_cast<std::uint32_t
>(decompressedSize);
251 buffer = make_unique<char[]>(m_dataSize);
252 reader.read(buffer.get(), m_dataSize);
261 const char *currentOffset = buffer.get() + 1;
262 for (
size_t currentIndex = 1; currentIndex < m_dataSize;) {
264 const auto substr(
parseSubstring(currentOffset, m_dataSize - currentIndex, dataEncoding,
false, diag));
267 if (!get<1>(substr)) {
268 if (currentIndex == 1) {
271 currentIndex =
static_cast<size_t>(get<2>(substr) - buffer.get());
272 currentOffset = get<2>(substr);
278 if (this->
value().isEmpty()) {
279 return &this->
value();
281 m_additionalValues.emplace_back();
282 return &m_additionalValues.back();
295 }
catch (
const ConversionException &) {
296 diag.emplace_back(DiagLevel::Warning,
"The value of track/disk position frame is not numeric and will be ignored.", context);
302 const auto milliseconds = [&] {
303 if (dataEncoding == TagTextEncoding::Utf16BigEndian || dataEncoding == TagTextEncoding::Utf16LittleEndian) {
304 const auto parsedStringRef =
parseSubstring(buffer.get() + 1, m_dataSize - 1, dataEncoding,
false, diag);
305 const auto convertedStringData = dataEncoding == TagTextEncoding::Utf16BigEndian
306 ? convertUtf16BEToUtf8(get<0>(parsedStringRef), get<1>(parsedStringRef))
307 : convertUtf16LEToUtf8(get<0>(parsedStringRef), get<1>(parsedStringRef));
308 return string(convertedStringData.first.get(), convertedStringData.second);
314 }
catch (
const ConversionException &) {
315 diag.emplace_back(DiagLevel::Warning,
"The value of the length frame is not numeric and will be ignored.", context);
320 const auto genreIndex = [&] {
327 if (genreIndex != -1) {
332 value->
assignData(get<0>(substr), get<1>(substr), TagDataType::Text, dataEncoding);
336 value->
assignData(get<0>(substr), get<1>(substr), TagDataType::Text, dataEncoding);
339 currentIndex =
static_cast<size_t>(get<2>(substr) - buffer.get());
340 currentOffset = get<2>(substr);
344 if (
version < 4 && !m_additionalValues.empty()) {
346 DiagLevel::Warning,
"Multiple strings found though the tag is pre-ID3v2.4. " + ignoreAdditionalValuesDiagMsg(), context);
403 void Id3v2Frame::reset()
411 m_additionalValues.clear();
417 std::string Id3v2Frame::ignoreAdditionalValuesDiagMsg()
const
419 if (m_additionalValues.size() == 1) {
420 return argsToString(
"Additional value \"", m_additionalValues.front().toString(
TagTextEncoding::Utf8),
"\" is supposed to be ignored.");
436 Id3v2FrameMaker::Id3v2FrameMaker(Id3v2Frame &frame, std::uint8_t
version, Diagnostics &diag)
438 , m_frameId(m_frame.id())
441 const string context(
"making " % m_frame.idToString() +
" frame");
444 if (m_frame.isEncrypted()) {
445 diag.emplace_back(DiagLevel::Critical,
"Cannot make an encrypted frame (isn't supported by this tagging library).", context);
446 throw InvalidDataException();
448 if (m_frame.hasPaddingReached()) {
449 diag.emplace_back(DiagLevel::Critical,
"Cannot make a frame which is marked as padding.", context);
450 throw InvalidDataException();
452 if (
version < 3 && m_frame.isCompressed()) {
453 diag.emplace_back(DiagLevel::Warning,
"Compression is not supported by the version of ID3v2 and won't be applied.", context);
455 if (
version < 3 && (m_frame.flag() || m_frame.group())) {
456 diag.emplace_back(DiagLevel::Warning,
457 "The existing flag and group information is not supported by the version of ID3v2 and will be ignored/discarted.", context);
461 vector<const TagValue *> values;
462 values.reserve(1 + frame.additionalValues().size());
463 if (!frame.value().isEmpty()) {
464 values.emplace_back(&frame.value());
466 for (
const auto &
value : frame.additionalValues()) {
468 values.emplace_back(&
value);
473 if (values.empty()) {
474 throw NoDataProvidedException();
479 if (values.size() != 1) {
481 diag.emplace_back(DiagLevel::Critical,
"Multiple values are not supported for non-text-frames.", context);
482 throw InvalidDataException();
485 DiagLevel::Warning,
"Multiple strings assigned to pre-ID3v2.4 text frame. " + frame.ignoreAdditionalValuesDiagMsg(), context);
494 diag.emplace_back(DiagLevel::Critical,
495 "The short frame ID can't be converted to its long equivalent which is needed to use the frame in a newer version of ID3v2.",
497 throw InvalidDataException();
504 diag.emplace_back(DiagLevel::Critical,
505 "The long frame ID can't be converted to its short equivalent which is needed to use the frame in the old version of ID3v2.",
507 throw InvalidDataException();
514 diag.emplace_back(DiagLevel::Warning,
515 argsToString(
"The frame is only supported in ID3v2.4 and newer but version of the tag being written is ID3v2.",
version,
516 ". The frame is written nevertheless but other tools might not be able to deal with it."),
519 diag.emplace_back(DiagLevel::Warning,
520 argsToString(
"The frame is only supported in ID3v2.3 and older but version of the tag being written is ID3v2.",
version,
521 ". The frame is written nevertheless but other tools might not be able to deal with it."),
529 vector<string> substrings;
530 substrings.reserve(1 + frame.additionalValues().size());
537 for (
const auto *
const value : values) {
546 }
catch (
const ConversionException &) {
547 diag.emplace_back(DiagLevel::Warning,
548 argsToString(
"The track/disk number \"", substrings.back(),
"\" is not of the expected form, eg. \"4/10\"."), context);
554 encoding = TagTextEncoding::Latin1;
555 for (
const auto *
const value : values) {
558 diag.emplace_back(DiagLevel::Critical, argsToString(
"Assigned duration \"",
duration.toString(),
"\" is negative."), context);
559 throw InvalidDataException();
561 substrings.emplace_back(numberToString(
static_cast<std::uint64_t
>(
duration.totalMilliseconds())));
567 for (
const auto *
const value : values) {
571 case TagDataType::StandardGenreIndex:
572 encoding = TagTextEncoding::Latin1;
578 case TagTextEncoding::Latin1:
580 case TagTextEncoding::Latin1:
590 encoding = TagTextEncoding::Utf16LittleEndian;
593 for (
const auto *
const value : values) {
594 if ((
value->
type() == TagDataType::StandardGenreIndex)
607 const auto terminationLength = (encoding == TagTextEncoding::Utf16BigEndian || encoding == TagTextEncoding::Utf16LittleEndian) ? 2u : 1u;
608 const auto byteOrderMark = [&] {
610 case TagTextEncoding::Utf16LittleEndian:
611 return string({
'\xFF',
'\xFE' });
612 case TagTextEncoding::Utf16BigEndian:
613 return string({
'\xFE',
'\xFF' });
618 const auto concatenatedSubstrings = joinStrings(substrings,
string(),
false, byteOrderMark,
string(terminationLength,
'\0'));
621 m_data = make_unique<char[]>(m_decompressedSize =
static_cast<std::uint32_t
>(1 + concatenatedSubstrings.size()));
623 concatenatedSubstrings.copy(&m_data[1], concatenatedSubstrings.size());
627 m_frame.makePicture(m_data, m_decompressedSize, *values.front(), m_frame.isTypeInfoAssigned() ? m_frame.typeInfo() : 0,
version, diag);
633 m_frame.makeComment(m_data, m_decompressedSize, *values.front(),
version, diag);
637 const auto &
value(*values.front());
639 diag.emplace_back(DiagLevel::Critical,
"Assigned value exceeds maximum size.", context);
640 throw InvalidDataException();
642 m_data = make_unique<char[]>(m_decompressedSize =
static_cast<std::uint32_t
>(
value.
dataSize()));
645 }
catch (
const ConversionException &) {
648 diag.emplace_back(DiagLevel::Critical,
649 argsToString(
"Assigned value(s) \"",
DiagMessage::formatList(valuesAsString),
"\" can not be converted appropriately."), context);
650 }
catch (
const ConversionException &) {
651 diag.emplace_back(DiagLevel::Critical,
"Assigned value(s) can not be converted appropriately.", context);
653 throw InvalidDataException();
657 if (
version >= 3 && m_frame.isCompressed()) {
658 auto compressedSize = compressBound(m_decompressedSize);
659 auto compressedData = make_unique<char[]>(compressedSize);
660 switch (compress(
reinterpret_cast<Bytef *
>(compressedData.get()),
reinterpret_cast<uLongf *
>(&compressedSize),
661 reinterpret_cast<Bytef *
>(m_data.get()), m_decompressedSize)) {
663 diag.emplace_back(DiagLevel::Critical,
"Decompressing failed. The source buffer was too small.", context);
664 throw InvalidDataException();
666 diag.emplace_back(DiagLevel::Critical,
"Decompressing failed. The destination buffer was too small.", context);
667 throw InvalidDataException();
671 diag.emplace_back(DiagLevel::Critical,
"Compressed size exceeds maximum data size.", context);
672 throw InvalidDataException();
674 m_data.swap(compressedData);
675 m_dataSize =
static_cast<std::uint32_t
>(compressedSize);
677 m_dataSize = m_decompressedSize;
682 m_requiredSize = m_dataSize;
688 m_requiredSize += 10;
690 if (m_frame.hasGroupInformation()) {
694 if (
version >= 3 && m_frame.isCompressed()) {
710 writer.writeUInt24BE(m_frameId);
711 writer.writeUInt24BE(m_dataSize);
713 writer.writeUInt32BE(m_frameId);
714 if (m_version >= 4) {
715 writer.writeSynchsafeUInt32BE(m_dataSize);
717 writer.writeUInt32BE(m_dataSize);
719 writer.writeUInt16BE(m_frame.
flag());
721 writer.writeByte(m_frame.
group());
724 if (m_version >= 4) {
725 writer.writeSynchsafeUInt32BE(m_decompressedSize);
727 writer.writeUInt32BE(m_decompressedSize);
731 writer.write(m_data.get(), m_dataSize);
742 switch (textEncodingByte) {
743 case Id3v2TextEncodingBytes::Ascii:
744 return TagTextEncoding::Latin1;
745 case Id3v2TextEncodingBytes::Utf16WithBom:
746 return TagTextEncoding::Utf16LittleEndian;
747 case Id3v2TextEncodingBytes::Utf16BigEndianWithoutBom:
748 return TagTextEncoding::Utf16BigEndian;
753 DiagLevel::Warning,
"The charset of the frame is invalid. Latin-1 will be used.",
"parsing encoding of frame " +
idToString());
754 return TagTextEncoding::Latin1;
763 switch (textEncoding) {
764 case TagTextEncoding::Latin1:
765 return Id3v2TextEncodingBytes::Ascii;
768 case TagTextEncoding::Utf16LittleEndian:
769 return Id3v2TextEncodingBytes::Utf16WithBom;
770 case TagTextEncoding::Utf16BigEndian:
771 return Id3v2TextEncodingBytes::Utf16WithBom;
794 tuple<const char *, size_t, const char *> res(buffer, 0, buffer + bufferSize);
797 case TagTextEncoding::Latin1:
799 if ((bufferSize >= 3) && (BE::toUInt24(buffer) == 0x00EFBBBF)) {
800 if (encoding == TagTextEncoding::Latin1) {
801 diag.emplace_back(DiagLevel::Critical,
"Denoted character set is Latin-1 but an UTF-8 BOM is present - assuming UTF-8.",
807 const char *pos = get<0>(res);
808 for (; *pos != 0x00; ++pos) {
809 if (pos < get<2>(res)) {
814 DiagLevel::Warning,
"String in frame is not terminated properly.",
"parsing termination of frame " +
idToString());
819 get<2>(res) = pos + 1;
822 case TagTextEncoding::Utf16BigEndian:
823 case TagTextEncoding::Utf16LittleEndian: {
824 if (bufferSize >= 2) {
825 switch (LE::toUInt16(buffer)) {
827 if (encoding == TagTextEncoding::Utf16BigEndian) {
828 diag.emplace_back(DiagLevel::Critical,
829 "Denoted character set is UTF-16 Big Endian but UTF-16 Little Endian BOM is present - assuming UTF-16 LE.",
831 encoding = TagTextEncoding::Utf16LittleEndian;
836 encoding = TagTextEncoding::Utf16BigEndian;
840 const std::uint16_t *pos =
reinterpret_cast<const std::uint16_t *
>(get<0>(res));
841 for (; *pos != 0x0000; ++pos) {
842 if (pos <
reinterpret_cast<const std::uint16_t *
>(get<2>(res))) {
847 DiagLevel::Warning,
"Wide string in frame is not terminated properly.",
"parsing termination of frame " +
idToString());
852 get<2>(res) =
reinterpret_cast<const char *
>(pos + 1);
891 case TagTextEncoding::Utf16BigEndian:
892 case TagTextEncoding::Utf16LittleEndian:
893 if ((maxSize >= 2) && (BE::toUInt16(buffer) == 0xFFFE)) {
894 encoding = TagTextEncoding::Utf16LittleEndian;
895 }
else if ((maxSize >= 2) && (BE::toUInt16(buffer) == 0xFEFF)) {
896 encoding = TagTextEncoding::Utf16BigEndian;
900 if ((maxSize >= 3) && (BE::toUInt24(buffer) == 0x00EFBBBF)) {
902 diag.emplace_back(DiagLevel::Warning,
"UTF-8 byte order mark found in text frame.",
"parsing byte oder mark of frame " +
idToString());
916 static const string context(
"parsing ID3v2.2 picture frame");
918 diag.emplace_back(DiagLevel::Critical,
"Picture frame is incomplete.", context);
921 const char *end = buffer + maxSize;
923 typeInfo =
static_cast<unsigned char>(*(buffer + 4));
924 auto substr =
parseSubstring(buffer + 5,
static_cast<size_t>(end - 5 - buffer), dataEncoding,
true, diag);
925 tagValue.
setDescription(
string(get<0>(substr), get<1>(substr)), dataEncoding);
926 if (get<2>(substr) >= end) {
927 diag.emplace_back(DiagLevel::Critical,
"Picture frame is incomplete (actual data is missing).", context);
930 tagValue.
assignData(get<2>(substr),
static_cast<size_t>(end - get<2>(substr)), TagDataType::Picture, dataEncoding);
942 static const string context(
"parsing ID3v2.3 picture frame");
943 const char *end = buffer + maxSize;
945 auto mimeTypeEncoding = TagTextEncoding::Latin1;
946 auto substr =
parseSubstring(buffer + 1, maxSize - 1, mimeTypeEncoding,
true, diag);
947 if (get<1>(substr)) {
948 tagValue.
setMimeType(
string(get<0>(substr), get<1>(substr)));
950 if (get<2>(substr) >= end) {
951 diag.emplace_back(DiagLevel::Critical,
"Picture frame is incomplete (type info, description and actual data are missing).", context);
954 typeInfo =
static_cast<unsigned char>(*get<2>(substr));
955 if (++get<2>(substr) >= end) {
956 diag.emplace_back(DiagLevel::Critical,
"Picture frame is incomplete (description and actual data are missing).", context);
959 substr =
parseSubstring(get<2>(substr),
static_cast<size_t>(end - get<2>(substr)), dataEncoding,
true, diag);
960 tagValue.
setDescription(
string(get<0>(substr), get<1>(substr)), dataEncoding);
961 if (get<2>(substr) >= end) {
962 diag.emplace_back(DiagLevel::Critical,
"Picture frame is incomplete (actual data is missing).", context);
965 tagValue.
assignData(get<2>(substr),
static_cast<size_t>(end - get<2>(substr)), TagDataType::Picture, dataEncoding);
976 static const string context(
"parsing comment/unsynchronized lyrics frame");
977 const char *end = buffer +
dataSize;
979 diag.emplace_back(DiagLevel::Critical,
"Comment frame is incomplete.", context);
987 tagValue.
setDescription(
string(get<0>(substr), get<1>(substr)), dataEncoding);
988 if (get<2>(substr) > end) {
989 diag.emplace_back(DiagLevel::Critical,
"Comment frame is incomplete (description not terminated?).", context);
992 substr =
parseSubstring(get<2>(substr),
static_cast<size_t>(end - get<2>(substr)), dataEncoding,
false, diag);
993 tagValue.
assignData(get<0>(substr), get<1>(substr), TagDataType::Text, dataEncoding);
1003 case TagTextEncoding::Utf16LittleEndian:
1004 LE::getBytes(
static_cast<std::uint16_t
>(0xFEFF), buffer);
1006 case TagTextEncoding::Utf16BigEndian:
1007 BE::getBytes(
static_cast<std::uint16_t
>(0xFEFF), buffer);
1018 unique_ptr<
char[]> &buffer, std::uint32_t &bufferSize,
const TagValue &picture, std::uint8_t typeInfo,
Diagnostics &diag)
1022 StringData convertedDescription;
1023 string::size_type descriptionSize = picture.
description().find(
1024 "\0\0", 0, descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? 2 : 1);
1025 if (descriptionSize == string::npos) {
1030 descriptionEncoding = TagTextEncoding::Utf16LittleEndian;
1031 convertedDescription = convertUtf8ToUtf16LE(picture.
description().data(), descriptionSize);
1032 descriptionSize = convertedDescription.second;
1037 const auto requiredBufferSize = 1 + 3 + 1 + descriptionSize
1038 + (descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? 4 : 1)
1040 if (requiredBufferSize > numeric_limits<std::uint32_t>::max()) {
1041 diag.emplace_back(DiagLevel::Critical,
"Required size exceeds maximum.",
"making legacy picture frame");
1044 buffer = make_unique<char[]>(bufferSize =
static_cast<std::uint32_t
>(requiredBufferSize));
1045 char *offset = buffer.get();
1051 const char *imageFormat;
1052 if (picture.
mimeType() ==
"image/jpeg") {
1053 imageFormat =
"JPG";
1054 }
else if (picture.
mimeType() ==
"image/png") {
1055 imageFormat =
"PNG";
1056 }
else if (picture.
mimeType() ==
"image/gif") {
1057 imageFormat =
"GIF";
1058 }
else if (picture.
mimeType() ==
"-->") {
1059 imageFormat = picture.
mimeType().data();
1061 imageFormat =
"UND";
1063 strncpy(++offset, imageFormat, 3);
1066 *(offset += 3) =
static_cast<char>(
typeInfo);
1069 offset +=
makeBom(offset + 1, descriptionEncoding);
1070 if (convertedDescription.first) {
1071 copy(convertedDescription.first.get(), convertedDescription.first.get() + descriptionSize, ++offset);
1073 picture.
description().copy(++offset, descriptionSize);
1075 *(offset += descriptionSize) = 0x00;
1076 if (descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian) {
1097 StringData convertedDescription;
1098 string::size_type descriptionSize = picture.
description().find(
1099 "\0\0", 0, descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? 2 : 1);
1100 if (descriptionSize == string::npos) {
1105 descriptionEncoding = TagTextEncoding::Utf16LittleEndian;
1106 convertedDescription = convertUtf8ToUtf16LE(picture.
description().data(), descriptionSize);
1107 descriptionSize = convertedDescription.second;
1110 string::size_type mimeTypeSize = picture.
mimeType().find(
'\0');
1111 if (mimeTypeSize == string::npos) {
1112 mimeTypeSize = picture.
mimeType().length();
1117 const auto requiredBufferSize = 1 + mimeTypeSize + 1 + 1 + descriptionSize
1118 + (descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? 4 : 1)
1120 if (requiredBufferSize > numeric_limits<uint32_t>::max()) {
1121 diag.emplace_back(DiagLevel::Critical,
"Required size exceeds maximum.",
"making picture frame");
1124 buffer = make_unique<char[]>(bufferSize =
static_cast<uint32_t
>(requiredBufferSize));
1125 char *offset = buffer.get();
1131 picture.
mimeType().copy(++offset, mimeTypeSize);
1133 *(offset += mimeTypeSize) = 0x00;
1135 *(++offset) =
static_cast<char>(
typeInfo);
1138 offset +=
makeBom(offset + 1, descriptionEncoding);
1139 if (convertedDescription.first) {
1140 copy(convertedDescription.first.get(), convertedDescription.first.get() + descriptionSize, ++offset);
1142 picture.
description().copy(++offset, descriptionSize);
1144 *(offset += descriptionSize) = 0x00;
1145 if (descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian) {
1158 static const string context(
"making comment frame");
1162 if (!
comment.description().empty() && encoding !=
comment.descriptionEncoding()) {
1163 diag.emplace_back(DiagLevel::Critical,
"Data encoding and description encoding aren't equal.", context);
1166 const string &lng =
comment.language();
1167 if (lng.length() > 3) {
1168 diag.emplace_back(DiagLevel::Critical,
"The language must be 3 bytes long (ISO-639-2).", context);
1171 StringData convertedDescription;
1172 string::size_type descriptionSize =
comment.description().find(
1173 "\0\0", 0, encoding == TagTextEncoding::Utf16BigEndian || encoding == TagTextEncoding::Utf16LittleEndian ? 2 : 1);
1174 if (descriptionSize == string::npos) {
1175 descriptionSize =
comment.description().size();
1179 encoding = TagTextEncoding::Utf16LittleEndian;
1180 convertedDescription = convertUtf8ToUtf16LE(
comment.description().data(), descriptionSize);
1181 descriptionSize = convertedDescription.second;
1186 const auto data =
comment.toString(encoding);
1187 const auto requiredBufferSize = 1 + 3 + descriptionSize + data.size()
1188 + (encoding == TagTextEncoding::Utf16BigEndian || encoding == TagTextEncoding::Utf16LittleEndian ? 6 : 1) + data.size();
1189 if (requiredBufferSize > numeric_limits<uint32_t>::max()) {
1190 diag.emplace_back(DiagLevel::Critical,
"Required size exceeds maximum.", context);
1193 buffer = make_unique<char[]>(bufferSize =
static_cast<uint32_t
>(requiredBufferSize));
1194 char *offset = buffer.get();
1200 for (
unsigned int i = 0; i < 3; ++i) {
1201 *(++offset) = (lng.length() > i) ? lng[i] : 0x00;
1205 offset +=
makeBom(offset + 1, encoding);
1206 if (convertedDescription.first) {
1207 copy(convertedDescription.first.get(), convertedDescription.first.get() + descriptionSize, ++offset);
1209 comment.description().copy(++offset, descriptionSize);
1211 offset += descriptionSize;
1213 if (encoding == TagTextEncoding::Utf16BigEndian || encoding == TagTextEncoding::Utf16LittleEndian) {
1218 offset +=
makeBom(offset + 1, encoding);
1219 data.copy(++offset, data.size());