diff --git a/CMakeLists.txt b/CMakeLists.txt index be6e389..680c45c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -142,6 +142,7 @@ set(SRC_FILES ) set(TEST_HEADER_FILES tests/overall.h + tests/helper.h ) set(TEST_SRC_FILES tests/cppunit.cpp diff --git a/id3/id3v2frame.cpp b/id3/id3v2frame.cpp index 4ccdc56..33ff48d 100644 --- a/id3/id3v2frame.cpp +++ b/id3/id3v2frame.cpp @@ -20,6 +20,16 @@ using namespace IoUtilities; namespace Media { +namespace Id3v2TextEncodingBytes { +enum Id3v2TextEncodingByte : byte +{ + Ascii, + Utf16WithBom, + Utf16BigEndianWithoutBom, + Utf8 +}; +} + /*! * \class Media::Id3v2Frame * \brief The Id3v2Frame class is used by Id3v2Tag to store the fields. @@ -422,7 +432,13 @@ Id3v2FrameMaker::Id3v2FrameMaker(Id3v2Frame &frame, const byte version) : m_frame.makeString(m_data, m_decompressedSize, ConversionUtilities::numberToString(m_frame.value().toStandardGenreIndex()), TagTextEncoding::Latin1); } else { // any other text frame - m_frame.makeString(m_data, m_decompressedSize, m_frame.value().toString(), m_frame.value().dataEncoding()); // the same as a normal text frame + if(version <= 3 && m_frame.value().dataEncoding() == TagTextEncoding::Utf8) { + // UTF-8 is only supported by ID3v2.4, so convert back to UTF-16 + m_frame.makeString(m_data, m_decompressedSize, m_frame.value().toString(TagTextEncoding::Utf16LittleEndian), TagTextEncoding::Utf16LittleEndian); + } else { + // just keep encoding of the assigned value + m_frame.makeString(m_data, m_decompressedSize, m_frame.value().toString(), m_frame.value().dataEncoding()); + } } } else if(version >= 3 && m_frameId == Id3v2FrameIds::lCover) { @@ -438,13 +454,11 @@ Id3v2FrameMaker::Id3v2FrameMaker(Id3v2Frame &frame, const byte version) : || ((version >= 3 && m_frameId == Id3v2FrameIds::lUnsynchronizedLyrics) || (version < 3 && m_frameId == Id3v2FrameIds::sUnsynchronizedLyrics))) { // the comment frame or the unsynchronized lyrics frame - m_frame.makeComment(m_data, m_decompressedSize, m_frame.value()); + m_frame.makeCommentConsideringVersion(m_data, m_decompressedSize, m_frame.value(), version); } else { // an unknown frame - // create buffer m_data = make_unique(m_decompressedSize = m_frame.value().dataSize()); - // just write the data copy(m_frame.value().dataPointer(), m_frame.value().dataPointer() + m_decompressedSize, m_data.get()); } } catch(const ConversionException &) { @@ -534,13 +548,13 @@ void Id3v2FrameMaker::make(BinaryWriter &writer) TagTextEncoding Id3v2Frame::parseTextEncodingByte(byte textEncodingByte) { switch(textEncodingByte) { - case 0: // Ascii + case Id3v2TextEncodingBytes::Ascii: return TagTextEncoding::Latin1; - case 1: // Utf 16 with bom + case Id3v2TextEncodingBytes::Utf16WithBom: return TagTextEncoding::Utf16LittleEndian; - case 2: // Utf 16 without bom + case Id3v2TextEncodingBytes::Utf16BigEndianWithoutBom: return TagTextEncoding::Utf16BigEndian; - case 3: // Utf 8 + case Id3v2TextEncodingBytes::Utf8: return TagTextEncoding::Utf8; default: addNotification(NotificationType::Warning, "The charset of the frame is invalid. Latin-1 will be used.", "parsing encoding of frame " + frameIdString()); @@ -555,13 +569,13 @@ byte Id3v2Frame::makeTextEncodingByte(TagTextEncoding textEncoding) { switch(textEncoding) { case TagTextEncoding::Latin1: - return 0; + return Id3v2TextEncodingBytes::Ascii; case TagTextEncoding::Utf8: - return 3; + return Id3v2TextEncodingBytes::Utf8; case TagTextEncoding::Utf16LittleEndian: - return 1; + return Id3v2TextEncodingBytes::Utf16WithBom; case TagTextEncoding::Utf16BigEndian: - return 2; + return Id3v2TextEncodingBytes::Utf16WithBom; default: return 0; } @@ -585,20 +599,43 @@ tuple Id3v2Frame::parseSubstring(const char { tuple res(buffer, 0, buffer + bufferSize); switch(encoding) { + case TagTextEncoding::Unspecified: + case TagTextEncoding::Latin1: + case TagTextEncoding::Utf8: { + if((bufferSize >= 3) && (ConversionUtilities::BE::toUInt24(buffer) == 0x00EFBBBF)) { + if(encoding == TagTextEncoding::Latin1) { + addNotification(NotificationType::Critical, "Denoted character set is Latin-1 but an UTF-8 BOM is present - assuming UTF-8.", "parsing frame " + frameIdString()); + encoding = TagTextEncoding::Utf8; + } + get<0>(res) += 3; + } + const char *pos = get<0>(res); + for(; *pos != 0x00; ++pos) { + if(pos < get<2>(res)) { + ++get<1>(res); + } else { + if(addWarnings) { + addNotification(NotificationType::Warning, "String in frame is not terminated proberly.", "parsing termination of frame " + frameIdString()); + } + break; + } + } + get<2>(res) = pos + 1; + break; + } case TagTextEncoding::Utf16BigEndian: case TagTextEncoding::Utf16LittleEndian: { if(bufferSize >= 2) { - if(ConversionUtilities::LE::toUInt16(buffer) == 0xFEFF) { - if(encoding != TagTextEncoding::Utf16LittleEndian) { - addNotification(NotificationType::Critical, "Denoted character set doesn't match present BOM - assuming UTF-16 Little Endian.", "parsing frame " + frameIdString()); + switch(ConversionUtilities::LE::toUInt16(buffer)) { + case 0xFEFF: + if(encoding == TagTextEncoding::Utf16BigEndian) { + addNotification(NotificationType::Critical, "Denoted character set is UTF-16 Big Endian but UTF-16 Little Endian BOM is present - assuming UTF-16 LE.", "parsing frame " + frameIdString()); encoding = TagTextEncoding::Utf16LittleEndian; } get<0>(res) += 2; - } else if(ConversionUtilities::BE::toUInt16(buffer) == 0xFEFF) { - if(encoding != TagTextEncoding::Utf16BigEndian) { - addNotification(NotificationType::Critical, "Denoted character set doesn't match present BOM - assuming UTF-16 Big Endian.", "parsing frame " + frameIdString()); - encoding = TagTextEncoding::Utf16BigEndian; - } + break; + case 0xFFFE: + encoding = TagTextEncoding::Utf16BigEndian; get<0>(res) += 2; } } @@ -613,29 +650,7 @@ tuple Id3v2Frame::parseSubstring(const char break; } } - get<2>(res) = reinterpret_cast(++pos); - break; - } - default: { - if((bufferSize >= 3) && (ConversionUtilities::BE::toUInt24(buffer) == 0x00EFBBBF)) { - get<0>(res) += 3; - if(encoding != TagTextEncoding::Utf8) { - addNotification(NotificationType::Critical, "Denoted character set doesn't match present BOM - assuming UTF-8.", "parsing frame " + frameIdString()); - encoding = TagTextEncoding::Utf8; - } - } - const char *pos = get<0>(res); - for(; *pos != 0x00; ++pos) { - if(pos < get<2>(res)) { - ++get<1>(res); - } else { - if(addWarnings) { - addNotification(NotificationType::Warning, "String in frame is not terminated proberly.", "parsing termination of frame " + frameIdString()); - } - break; - } - } - get<2>(res) = ++pos; + get<2>(res) = reinterpret_cast(pos + 1); break; } } @@ -725,7 +740,6 @@ void Id3v2Frame::parseLegacyPicture(const char *buffer, std::size_t maxSize, Tag } const char *end = buffer + maxSize; auto dataEncoding = parseTextEncodingByte(*buffer); // the first byte stores the encoding - //auto imageFormat = parseSubstring(buffer + 1, 3, TagTextEncoding::Latin1); typeInfo = static_cast(*(buffer + 4)); auto substr = parseSubstring(buffer + 5, end - 5 - buffer, dataEncoding, true); tagValue.setDescription(string(get<0>(substr), get<1>(substr)), dataEncoding); @@ -872,15 +886,25 @@ size_t Id3v2Frame::makeBom(char *buffer, TagTextEncoding encoding) */ void Id3v2Frame::makeLegacyPicture(unique_ptr &buffer, uint32 &bufferSize, const TagValue &picture, byte typeInfo) { - // calculate needed buffer size and create buffer - const TagTextEncoding descriptionEncoding = picture.descriptionEncoding(); - const uint32 dataSize = picture.dataSize(); - string::size_type descriptionLength = picture.description().find(descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? "\0\0" : "\0"); - if(descriptionLength == string::npos) { - descriptionLength = picture.description().length(); + // determine description + TagTextEncoding descriptionEncoding = picture.descriptionEncoding(); + StringData convertedDescription; + string::size_type descriptionSize = picture.description().find(descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? "\0\0" : "\0"); + if(descriptionEncoding == TagTextEncoding::Utf8) { + // UTF-8 is only supported by ID3v2.4, so convert back to UTF-16 + descriptionEncoding = TagTextEncoding::Utf16LittleEndian; + convertedDescription = convertUtf8ToUtf16LE(picture.description().data(), picture.description().size()); + descriptionSize = convertedDescription.second; + } else { + descriptionSize = picture.description().find(descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? "\0\0" : "\0"); + if(descriptionSize == string::npos) { + descriptionSize = picture.description().size(); + } } - buffer = make_unique(bufferSize = 1 + 3 + 1 + descriptionLength + (descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? 4 : 1) + dataSize); - // note: encoding byte + image format + picture type byte + description length + 1 or 2 null bytes (depends on encoding) + data size + // calculate needed buffer size and create buffer + const uint32 dataSize = picture.dataSize(); + buffer = make_unique(bufferSize = 1 + 3 + 1 + descriptionSize + (descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? 4 : 1) + dataSize); + // note: encoding byte + image format + picture type byte + description size + 1 or 2 null bytes (depends on encoding) + data size char *offset = buffer.get(); // write encoding byte *offset = makeTextEncodingByte(descriptionEncoding); @@ -902,8 +926,12 @@ void Id3v2Frame::makeLegacyPicture(unique_ptr &buffer, uint32 &bufferSiz *(offset += 3) = typeInfo; // write description offset += makeBom(offset + 1, descriptionEncoding); - picture.description().copy(++offset, descriptionLength); - *(offset += descriptionLength) = 0x00; // terminate description and increase data offset + if(convertedDescription.first) { + copy(convertedDescription.first.get(), convertedDescription.first.get() + descriptionSize, ++offset); + } else { + picture.description().copy(++offset, descriptionSize); + } + *(offset += descriptionSize) = 0x00; // terminate description and increase data offset if(descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian) { *(++offset) = 0x00; } @@ -916,31 +944,46 @@ void Id3v2Frame::makeLegacyPicture(unique_ptr &buffer, uint32 &bufferSiz */ void Id3v2Frame::makePicture(unique_ptr &buffer, uint32 &bufferSize, const TagValue &picture, byte typeInfo) { + // determine description + TagTextEncoding descriptionEncoding = picture.descriptionEncoding(); + StringData convertedDescription; + string::size_type descriptionSize = picture.description().find(descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? "\0\0" : "\0"); + if(descriptionEncoding == TagTextEncoding::Utf8) { + // UTF-8 is only supported by ID3v2.4, so convert back to UTF-16 + descriptionEncoding = TagTextEncoding::Utf16LittleEndian; + convertedDescription = convertUtf8ToUtf16LE(picture.description().data(), picture.description().size()); + descriptionSize = convertedDescription.second; + } else { + descriptionSize = picture.description().find(descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? "\0\0" : "\0"); + if(descriptionSize == string::npos) { + descriptionSize = picture.description().size(); + } + } + // determine mime-type + string::size_type mimeTypeSize = picture.mimeType().find('\0'); + if(mimeTypeSize == string::npos) { + mimeTypeSize = picture.mimeType().length(); + } // calculate needed buffer size and create buffer - const TagTextEncoding descriptionEncoding = picture.descriptionEncoding(); const uint32 dataSize = picture.dataSize(); - string::size_type mimeTypeLength = picture.mimeType().find('\0'); - if(mimeTypeLength == string::npos) { - mimeTypeLength = picture.mimeType().length(); - } - string::size_type descriptionLength = picture.description().find(descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? "\0\0" : "\0"); - if(descriptionLength == string::npos) { - descriptionLength = picture.description().length(); - } - buffer = make_unique(bufferSize = 1 + mimeTypeLength + 1 + 1 + descriptionLength + (descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? 4 : 1) + dataSize); - // note: encoding byte + mime type length + 0 byte + picture type byte + description length + 1 or 4 null bytes (depends on encoding) + data size + buffer = make_unique(bufferSize = 1 + mimeTypeSize + 1 + 1 + descriptionSize + (descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? 4 : 1) + dataSize); + // note: encoding byte + mime type size + 0 byte + picture type byte + description size + 1 or 4 null bytes (depends on encoding) + data size char *offset = buffer.get(); // write encoding byte *offset = makeTextEncodingByte(descriptionEncoding); // write mime type - picture.mimeType().copy(++offset, mimeTypeLength); - *(offset += mimeTypeLength) = 0x00; // terminate mime type + picture.mimeType().copy(++offset, mimeTypeSize); + *(offset += mimeTypeSize) = 0x00; // terminate mime type // write picture type *(++offset) = typeInfo; // write description offset += makeBom(offset + 1, descriptionEncoding); - picture.description().copy(++offset, descriptionLength); - *(offset += descriptionLength) = 0x00; // terminate description and increase data offset + if(convertedDescription.first) { + copy(convertedDescription.first.get(), convertedDescription.first.get() + descriptionSize, ++offset); + } else { + picture.description().copy(++offset, descriptionSize); + } + *(offset += descriptionSize) = 0x00; // terminate description and increase data offset if(descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian) { *(++offset) = 0x00; } @@ -952,6 +995,14 @@ void Id3v2Frame::makePicture(unique_ptr &buffer, uint32 &bufferSize, con * \brief Writes the specified comment to the specified buffer. */ void Id3v2Frame::makeComment(unique_ptr &buffer, uint32 &bufferSize, const TagValue &comment) +{ + makeCommentConsideringVersion(buffer, bufferSize, comment, 3); +} + +/*! + * \brief Writes the specified comment to the specified buffer. + */ +void Id3v2Frame::makeCommentConsideringVersion(unique_ptr &buffer, uint32 &bufferSize, const TagValue &comment, byte version) { static const string context("making comment frame"); // check type and other values are valid @@ -965,14 +1016,23 @@ void Id3v2Frame::makeComment(unique_ptr &buffer, uint32 &bufferSize, con addNotification(NotificationType::Critical, "The language must be 3 bytes long (ISO-639-2).", context); throw InvalidDataException(); } - // calculate needed buffer size and create buffer - string::size_type descriptionLength = comment.description().find(encoding == TagTextEncoding::Utf16BigEndian || encoding == TagTextEncoding::Utf16LittleEndian ? "\0\0" : "\0"); - if(descriptionLength == string::npos) { - descriptionLength = comment.description().length(); + StringData convertedDescription; + string::size_type descriptionSize; + if(version < 4 && encoding == TagTextEncoding::Utf8) { + // UTF-8 is only supported by ID3v2.4, so convert back to UTF-16 + encoding = TagTextEncoding::Utf16LittleEndian; + convertedDescription = convertUtf8ToUtf16LE(comment.description().data(), comment.description().size()); + descriptionSize = convertedDescription.second; + } else { + descriptionSize = comment.description().find(encoding == TagTextEncoding::Utf16BigEndian || encoding == TagTextEncoding::Utf16LittleEndian ? "\0\0" : "\0"); + if(descriptionSize == string::npos) { + descriptionSize = comment.description().size(); + } } - const auto data = comment.toString(); - buffer = make_unique(bufferSize = 1 + 3 + descriptionLength + data.size() + (encoding == TagTextEncoding::Utf16BigEndian || encoding == TagTextEncoding::Utf16LittleEndian ? 6 : 1) + data.size()); - // note: encoding byte + language + description length + actual data size + BOMs and termination + // calculate needed buffer size and create buffer + const auto data = comment.toString(encoding); + buffer = make_unique(bufferSize = 1 + 3 + descriptionSize + data.size() + (encoding == TagTextEncoding::Utf16BigEndian || encoding == TagTextEncoding::Utf16LittleEndian ? 6 : 1) + data.size()); + // note: encoding byte + language + description size + actual data size + BOMs and termination char *offset = buffer.get(); // write encoding *offset = makeTextEncodingByte(encoding); @@ -982,8 +1042,12 @@ void Id3v2Frame::makeComment(unique_ptr &buffer, uint32 &bufferSize, con } // write description offset += makeBom(offset + 1, encoding); - comment.description().copy(++offset, descriptionLength); - offset += descriptionLength; + if(convertedDescription.first) { + copy(convertedDescription.first.get(), convertedDescription.first.get() + descriptionSize, ++offset); + } else { + comment.description().copy(++offset, descriptionSize); + } + offset += descriptionSize; *offset = 0x00; // terminate description and increase data offset if(encoding == TagTextEncoding::Utf16BigEndian || encoding == TagTextEncoding::Utf16LittleEndian) { *(++offset) = 0x00; diff --git a/id3/id3v2frame.h b/id3/id3v2frame.h index 6422db6..c4e6d60 100644 --- a/id3/id3v2frame.h +++ b/id3/id3v2frame.h @@ -100,6 +100,7 @@ public: class TAG_PARSER_EXPORT Id3v2Frame : public TagField, public StatusProvider { friend class TagField; + friend class Id3v2FrameMaker; // FIXME: remove when making methods public in next minor release public: Id3v2Frame(); @@ -157,7 +158,9 @@ protected: void cleared(); private: - std::size_t makeBom(char *buffer, TagTextEncoding encoding); // FIXME: add to public API in next minor release + // FIXME: add to public API in next minor release + void makeCommentConsideringVersion(std::unique_ptr &buffer, uint32 &bufferSize, const TagValue &comment, byte version); + std::size_t makeBom(char *buffer, TagTextEncoding encoding); uint16 m_flag; byte m_group; diff --git a/tagvalue.cpp b/tagvalue.cpp index e1bab56..3d85a9e 100644 --- a/tagvalue.cpp +++ b/tagvalue.cpp @@ -84,6 +84,7 @@ bool TagValue::operator==(const TagValue &other) const switch(m_type) { case TagDataType::Text: if(m_size != other.m_size && m_encoding != other.m_encoding) { + // don't consider differently encoded text values equal return false; } return strncmp(m_ptr.get(), other.m_ptr.get(), m_size) == 0; diff --git a/tests/helper.h b/tests/helper.h new file mode 100644 index 0000000..cc5bbfa --- /dev/null +++ b/tests/helper.h @@ -0,0 +1,38 @@ +#ifndef TAGPARSER_TEST_HELPER +#define TAGPARSER_TEST_HELPER + +#include "../tagvalue.h" + +#include + +/*! + * \brief Prints a TagTextEncoding to enable CPPUNIT_ASSERT_EQUAL for tag values. + */ +std::ostream &operator <<(std::ostream &os, const Media::TagTextEncoding &encoding) +{ + using namespace Media; + switch(encoding) { + case TagTextEncoding::Unspecified: + return os << "unspecified"; + case TagTextEncoding::Latin1: + return os << "Latin-1"; + case TagTextEncoding::Utf8: + return os << "UTF-8"; + case TagTextEncoding::Utf16LittleEndian: + return os << "UTF-16 LE"; + case TagTextEncoding::Utf16BigEndian: + return os << "UTF-16 BE"; + } + return os; +} + +/*! + * \brief Prints a TagValue UTF-8 encoded to enable CPPUNIT_ASSERT_EQUAL for tag values. + */ +std::ostream &operator <<(std::ostream &os, const Media::TagValue &tagValue) +{ + os << tagValue.toString(Media::TagTextEncoding::Utf8) << " (encoding: " << tagValue.dataEncoding() << ")"; + return os; +} + +#endif // TAGPARSER_TEST_HELPER diff --git a/tests/overallgeneral.cpp b/tests/overallgeneral.cpp index f343ddb..6d0aee7 100644 --- a/tests/overallgeneral.cpp +++ b/tests/overallgeneral.cpp @@ -9,6 +9,7 @@ void OverallTests::setUp() { m_testTitle.assignText("some title", TagTextEncoding::Utf8); m_testComment.assignText("some cómment", TagTextEncoding::Utf8); + m_testComment.setDescription("some descriptión", TagTextEncoding::Utf8); m_testAlbum.assignText("some album", TagTextEncoding::Utf8); m_testPartNumber.assignInteger(41); m_testTotalParts.assignInteger(61); @@ -17,8 +18,10 @@ void OverallTests::setUp() void OverallTests::tearDown() { - if(!m_nestedTagsMkvPath.empty()) { - remove(m_nestedTagsMkvPath.data()); + for(const string &file : {m_nestedTagsMkvPath, m_rawFlacPath, m_flacInOggPath}) { + if(!file.empty()) { + remove(file.data()); + } } } diff --git a/tests/overallmp3.cpp b/tests/overallmp3.cpp index d968580..12997bc 100644 --- a/tests/overallmp3.cpp +++ b/tests/overallmp3.cpp @@ -1,3 +1,4 @@ +#include "./helper.h" #include "./overall.h" #include "../abstracttrack.h" @@ -91,16 +92,28 @@ void OverallTests::checkMp3TestMetaData() } // check common test meta data - for(Tag *tag : initializer_list{id3v1Tag, id3v2Tag}) { - if(tag) { - CPPUNIT_ASSERT(tag->value(KnownField::Title) == m_testTitle); - CPPUNIT_ASSERT(tag->value(KnownField::Comment) == m_testComment); - CPPUNIT_ASSERT(tag->value(KnownField::Album) == m_testAlbum); - CPPUNIT_ASSERT(tag->value(KnownField::Artist) == m_preservedMetaData.front()); - // TODO: check more fields - m_preservedMetaData.pop(); - } + if(id3v1Tag) { + CPPUNIT_ASSERT_EQUAL(m_testTitle, id3v1Tag->value(KnownField::Title)); + CPPUNIT_ASSERT_EQUAL(m_testComment.toString(), id3v1Tag->value(KnownField::Comment).toString()); // ignore encoding here + CPPUNIT_ASSERT_EQUAL(m_testAlbum, id3v1Tag->value(KnownField::Album)); + CPPUNIT_ASSERT_EQUAL(m_preservedMetaData.front(), id3v1Tag->value(KnownField::Artist)); + m_preservedMetaData.pop(); } + if(id3v2Tag) { + const TagValue &titleValue = id3v2Tag->value(KnownField::Title); + CPPUNIT_ASSERT_MESSAGE("not attempted to use UTF-8 in ID3v2.3", titleValue.dataEncoding() == TagTextEncoding::Utf16LittleEndian); + CPPUNIT_ASSERT_EQUAL(m_testTitle.toString(), titleValue.toString(TagTextEncoding::Utf8)); + const TagValue &commentValue = id3v2Tag->value(KnownField::Comment); + CPPUNIT_ASSERT_MESSAGE("not attempted to use UTF-8 in ID3v2.3", commentValue.dataEncoding() == TagTextEncoding::Utf16LittleEndian); + CPPUNIT_ASSERT_MESSAGE("not attempted to use UTF-8 in ID3v2.3", commentValue.descriptionEncoding() == TagTextEncoding::Utf16LittleEndian); + CPPUNIT_ASSERT_EQUAL(m_testComment.toString(), commentValue.toString(TagTextEncoding::Utf8)); + CPPUNIT_ASSERT_EQUAL_MESSAGE("description is also converted to UTF-16", "s\0o\0m\0e\0 \0d\0e\0s\0c\0r\0i\0p\0t\0i\0\xf3\0n\0"s, commentValue.description()); + CPPUNIT_ASSERT_EQUAL(m_testAlbum.toString(TagTextEncoding::Utf8), id3v2Tag->value(KnownField::Album).toString(TagTextEncoding::Utf8)); + CPPUNIT_ASSERT_EQUAL(m_preservedMetaData.front(), id3v2Tag->value(KnownField::Artist)); + // TODO: check more fields + m_preservedMetaData.pop(); + } + // test ID3v1 specific test meta data if(id3v1Tag) { CPPUNIT_ASSERT(id3v1Tag->value(KnownField::TrackPosition).toPositionInSet().position() == m_testPosition.toPositionInSet().position());