2015-09-06 19:57:33 +02:00
|
|
|
#include "./id3v2frame.h"
|
|
|
|
#include "./id3genres.h"
|
|
|
|
#include "./id3v2frameids.h"
|
2015-09-06 15:42:18 +02:00
|
|
|
|
2018-03-05 17:49:29 +01:00
|
|
|
#include "../diagnostics.h"
|
2018-03-07 01:17:50 +01:00
|
|
|
#include "../exceptions.h"
|
2015-04-22 19:22:01 +02:00
|
|
|
|
2017-01-27 18:59:22 +01:00
|
|
|
#include <c++utilities/conversion/stringbuilder.h>
|
2018-03-07 01:17:50 +01:00
|
|
|
#include <c++utilities/conversion/stringconversion.h>
|
2015-04-22 19:22:01 +02:00
|
|
|
|
|
|
|
#include <zlib.h>
|
|
|
|
|
|
|
|
#include <algorithm>
|
2019-03-13 19:06:42 +01:00
|
|
|
#include <cstdint>
|
2015-04-22 19:22:01 +02:00
|
|
|
#include <cstring>
|
2019-02-13 21:43:56 +01:00
|
|
|
#include <limits>
|
2017-02-05 21:02:40 +01:00
|
|
|
#include <memory>
|
2015-04-22 19:22:01 +02:00
|
|
|
|
|
|
|
using namespace std;
|
2019-06-10 22:49:11 +02:00
|
|
|
using namespace CppUtilities;
|
2018-03-06 23:09:15 +01:00
|
|
|
namespace TagParser {
|
2015-04-22 19:22:01 +02:00
|
|
|
|
2020-11-05 19:52:09 +01:00
|
|
|
/// \cond
|
2017-05-20 23:34:45 +02:00
|
|
|
namespace Id3v2TextEncodingBytes {
|
2019-03-13 19:06:42 +01:00
|
|
|
enum Id3v2TextEncodingByte : std::uint8_t { Ascii, Utf16WithBom, Utf16BigEndianWithoutBom, Utf8 };
|
2017-05-20 23:34:45 +02:00
|
|
|
}
|
2020-11-05 19:52:09 +01:00
|
|
|
/// \endcond
|
2017-05-20 23:34:45 +02:00
|
|
|
|
2018-06-02 22:56:08 +02:00
|
|
|
/// \brief The maximum (supported) size of an ID3v2Frame.
|
2019-03-13 19:06:42 +01:00
|
|
|
constexpr auto maxId3v2FrameDataSize(numeric_limits<std::uint32_t>::max() - 15);
|
2018-06-02 22:56:08 +02:00
|
|
|
|
2015-04-22 19:22:01 +02:00
|
|
|
/*!
|
2018-06-03 20:38:32 +02:00
|
|
|
* \class TagParser::Id3v2Frame
|
2015-04-22 19:22:01 +02:00
|
|
|
* \brief The Id3v2Frame class is used by Id3v2Tag to store the fields.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Constructs a new Id3v2Frame.
|
|
|
|
*/
|
2018-03-07 01:17:50 +01:00
|
|
|
Id3v2Frame::Id3v2Frame()
|
2018-03-11 16:14:42 +01:00
|
|
|
: m_parsedVersion(0)
|
2018-03-07 01:17:50 +01:00
|
|
|
, m_dataSize(0)
|
|
|
|
, m_totalSize(0)
|
2018-03-11 16:14:42 +01:00
|
|
|
, m_flag(0)
|
|
|
|
, m_group(0)
|
2018-03-07 01:17:50 +01:00
|
|
|
, m_padding(false)
|
|
|
|
{
|
|
|
|
}
|
2015-04-22 19:22:01 +02:00
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Constructs a new Id3v2Frame with the specified \a id, \a value, \a group and \a flag.
|
|
|
|
*/
|
2019-03-13 19:06:42 +01:00
|
|
|
Id3v2Frame::Id3v2Frame(const IdentifierType &id, const TagValue &value, std::uint8_t group, std::uint16_t flag)
|
2018-03-07 01:17:50 +01:00
|
|
|
: TagField<Id3v2Frame>(id, value)
|
|
|
|
, m_parsedVersion(0)
|
|
|
|
, m_dataSize(0)
|
|
|
|
, m_totalSize(0)
|
2018-03-11 16:14:42 +01:00
|
|
|
, m_flag(flag)
|
|
|
|
, m_group(group)
|
2018-03-07 01:17:50 +01:00
|
|
|
, m_padding(false)
|
|
|
|
{
|
|
|
|
}
|
2015-04-22 19:22:01 +02:00
|
|
|
|
2016-02-23 20:33:00 +01:00
|
|
|
/*!
|
|
|
|
* \brief Helper function to parse the genre index.
|
|
|
|
* \returns Returns the genre index or -1 if the specified string does not denote a genre index.
|
|
|
|
*/
|
2018-03-07 01:17:50 +01:00
|
|
|
template <class stringtype> int parseGenreIndex(const stringtype &denotation)
|
2016-02-23 20:33:00 +01:00
|
|
|
{
|
|
|
|
int index = -1;
|
2018-03-07 01:17:50 +01:00
|
|
|
for (auto c : denotation) {
|
|
|
|
if (index == -1) {
|
|
|
|
switch (c) {
|
2016-02-23 20:33:00 +01:00
|
|
|
case ' ':
|
|
|
|
break;
|
|
|
|
case '(':
|
|
|
|
index = 0;
|
|
|
|
break;
|
|
|
|
case '\0':
|
|
|
|
return -1;
|
|
|
|
default:
|
2018-03-07 01:17:50 +01:00
|
|
|
if (c >= '0' && c <= '9') {
|
2016-02-23 20:33:00 +01:00
|
|
|
index = c - '0';
|
|
|
|
} else {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
2018-03-07 01:17:50 +01:00
|
|
|
switch (c) {
|
2016-02-23 20:33:00 +01:00
|
|
|
case ')':
|
|
|
|
return index;
|
|
|
|
case '\0':
|
|
|
|
return index;
|
|
|
|
default:
|
2018-03-07 01:17:50 +01:00
|
|
|
if (c >= '0' && c <= '9') {
|
2016-02-23 20:33:00 +01:00
|
|
|
index = index * 10 + c - '0';
|
|
|
|
} else {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return index;
|
|
|
|
}
|
|
|
|
|
2018-07-01 02:04:29 +02:00
|
|
|
/*!
|
|
|
|
* \brief Returns an std::string instance for the substring parsed using parseSubstring().
|
|
|
|
*/
|
|
|
|
string stringFromSubstring(tuple<const char *, size_t, const char *> substr)
|
|
|
|
{
|
|
|
|
return string(get<0>(substr), get<1>(substr));
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Returns an std::u16string instance for the substring parsed using parseSubstring().
|
|
|
|
*/
|
|
|
|
u16string wideStringFromSubstring(tuple<const char *, size_t, const char *> substr, TagTextEncoding encoding)
|
|
|
|
{
|
|
|
|
u16string res(reinterpret_cast<u16string::const_pointer>(get<0>(substr)), get<1>(substr) / 2);
|
|
|
|
TagValue::ensureHostByteOrder(res, encoding);
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
2015-04-22 19:22:01 +02:00
|
|
|
/*!
|
|
|
|
* \brief Parses a frame from the stream read using the specified \a reader.
|
|
|
|
*
|
|
|
|
* The position of the current character in the input stream is expected to be
|
|
|
|
* at the beginning of the frame to be parsed.
|
|
|
|
*
|
|
|
|
* \throws Throws std::ios_base::failure when an IO error occurs.
|
2018-06-03 20:38:32 +02:00
|
|
|
* \throws Throws TagParser::Failure or a derived exception when a parsing
|
2015-04-22 19:22:01 +02:00
|
|
|
* error occurs.
|
|
|
|
*/
|
2019-03-13 19:06:42 +01:00
|
|
|
void Id3v2Frame::parse(BinaryReader &reader, std::uint32_t version, std::uint32_t maximalSize, Diagnostics &diag)
|
2015-04-22 19:22:01 +02:00
|
|
|
{
|
2017-02-06 18:52:21 +01:00
|
|
|
static const string defaultContext("parsing ID3v2 frame");
|
|
|
|
string context;
|
2015-12-22 23:54:35 +01:00
|
|
|
|
|
|
|
// parse header
|
2018-03-07 01:17:50 +01:00
|
|
|
if (version < 3) {
|
2015-04-22 19:22:01 +02:00
|
|
|
// parse header for ID3v2.1 and ID3v2.2
|
2015-12-22 23:54:35 +01:00
|
|
|
// -> read ID
|
2015-04-22 19:22:01 +02:00
|
|
|
setId(reader.readUInt24BE());
|
2018-03-07 01:17:50 +01:00
|
|
|
if (id() & 0xFFFF0000u) {
|
2016-03-18 21:43:09 +01:00
|
|
|
m_padding = false;
|
|
|
|
} else {
|
2015-12-22 23:54:35 +01:00
|
|
|
// padding reached
|
2015-04-22 19:22:01 +02:00
|
|
|
m_padding = true;
|
|
|
|
throw NoDataFoundException();
|
|
|
|
}
|
2015-12-22 23:54:35 +01:00
|
|
|
|
|
|
|
// -> update context
|
2018-07-11 11:08:48 +02:00
|
|
|
context = "parsing " % idToString() + " frame";
|
2015-12-22 23:54:35 +01:00
|
|
|
|
|
|
|
// -> read size, check whether frame is truncated
|
2015-04-22 19:22:01 +02:00
|
|
|
m_dataSize = reader.readUInt24BE();
|
2015-12-22 17:01:25 +01:00
|
|
|
m_totalSize = m_dataSize + 6;
|
2018-03-07 01:17:50 +01:00
|
|
|
if (m_totalSize > maximalSize) {
|
2018-03-05 17:49:29 +01:00
|
|
|
diag.emplace_back(DiagLevel::Warning, "The frame is truncated and will be ignored.", context);
|
2015-04-22 19:22:01 +02:00
|
|
|
throw TruncatedDataException();
|
|
|
|
}
|
2015-12-22 23:54:35 +01:00
|
|
|
|
|
|
|
// -> no flags/group in ID3v2.2
|
2015-04-22 19:22:01 +02:00
|
|
|
m_flag = 0;
|
|
|
|
m_group = 0;
|
2015-12-22 23:54:35 +01:00
|
|
|
|
2015-04-22 19:22:01 +02:00
|
|
|
} else {
|
|
|
|
// parse header for ID3v2.3 and ID3v2.4
|
2015-12-22 23:54:35 +01:00
|
|
|
// -> read ID
|
2015-04-22 19:22:01 +02:00
|
|
|
setId(reader.readUInt32BE());
|
2018-03-07 01:17:50 +01:00
|
|
|
if (id() & 0xFF000000u) {
|
2016-03-18 21:43:09 +01:00
|
|
|
m_padding = false;
|
|
|
|
} else {
|
2015-12-22 23:54:35 +01:00
|
|
|
// padding reached
|
2015-04-22 19:22:01 +02:00
|
|
|
m_padding = true;
|
|
|
|
throw NoDataFoundException();
|
|
|
|
}
|
2015-12-22 23:54:35 +01:00
|
|
|
|
|
|
|
// -> update context
|
2018-07-11 11:08:48 +02:00
|
|
|
context = "parsing " % idToString() + " frame";
|
2015-12-22 23:54:35 +01:00
|
|
|
|
|
|
|
// -> read size, check whether frame is truncated
|
2018-03-07 01:17:50 +01:00
|
|
|
m_dataSize = version >= 4 ? reader.readSynchsafeUInt32BE() : reader.readUInt32BE();
|
2015-12-22 17:01:25 +01:00
|
|
|
m_totalSize = m_dataSize + 10;
|
2018-03-07 01:17:50 +01:00
|
|
|
if (m_totalSize > maximalSize) {
|
2018-03-05 17:49:29 +01:00
|
|
|
diag.emplace_back(DiagLevel::Warning, "The frame is truncated and will be ignored.", context);
|
2015-04-22 19:22:01 +02:00
|
|
|
throw TruncatedDataException();
|
|
|
|
}
|
2015-12-22 23:54:35 +01:00
|
|
|
|
|
|
|
// -> read flags and group
|
2015-04-22 19:22:01 +02:00
|
|
|
m_flag = reader.readUInt16BE();
|
|
|
|
m_group = hasGroupInformation() ? reader.readByte() : 0;
|
2018-03-07 01:17:50 +01:00
|
|
|
if (isEncrypted()) {
|
2015-12-22 23:54:35 +01:00
|
|
|
// encryption is not implemented
|
2018-03-05 17:49:29 +01:00
|
|
|
diag.emplace_back(DiagLevel::Critical, "Encrypted frames aren't supported.", context);
|
2015-04-22 19:22:01 +02:00
|
|
|
throw VersionNotSupportedException();
|
|
|
|
}
|
|
|
|
}
|
2015-12-22 23:54:35 +01:00
|
|
|
|
2020-04-22 19:48:27 +02:00
|
|
|
// add a warning if a frame appears in an ID3v2 tag known not to support it
|
|
|
|
if (version <= 3 && Id3v2FrameIds::isOnlyId3v24Id(version < 3 ? Id3v2FrameIds::convertToLongId(id()) : id())) {
|
|
|
|
diag.emplace_back(DiagLevel::Warning,
|
|
|
|
argsToString("The frame is only supported in ID3v2.4 and newer but the tag's version is ID3v2.", version, '.'), context);
|
|
|
|
} else if (version > 3 && Id3v2FrameIds::isPreId3v24Id(id())) {
|
|
|
|
diag.emplace_back(DiagLevel::Warning,
|
|
|
|
argsToString("The frame is only supported in ID3v2.3 and older but the tag's version is ID3v2.", version, '.'), context);
|
|
|
|
}
|
|
|
|
|
2015-12-22 23:54:35 +01:00
|
|
|
// frame size mustn't be 0
|
2018-03-07 01:17:50 +01:00
|
|
|
if (m_dataSize <= 0) {
|
2018-11-15 22:09:10 +01:00
|
|
|
diag.emplace_back(DiagLevel::Warning, "The frame size is 0.", context);
|
2015-04-22 19:22:01 +02:00
|
|
|
throw InvalidDataException();
|
|
|
|
}
|
2015-12-22 23:54:35 +01:00
|
|
|
|
2015-04-22 19:22:01 +02:00
|
|
|
// parse the data
|
2015-09-19 22:34:07 +02:00
|
|
|
unique_ptr<char[]> buffer;
|
2015-12-22 23:54:35 +01:00
|
|
|
|
|
|
|
// -> decompress data if compressed; otherwise just read it
|
2018-03-07 01:17:50 +01:00
|
|
|
if (isCompressed()) {
|
2015-04-22 19:22:01 +02:00
|
|
|
uLongf decompressedSize = version >= 4 ? reader.readSynchsafeUInt32BE() : reader.readUInt32BE();
|
2018-03-07 01:17:50 +01:00
|
|
|
if (decompressedSize < m_dataSize) {
|
2018-03-05 17:49:29 +01:00
|
|
|
diag.emplace_back(DiagLevel::Critical, "The decompressed size is smaller than the compressed size.", context);
|
2015-04-22 19:22:01 +02:00
|
|
|
throw InvalidDataException();
|
|
|
|
}
|
2018-03-11 22:28:15 +01:00
|
|
|
const auto bufferCompressed = make_unique<char[]>(m_dataSize);
|
2015-09-19 22:34:07 +02:00
|
|
|
reader.read(bufferCompressed.get(), m_dataSize);
|
|
|
|
buffer = make_unique<char[]>(decompressedSize);
|
2018-03-07 01:17:50 +01:00
|
|
|
switch (
|
|
|
|
uncompress(reinterpret_cast<Bytef *>(buffer.get()), &decompressedSize, reinterpret_cast<Bytef *>(bufferCompressed.get()), m_dataSize)) {
|
2015-04-22 19:22:01 +02:00
|
|
|
case Z_MEM_ERROR:
|
2018-03-05 17:49:29 +01:00
|
|
|
diag.emplace_back(DiagLevel::Critical, "Decompressing failed. The source buffer was too small.", context);
|
2015-04-22 19:22:01 +02:00
|
|
|
throw InvalidDataException();
|
|
|
|
case Z_BUF_ERROR:
|
2018-03-05 17:49:29 +01:00
|
|
|
diag.emplace_back(DiagLevel::Critical, "Decompressing failed. The destination buffer was too small.", context);
|
2015-04-22 19:22:01 +02:00
|
|
|
throw InvalidDataException();
|
|
|
|
case Z_DATA_ERROR:
|
2018-03-05 17:49:29 +01:00
|
|
|
diag.emplace_back(DiagLevel::Critical, "Decompressing failed. The input data was corrupted or incomplete.", context);
|
2015-04-22 19:22:01 +02:00
|
|
|
throw InvalidDataException();
|
|
|
|
case Z_OK:
|
2015-12-22 23:54:35 +01:00
|
|
|
break;
|
|
|
|
default:
|
2018-03-05 17:49:29 +01:00
|
|
|
diag.emplace_back(DiagLevel::Critical, "Decompressing failed (unknown reason).", context);
|
2015-12-22 23:54:35 +01:00
|
|
|
throw InvalidDataException();
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
2018-06-02 22:56:08 +02:00
|
|
|
if (decompressedSize > maxId3v2FrameDataSize) {
|
|
|
|
diag.emplace_back(DiagLevel::Critical, "The decompressed data exceeds the maximum supported frame size.", context);
|
|
|
|
throw InvalidDataException();
|
|
|
|
}
|
2019-03-13 19:06:42 +01:00
|
|
|
m_dataSize = static_cast<std::uint32_t>(decompressedSize);
|
2015-04-22 19:22:01 +02:00
|
|
|
} else {
|
2015-09-19 22:34:07 +02:00
|
|
|
buffer = make_unique<char[]>(m_dataSize);
|
|
|
|
reader.read(buffer.get(), m_dataSize);
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
2015-12-22 23:54:35 +01:00
|
|
|
|
2018-07-01 02:04:29 +02:00
|
|
|
// read tag value depending on frame ID/type
|
2018-03-07 01:17:50 +01:00
|
|
|
if (Id3v2FrameIds::isTextFrame(id())) {
|
2018-07-01 02:04:29 +02:00
|
|
|
// parse text encoding byte
|
2019-03-13 19:06:42 +01:00
|
|
|
TagTextEncoding dataEncoding = parseTextEncodingByte(static_cast<std::uint8_t>(*buffer.get()), diag);
|
2018-07-01 02:04:29 +02:00
|
|
|
|
|
|
|
// parse string values (since ID3v2.4 a text frame may contain multiple strings)
|
|
|
|
const char *currentOffset = buffer.get() + 1;
|
|
|
|
for (size_t currentIndex = 1; currentIndex < m_dataSize;) {
|
|
|
|
// determine the next substring
|
|
|
|
const auto substr(parseSubstring(currentOffset, m_dataSize - currentIndex, dataEncoding, false, diag));
|
|
|
|
|
|
|
|
// handle case when string is empty
|
|
|
|
if (!get<1>(substr)) {
|
|
|
|
if (currentIndex == 1) {
|
|
|
|
value().clearDataAndMetadata();
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
2018-07-01 02:04:29 +02:00
|
|
|
currentIndex = static_cast<size_t>(get<2>(substr) - buffer.get());
|
|
|
|
currentOffset = get<2>(substr);
|
|
|
|
continue;
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
2015-12-22 23:54:35 +01:00
|
|
|
|
2018-07-01 02:04:29 +02:00
|
|
|
// determine the TagValue instance to store the value
|
|
|
|
TagValue *const value = [&] {
|
|
|
|
if (this->value().isEmpty()) {
|
|
|
|
return &this->value();
|
|
|
|
}
|
2018-07-01 23:06:36 +02:00
|
|
|
m_additionalValues.emplace_back();
|
|
|
|
return &m_additionalValues.back();
|
2018-07-01 02:04:29 +02:00
|
|
|
}();
|
|
|
|
|
|
|
|
// apply further parsing for some text frame types (eg. convert track number to PositionInSet)
|
|
|
|
if ((version >= 3 && (id() == Id3v2FrameIds::lTrackPosition || id() == Id3v2FrameIds::lDiskPosition))
|
|
|
|
|| (version < 3 && (id() == Id3v2FrameIds::sTrackPosition || id() == Id3v2FrameIds::sDiskPosition))) {
|
|
|
|
// parse the track number or the disk number frame
|
|
|
|
try {
|
|
|
|
if (characterSize(dataEncoding) > 1) {
|
|
|
|
value->assignPosition(PositionInSet(wideStringFromSubstring(substr, dataEncoding)));
|
|
|
|
} else {
|
|
|
|
value->assignPosition(PositionInSet(stringFromSubstring(substr)));
|
|
|
|
}
|
|
|
|
} catch (const ConversionException &) {
|
|
|
|
diag.emplace_back(DiagLevel::Warning, "The value of track/disk position frame is not numeric and will be ignored.", context);
|
|
|
|
}
|
|
|
|
|
|
|
|
} else if ((version >= 3 && id() == Id3v2FrameIds::lLength) || (version < 3 && id() == Id3v2FrameIds::sLength)) {
|
|
|
|
// parse frame contains length
|
|
|
|
try {
|
|
|
|
const auto milliseconds = [&] {
|
|
|
|
if (dataEncoding == TagTextEncoding::Utf16BigEndian || dataEncoding == TagTextEncoding::Utf16LittleEndian) {
|
|
|
|
const auto parsedStringRef = parseSubstring(buffer.get() + 1, m_dataSize - 1, dataEncoding, false, diag);
|
|
|
|
const auto convertedStringData = dataEncoding == TagTextEncoding::Utf16BigEndian
|
|
|
|
? convertUtf16BEToUtf8(get<0>(parsedStringRef), get<1>(parsedStringRef))
|
|
|
|
: convertUtf16LEToUtf8(get<0>(parsedStringRef), get<1>(parsedStringRef));
|
|
|
|
return string(convertedStringData.first.get(), convertedStringData.second);
|
|
|
|
} else { // Latin-1 or UTF-8
|
|
|
|
return stringFromSubstring(substr);
|
|
|
|
}
|
|
|
|
}();
|
|
|
|
value->assignTimeSpan(TimeSpan::fromMilliseconds(stringToNumber<double>(milliseconds)));
|
|
|
|
} catch (const ConversionException &) {
|
|
|
|
diag.emplace_back(DiagLevel::Warning, "The value of the length frame is not numeric and will be ignored.", context);
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
2015-12-22 23:54:35 +01:00
|
|
|
|
2018-07-01 02:04:29 +02:00
|
|
|
} else if ((version >= 3 && id() == Id3v2FrameIds::lGenre) || (version < 3 && id() == Id3v2FrameIds::sGenre)) {
|
|
|
|
// parse genre/content type
|
|
|
|
const auto genreIndex = [&] {
|
|
|
|
if (characterSize(dataEncoding) > 1) {
|
|
|
|
return parseGenreIndex(wideStringFromSubstring(substr, dataEncoding));
|
|
|
|
} else {
|
|
|
|
return parseGenreIndex(stringFromSubstring(substr));
|
|
|
|
}
|
|
|
|
}();
|
|
|
|
if (genreIndex != -1) {
|
|
|
|
// genre is specified as ID3 genre number
|
|
|
|
value->assignStandardGenreIndex(genreIndex);
|
|
|
|
} else {
|
|
|
|
// genre is specified as string
|
|
|
|
value->assignData(get<0>(substr), get<1>(substr), TagDataType::Text, dataEncoding);
|
|
|
|
}
|
2016-02-23 20:33:00 +01:00
|
|
|
} else {
|
2018-07-01 02:04:29 +02:00
|
|
|
// store any other text frames as-is
|
|
|
|
value->assignData(get<0>(substr), get<1>(substr), TagDataType::Text, dataEncoding);
|
2016-02-23 20:33:00 +01:00
|
|
|
}
|
2018-07-01 02:04:29 +02:00
|
|
|
|
|
|
|
currentIndex = static_cast<size_t>(get<2>(substr) - buffer.get());
|
|
|
|
currentOffset = get<2>(substr);
|
|
|
|
}
|
|
|
|
|
|
|
|
// add warning about additional values
|
2018-07-01 23:06:36 +02:00
|
|
|
if (version < 4 && !m_additionalValues.empty()) {
|
|
|
|
diag.emplace_back(
|
|
|
|
DiagLevel::Warning, "Multiple strings found though the tag is pre-ID3v2.4. " + ignoreAdditionalValuesDiagMsg(), context);
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
2015-12-22 23:54:35 +01:00
|
|
|
|
2018-03-07 01:17:50 +01:00
|
|
|
} else if (version >= 3 && id() == Id3v2FrameIds::lCover) {
|
2018-07-01 02:04:29 +02:00
|
|
|
// parse picture frame
|
2019-03-13 19:06:42 +01:00
|
|
|
std::uint8_t type;
|
2018-03-05 17:49:29 +01:00
|
|
|
parsePicture(buffer.get(), m_dataSize, value(), type, diag);
|
2015-09-19 22:34:07 +02:00
|
|
|
setTypeInfo(type);
|
2015-12-22 23:54:35 +01:00
|
|
|
|
2018-03-07 01:17:50 +01:00
|
|
|
} else if (version < 3 && id() == Id3v2FrameIds::sCover) {
|
2018-07-01 02:04:29 +02:00
|
|
|
// parse legacy picutre
|
2019-03-13 19:06:42 +01:00
|
|
|
std::uint8_t type;
|
2018-03-05 17:49:29 +01:00
|
|
|
parseLegacyPicture(buffer.get(), m_dataSize, value(), type, diag);
|
2015-04-22 19:22:01 +02:00
|
|
|
setTypeInfo(type);
|
2015-12-22 23:54:35 +01:00
|
|
|
|
2018-03-07 01:17:50 +01:00
|
|
|
} else if (((version >= 3 && id() == Id3v2FrameIds::lComment) || (version < 3 && id() == Id3v2FrameIds::sComment))
|
|
|
|
|| ((version >= 3 && id() == Id3v2FrameIds::lUnsynchronizedLyrics) || (version < 3 && id() == Id3v2FrameIds::sUnsynchronizedLyrics))) {
|
2018-07-01 02:04:29 +02:00
|
|
|
// parse comment frame or unsynchronized lyrics frame (these two frame types have the same structure)
|
2018-03-05 17:49:29 +01:00
|
|
|
parseComment(buffer.get(), m_dataSize, value(), diag);
|
2015-12-22 23:54:35 +01:00
|
|
|
|
2015-04-22 19:22:01 +02:00
|
|
|
} else {
|
2018-07-01 02:04:29 +02:00
|
|
|
// parse unknown/unsupported frame
|
2015-09-19 22:34:07 +02:00
|
|
|
value().assignData(buffer.get(), m_dataSize, TagDataType::Undefined);
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-12-22 23:54:35 +01:00
|
|
|
/*!
|
|
|
|
* \brief Prepares making.
|
|
|
|
* \returns Returns a Id3v2FrameMaker object which can be used to actually make the frame.
|
|
|
|
* \remarks The field must NOT be mutated after making is prepared when it is intended to actually
|
|
|
|
* make the field using the make method of the returned object.
|
2018-06-03 20:38:32 +02:00
|
|
|
* \throws Throws TagParser::Failure or a derived exception when a making
|
2015-12-22 23:54:35 +01:00
|
|
|
* error occurs.
|
|
|
|
*
|
|
|
|
* This method might be useful when it is necessary to know the size of the field before making it.
|
|
|
|
*/
|
2019-03-13 19:06:42 +01:00
|
|
|
Id3v2FrameMaker Id3v2Frame::prepareMaking(std::uint8_t version, Diagnostics &diag)
|
2015-12-22 23:54:35 +01:00
|
|
|
{
|
2018-03-05 17:49:29 +01:00
|
|
|
return Id3v2FrameMaker(*this, version, diag);
|
2015-12-22 23:54:35 +01:00
|
|
|
}
|
|
|
|
|
2015-04-22 19:22:01 +02:00
|
|
|
/*!
|
|
|
|
* \brief Writes the frame to a stream using the specified \a writer and the
|
2015-12-22 23:54:35 +01:00
|
|
|
* specified ID3v2 \a version.
|
2015-04-22 19:22:01 +02:00
|
|
|
*
|
|
|
|
* \throws Throws std::ios_base::failure when an IO error occurs.
|
2018-06-03 20:38:32 +02:00
|
|
|
* \throws Throws TagParser::Failure or a derived exception when a making
|
2015-04-22 19:22:01 +02:00
|
|
|
* error occurs.
|
|
|
|
*/
|
2019-03-13 19:06:42 +01:00
|
|
|
void Id3v2Frame::make(BinaryWriter &writer, std::uint8_t version, Diagnostics &diag)
|
2015-04-22 19:22:01 +02:00
|
|
|
{
|
2018-03-05 17:49:29 +01:00
|
|
|
prepareMaking(version, diag).make(writer);
|
2015-12-22 23:54:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
2021-01-30 18:25:46 +01:00
|
|
|
* \brief Clears ID3v2-specific values. Called via clear() and clearValue().
|
2015-12-22 23:54:35 +01:00
|
|
|
*/
|
2021-01-30 18:25:46 +01:00
|
|
|
void Id3v2Frame::internallyClearValue()
|
|
|
|
{
|
|
|
|
value().clearDataAndMetadata();
|
|
|
|
m_additionalValues.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Clears ID3v2-specific values. Called via clear().
|
|
|
|
*/
|
|
|
|
void Id3v2Frame::internallyClearFurtherData()
|
2015-12-22 23:54:35 +01:00
|
|
|
{
|
|
|
|
m_flag = 0;
|
|
|
|
m_group = 0;
|
|
|
|
m_parsedVersion = 0;
|
|
|
|
m_dataSize = 0;
|
|
|
|
m_totalSize = 0;
|
|
|
|
m_padding = false;
|
2018-07-01 23:06:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Returns a diag message that additional values are ignored.
|
|
|
|
*/
|
|
|
|
std::string Id3v2Frame::ignoreAdditionalValuesDiagMsg() const
|
|
|
|
{
|
|
|
|
if (m_additionalValues.size() == 1) {
|
|
|
|
return argsToString("Additional value \"", m_additionalValues.front().toString(TagTextEncoding::Utf8), "\" is supposed to be ignored.");
|
|
|
|
}
|
|
|
|
return argsToString("Additional values ", DiagMessage::formatList(TagValue::toStrings(m_additionalValues)), " are supposed to be ignored.");
|
2015-12-22 23:54:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
2018-06-03 20:38:32 +02:00
|
|
|
* \class TagParser::Id3v2FrameMaker
|
2015-12-22 23:54:35 +01:00
|
|
|
* \brief The Id3v2FrameMaker class helps making ID3v2 frames.
|
|
|
|
* It allows to calculate the required size.
|
|
|
|
* \sa See Id3v2FrameMaker::prepareMaking() for more information.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Prepares making the specified \a frame.
|
|
|
|
* \sa See Id3v2Frame::prepareMaking() for more information.
|
|
|
|
*/
|
2019-03-13 19:06:42 +01:00
|
|
|
Id3v2FrameMaker::Id3v2FrameMaker(Id3v2Frame &frame, std::uint8_t version, Diagnostics &diag)
|
2018-03-07 01:17:50 +01:00
|
|
|
: m_frame(frame)
|
|
|
|
, m_frameId(m_frame.id())
|
|
|
|
, m_version(version)
|
2015-12-22 23:54:35 +01:00
|
|
|
{
|
2018-07-11 11:08:48 +02:00
|
|
|
const string context("making " % m_frame.idToString() + " frame");
|
2015-12-22 23:54:35 +01:00
|
|
|
|
2019-02-13 20:19:46 +01:00
|
|
|
// validate frame's configuration
|
2018-03-07 01:17:50 +01:00
|
|
|
if (m_frame.isEncrypted()) {
|
2018-03-05 17:49:29 +01:00
|
|
|
diag.emplace_back(DiagLevel::Critical, "Cannot make an encrypted frame (isn't supported by this tagging library).", context);
|
2015-04-22 19:22:01 +02:00
|
|
|
throw InvalidDataException();
|
|
|
|
}
|
2018-03-07 01:17:50 +01:00
|
|
|
if (m_frame.hasPaddingReached()) {
|
2018-03-05 17:49:29 +01:00
|
|
|
diag.emplace_back(DiagLevel::Critical, "Cannot make a frame which is marked as padding.", context);
|
2015-04-22 19:22:01 +02:00
|
|
|
throw InvalidDataException();
|
|
|
|
}
|
2018-03-07 01:17:50 +01:00
|
|
|
if (version < 3 && m_frame.isCompressed()) {
|
2018-03-05 17:49:29 +01:00
|
|
|
diag.emplace_back(DiagLevel::Warning, "Compression is not supported by the version of ID3v2 and won't be applied.", context);
|
2015-12-22 23:54:35 +01:00
|
|
|
}
|
2018-03-07 01:17:50 +01:00
|
|
|
if (version < 3 && (m_frame.flag() || m_frame.group())) {
|
|
|
|
diag.emplace_back(DiagLevel::Warning,
|
|
|
|
"The existing flag and group information is not supported by the version of ID3v2 and will be ignored/discarted.", context);
|
2015-12-22 23:54:35 +01:00
|
|
|
}
|
2019-02-13 20:19:46 +01:00
|
|
|
|
|
|
|
// get non-empty, assigned values
|
|
|
|
vector<const TagValue *> values;
|
|
|
|
values.reserve(1 + frame.additionalValues().size());
|
|
|
|
if (!frame.value().isEmpty()) {
|
|
|
|
values.emplace_back(&frame.value());
|
|
|
|
}
|
|
|
|
for (const auto &value : frame.additionalValues()) {
|
|
|
|
if (!value.isEmpty()) {
|
|
|
|
values.emplace_back(&value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// validate assigned values
|
|
|
|
if (values.empty()) {
|
|
|
|
throw NoDataProvidedException();
|
|
|
|
// note: This is not really an issue because in the case we're not provided with any value here just means that the field
|
|
|
|
// is supposed to be removed. So don't add any diagnostic messages here.
|
|
|
|
}
|
2018-07-01 23:06:36 +02:00
|
|
|
const bool isTextFrame = Id3v2FrameIds::isTextFrame(m_frameId);
|
|
|
|
if (values.size() != 1) {
|
|
|
|
if (!isTextFrame) {
|
|
|
|
diag.emplace_back(DiagLevel::Critical, "Multiple values are not supported for non-text-frames.", context);
|
|
|
|
throw InvalidDataException();
|
|
|
|
} else if (version < 4) {
|
|
|
|
diag.emplace_back(
|
|
|
|
DiagLevel::Warning, "Multiple strings assigned to pre-ID3v2.4 text frame. " + frame.ignoreAdditionalValuesDiagMsg(), context);
|
|
|
|
}
|
|
|
|
}
|
2015-12-22 23:54:35 +01:00
|
|
|
|
|
|
|
// convert frame ID if necessary
|
2018-03-07 01:17:50 +01:00
|
|
|
if (version >= 3) {
|
|
|
|
if (Id3v2FrameIds::isShortId(m_frameId)) {
|
2015-12-22 23:54:35 +01:00
|
|
|
// try to convert the short frame ID to its long equivalent
|
2018-03-07 01:17:50 +01:00
|
|
|
if (!(m_frameId = Id3v2FrameIds::convertToLongId(m_frameId))) {
|
|
|
|
diag.emplace_back(DiagLevel::Critical,
|
|
|
|
"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.",
|
|
|
|
context);
|
2015-04-22 19:22:01 +02:00
|
|
|
throw InvalidDataException();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
2018-03-07 01:17:50 +01:00
|
|
|
if (Id3v2FrameIds::isLongId(m_frameId)) {
|
2015-12-22 23:54:35 +01:00
|
|
|
// try to convert the long frame ID to its short equivalent
|
2018-03-07 01:17:50 +01:00
|
|
|
if (!(m_frameId = Id3v2FrameIds::convertToShortId(m_frameId))) {
|
|
|
|
diag.emplace_back(DiagLevel::Critical,
|
|
|
|
"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.",
|
|
|
|
context);
|
2015-04-22 19:22:01 +02:00
|
|
|
throw InvalidDataException();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-12-22 23:54:35 +01:00
|
|
|
|
2020-04-22 19:48:27 +02:00
|
|
|
// add a warning if we're writing the frame for an ID3v2 tag known not to support it
|
|
|
|
if (version <= 3 && Id3v2FrameIds::isOnlyId3v24Id(version < 3 ? Id3v2FrameIds::convertToLongId(m_frameId) : m_frameId)) {
|
|
|
|
diag.emplace_back(DiagLevel::Warning,
|
|
|
|
argsToString("The frame is only supported in ID3v2.4 and newer but version of the tag being written is ID3v2.", version,
|
|
|
|
". The frame is written nevertheless but other tools might not be able to deal with it."),
|
|
|
|
context);
|
|
|
|
} else if (version > 3 && Id3v2FrameIds::isPreId3v24Id(m_frameId)) {
|
|
|
|
diag.emplace_back(DiagLevel::Warning,
|
|
|
|
argsToString("The frame is only supported in ID3v2.3 and older but version of the tag being written is ID3v2.", version,
|
|
|
|
". The frame is written nevertheless but other tools might not be able to deal with it."),
|
|
|
|
context);
|
|
|
|
}
|
|
|
|
|
2015-12-22 23:54:35 +01:00
|
|
|
// make actual data depending on the frame ID
|
2015-04-22 19:22:01 +02:00
|
|
|
try {
|
2018-07-01 23:06:36 +02:00
|
|
|
if (isTextFrame) {
|
|
|
|
// make text frame
|
|
|
|
vector<string> substrings;
|
|
|
|
substrings.reserve(1 + frame.additionalValues().size());
|
|
|
|
TagTextEncoding encoding = TagTextEncoding::Unspecified;
|
|
|
|
|
2018-03-07 01:17:50 +01:00
|
|
|
if ((version >= 3 && (m_frameId == Id3v2FrameIds::lTrackPosition || m_frameId == Id3v2FrameIds::lDiskPosition))
|
2018-06-02 23:08:14 +02:00
|
|
|
|| (version < 3 && (m_frameId == Id3v2FrameIds::sTrackPosition || m_frameId == Id3v2FrameIds::sDiskPosition))) {
|
2018-06-02 19:34:07 +02:00
|
|
|
// make track number or disk number frame
|
2018-07-01 23:06:36 +02:00
|
|
|
encoding = version >= 4 ? TagTextEncoding::Utf8 : TagTextEncoding::Latin1;
|
|
|
|
for (const auto *const value : values) {
|
|
|
|
// convert the position to string
|
|
|
|
substrings.emplace_back(value->toString(encoding));
|
|
|
|
// warn if value is no valid position (although we just store a string after all)
|
|
|
|
if (value->type() == TagDataType::PositionInSet) {
|
|
|
|
continue;
|
|
|
|
}
|
2018-05-31 00:23:01 +02:00
|
|
|
try {
|
2018-07-01 23:06:36 +02:00
|
|
|
value->toPositionInSet();
|
2018-05-31 00:23:01 +02:00
|
|
|
} catch (const ConversionException &) {
|
|
|
|
diag.emplace_back(DiagLevel::Warning,
|
2018-07-01 23:06:36 +02:00
|
|
|
argsToString("The track/disk number \"", substrings.back(), "\" is not of the expected form, eg. \"4/10\"."), context);
|
2018-05-31 00:23:01 +02:00
|
|
|
}
|
|
|
|
}
|
2018-07-01 23:06:36 +02:00
|
|
|
|
2018-03-07 01:17:50 +01:00
|
|
|
} else if ((version >= 3 && m_frameId == Id3v2FrameIds::lLength) || (version < 3 && m_frameId == Id3v2FrameIds::sLength)) {
|
2018-06-02 19:34:07 +02:00
|
|
|
// make length frame
|
2018-07-01 23:06:36 +02:00
|
|
|
encoding = TagTextEncoding::Latin1;
|
|
|
|
for (const auto *const value : values) {
|
|
|
|
const auto duration(value->toTimeSpan());
|
|
|
|
if (duration.isNegative()) {
|
|
|
|
diag.emplace_back(DiagLevel::Critical, argsToString("Assigned duration \"", duration.toString(), "\" is negative."), context);
|
|
|
|
throw InvalidDataException();
|
|
|
|
}
|
2019-06-10 22:49:11 +02:00
|
|
|
substrings.emplace_back(numberToString(static_cast<std::uint64_t>(duration.totalMilliseconds())));
|
2018-06-02 19:31:39 +02:00
|
|
|
}
|
2018-07-01 23:06:36 +02:00
|
|
|
|
2015-04-22 19:22:01 +02:00
|
|
|
} else {
|
2018-07-01 23:06:36 +02:00
|
|
|
// make standard genre index and other text frames
|
|
|
|
// -> find text encoding suitable for all assigned values
|
|
|
|
for (const auto *const value : values) {
|
|
|
|
switch (encoding) {
|
|
|
|
case TagTextEncoding::Unspecified:
|
|
|
|
switch (value->type()) {
|
|
|
|
case TagDataType::StandardGenreIndex:
|
|
|
|
encoding = TagTextEncoding::Latin1;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
encoding = value->dataEncoding();
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case TagTextEncoding::Latin1:
|
|
|
|
switch (value->dataEncoding()) {
|
|
|
|
case TagTextEncoding::Latin1:
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
encoding = value->dataEncoding();
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (version <= 3 && encoding == TagTextEncoding::Utf8) {
|
|
|
|
encoding = TagTextEncoding::Utf16LittleEndian;
|
|
|
|
}
|
|
|
|
// -> format values
|
|
|
|
for (const auto *const value : values) {
|
|
|
|
if ((value->type() == TagDataType::StandardGenreIndex)
|
|
|
|
&& ((version >= 3 && m_frameId == Id3v2FrameIds::lGenre) || (version < 3 && m_frameId == Id3v2FrameIds::sGenre))) {
|
|
|
|
// make standard genere index
|
2019-06-10 22:49:11 +02:00
|
|
|
substrings.emplace_back(numberToString(value->toStandardGenreIndex()));
|
2018-07-01 23:06:36 +02:00
|
|
|
|
|
|
|
} else {
|
|
|
|
// make other text frame
|
|
|
|
substrings.emplace_back(value->toString(encoding));
|
|
|
|
}
|
2017-05-20 23:34:45 +02:00
|
|
|
}
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
2015-12-22 23:54:35 +01:00
|
|
|
|
2018-07-01 23:06:36 +02:00
|
|
|
// concatenate substrings using encoding specific byte order mark and termination
|
|
|
|
const auto terminationLength = (encoding == TagTextEncoding::Utf16BigEndian || encoding == TagTextEncoding::Utf16LittleEndian) ? 2u : 1u;
|
|
|
|
const auto byteOrderMark = [&] {
|
|
|
|
switch (encoding) {
|
|
|
|
case TagTextEncoding::Utf16LittleEndian:
|
|
|
|
return string({ '\xFF', '\xFE' });
|
|
|
|
case TagTextEncoding::Utf16BigEndian:
|
|
|
|
return string({ '\xFE', '\xFF' });
|
|
|
|
default:
|
|
|
|
return string();
|
|
|
|
}
|
|
|
|
}();
|
|
|
|
const auto concatenatedSubstrings = joinStrings(substrings, string(), false, byteOrderMark, string(terminationLength, '\0'));
|
|
|
|
|
|
|
|
// write text encoding byte and concatenated strings to data buffer
|
2019-03-13 19:06:42 +01:00
|
|
|
m_data = make_unique<char[]>(m_decompressedSize = static_cast<std::uint32_t>(1 + concatenatedSubstrings.size()));
|
2018-07-01 23:06:36 +02:00
|
|
|
m_data[0] = static_cast<char>(Id3v2Frame::makeTextEncodingByte(encoding));
|
|
|
|
concatenatedSubstrings.copy(&m_data[1], concatenatedSubstrings.size());
|
|
|
|
|
2018-03-07 01:17:50 +01:00
|
|
|
} else if ((version >= 3 && m_frameId == Id3v2FrameIds::lCover) || (version < 3 && m_frameId == Id3v2FrameIds::sCover)) {
|
2018-06-02 19:34:07 +02:00
|
|
|
// make picture frame
|
2019-06-01 22:26:16 +02:00
|
|
|
m_frame.makePicture(m_data, m_decompressedSize, *values.front(), m_frame.isTypeInfoAssigned() ? m_frame.typeInfo() : 0, version, diag);
|
2015-12-22 23:54:35 +01:00
|
|
|
|
2018-03-07 01:17:50 +01:00
|
|
|
} else if (((version >= 3 && m_frameId == Id3v2FrameIds::lComment) || (version < 3 && m_frameId == Id3v2FrameIds::sComment))
|
|
|
|
|| ((version >= 3 && m_frameId == Id3v2FrameIds::lUnsynchronizedLyrics)
|
2019-05-04 21:03:09 +02:00
|
|
|
|| (version < 3 && m_frameId == Id3v2FrameIds::sUnsynchronizedLyrics))) {
|
2018-06-02 19:34:07 +02:00
|
|
|
// make comment frame or the unsynchronized lyrics frame
|
2018-07-01 23:06:36 +02:00
|
|
|
m_frame.makeComment(m_data, m_decompressedSize, *values.front(), version, diag);
|
2015-12-22 23:54:35 +01:00
|
|
|
|
2018-03-07 01:17:50 +01:00
|
|
|
} else {
|
2018-06-02 19:34:07 +02:00
|
|
|
// make unknown frame
|
2018-07-01 23:06:36 +02:00
|
|
|
const auto &value(*values.front());
|
2018-06-02 22:56:08 +02:00
|
|
|
if (value.dataSize() > maxId3v2FrameDataSize) {
|
|
|
|
diag.emplace_back(DiagLevel::Critical, "Assigned value exceeds maximum size.", context);
|
|
|
|
throw InvalidDataException();
|
|
|
|
}
|
2019-03-13 19:06:42 +01:00
|
|
|
m_data = make_unique<char[]>(m_decompressedSize = static_cast<std::uint32_t>(value.dataSize()));
|
2018-06-02 19:34:07 +02:00
|
|
|
copy(value.dataPointer(), value.dataPointer() + m_decompressedSize, m_data.get());
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
2018-03-07 01:17:50 +01:00
|
|
|
} catch (const ConversionException &) {
|
2018-06-02 19:29:43 +02:00
|
|
|
try {
|
2018-07-01 23:06:36 +02:00
|
|
|
const auto valuesAsString = TagValue::toStrings(values);
|
2018-06-02 19:29:43 +02:00
|
|
|
diag.emplace_back(DiagLevel::Critical,
|
2018-07-01 23:06:36 +02:00
|
|
|
argsToString("Assigned value(s) \"", DiagMessage::formatList(valuesAsString), "\" can not be converted appropriately."), context);
|
2018-06-02 19:29:43 +02:00
|
|
|
} catch (const ConversionException &) {
|
2018-07-01 23:06:36 +02:00
|
|
|
diag.emplace_back(DiagLevel::Critical, "Assigned value(s) can not be converted appropriately.", context);
|
2018-06-02 19:29:43 +02:00
|
|
|
}
|
2015-04-22 19:22:01 +02:00
|
|
|
throw InvalidDataException();
|
|
|
|
}
|
2015-12-22 23:54:35 +01:00
|
|
|
|
|
|
|
// apply compression if frame should be compressed
|
2018-03-07 01:17:50 +01:00
|
|
|
if (version >= 3 && m_frame.isCompressed()) {
|
2018-06-02 22:56:08 +02:00
|
|
|
auto compressedSize = compressBound(m_decompressedSize);
|
|
|
|
auto compressedData = make_unique<char[]>(compressedSize);
|
|
|
|
switch (compress(reinterpret_cast<Bytef *>(compressedData.get()), reinterpret_cast<uLongf *>(&compressedSize),
|
2018-03-07 01:17:50 +01:00
|
|
|
reinterpret_cast<Bytef *>(m_data.get()), m_decompressedSize)) {
|
2015-04-22 19:22:01 +02:00
|
|
|
case Z_MEM_ERROR:
|
2018-03-05 17:49:29 +01:00
|
|
|
diag.emplace_back(DiagLevel::Critical, "Decompressing failed. The source buffer was too small.", context);
|
2015-04-22 19:22:01 +02:00
|
|
|
throw InvalidDataException();
|
|
|
|
case Z_BUF_ERROR:
|
2018-03-05 17:49:29 +01:00
|
|
|
diag.emplace_back(DiagLevel::Critical, "Decompressing failed. The destination buffer was too small.", context);
|
2015-04-22 19:22:01 +02:00
|
|
|
throw InvalidDataException();
|
2018-03-07 01:17:50 +01:00
|
|
|
case Z_OK:;
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
2018-06-02 22:56:08 +02:00
|
|
|
if (compressedSize > maxId3v2FrameDataSize) {
|
|
|
|
diag.emplace_back(DiagLevel::Critical, "Compressed size exceeds maximum data size.", context);
|
|
|
|
throw InvalidDataException();
|
|
|
|
}
|
2015-12-22 23:54:35 +01:00
|
|
|
m_data.swap(compressedData);
|
2019-03-13 19:06:42 +01:00
|
|
|
m_dataSize = static_cast<std::uint32_t>(compressedSize);
|
2015-08-16 23:39:42 +02:00
|
|
|
} else {
|
2015-12-22 23:54:35 +01:00
|
|
|
m_dataSize = m_decompressedSize;
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
2015-12-22 23:54:35 +01:00
|
|
|
|
|
|
|
// calculate required size
|
|
|
|
// -> data size
|
|
|
|
m_requiredSize = m_dataSize;
|
2018-03-07 01:17:50 +01:00
|
|
|
if (version < 3) {
|
2015-12-22 23:54:35 +01:00
|
|
|
// -> header size
|
2016-08-05 01:22:46 +02:00
|
|
|
m_requiredSize += 6;
|
2015-04-22 19:22:01 +02:00
|
|
|
} else {
|
2015-12-22 23:54:35 +01:00
|
|
|
// -> header size
|
|
|
|
m_requiredSize += 10;
|
|
|
|
// -> group byte
|
2018-03-07 01:17:50 +01:00
|
|
|
if (m_frame.hasGroupInformation()) {
|
2015-12-22 23:54:35 +01:00
|
|
|
m_requiredSize += 1;
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
2015-12-22 23:54:35 +01:00
|
|
|
// -> decompressed size
|
2018-03-07 01:17:50 +01:00
|
|
|
if (version >= 3 && m_frame.isCompressed()) {
|
2015-12-22 23:54:35 +01:00
|
|
|
m_requiredSize += 4;
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
2015-12-22 23:54:35 +01:00
|
|
|
* \brief Saves the frame (specified when constructing the object) using
|
|
|
|
* the specified \a writer.
|
|
|
|
* \throws Throws std::ios_base::failure when an IO error occurs.
|
|
|
|
* \throws Throws Assumes the data is already validated and thus does NOT
|
2018-06-03 20:38:32 +02:00
|
|
|
* throw TagParser::Failure or a derived exception.
|
2015-04-22 19:22:01 +02:00
|
|
|
*/
|
2015-12-22 23:54:35 +01:00
|
|
|
void Id3v2FrameMaker::make(BinaryWriter &writer)
|
2015-04-22 19:22:01 +02:00
|
|
|
{
|
2018-03-07 01:17:50 +01:00
|
|
|
if (m_version < 3) {
|
2015-12-22 23:54:35 +01:00
|
|
|
writer.writeUInt24BE(m_frameId);
|
|
|
|
writer.writeUInt24BE(m_dataSize);
|
|
|
|
} else {
|
|
|
|
writer.writeUInt32BE(m_frameId);
|
2018-03-07 01:17:50 +01:00
|
|
|
if (m_version >= 4) {
|
2015-12-22 23:54:35 +01:00
|
|
|
writer.writeSynchsafeUInt32BE(m_dataSize);
|
|
|
|
} else {
|
|
|
|
writer.writeUInt32BE(m_dataSize);
|
|
|
|
}
|
|
|
|
writer.writeUInt16BE(m_frame.flag());
|
2018-03-07 01:17:50 +01:00
|
|
|
if (m_frame.hasGroupInformation()) {
|
2015-12-22 23:54:35 +01:00
|
|
|
writer.writeByte(m_frame.group());
|
|
|
|
}
|
2018-03-07 01:17:50 +01:00
|
|
|
if (m_version >= 3 && m_frame.isCompressed()) {
|
|
|
|
if (m_version >= 4) {
|
2015-12-22 23:54:35 +01:00
|
|
|
writer.writeSynchsafeUInt32BE(m_decompressedSize);
|
|
|
|
} else {
|
|
|
|
writer.writeUInt32BE(m_decompressedSize);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
writer.write(m_data.get(), m_dataSize);
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Returns the text encoding for the specified \a textEncodingByte.
|
|
|
|
*
|
|
|
|
* If the \a textEncodingByte doesn't match any encoding TagTextEncoding::Latin1 is
|
|
|
|
* returned and a parsing notification is added.
|
|
|
|
*/
|
2019-03-13 19:06:42 +01:00
|
|
|
TagTextEncoding Id3v2Frame::parseTextEncodingByte(std::uint8_t textEncodingByte, Diagnostics &diag)
|
2015-04-22 19:22:01 +02:00
|
|
|
{
|
2018-03-07 01:17:50 +01:00
|
|
|
switch (textEncodingByte) {
|
2017-05-20 23:34:45 +02:00
|
|
|
case Id3v2TextEncodingBytes::Ascii:
|
2015-04-22 19:22:01 +02:00
|
|
|
return TagTextEncoding::Latin1;
|
2017-05-20 23:34:45 +02:00
|
|
|
case Id3v2TextEncodingBytes::Utf16WithBom:
|
2015-04-22 19:22:01 +02:00
|
|
|
return TagTextEncoding::Utf16LittleEndian;
|
2017-05-20 23:34:45 +02:00
|
|
|
case Id3v2TextEncodingBytes::Utf16BigEndianWithoutBom:
|
2015-04-22 19:22:01 +02:00
|
|
|
return TagTextEncoding::Utf16BigEndian;
|
2017-05-20 23:34:45 +02:00
|
|
|
case Id3v2TextEncodingBytes::Utf8:
|
2015-04-22 19:22:01 +02:00
|
|
|
return TagTextEncoding::Utf8;
|
|
|
|
default:
|
2018-03-07 01:17:50 +01:00
|
|
|
diag.emplace_back(
|
2018-07-11 11:08:48 +02:00
|
|
|
DiagLevel::Warning, "The charset of the frame is invalid. Latin-1 will be used.", "parsing encoding of frame " + idToString());
|
2015-04-22 19:22:01 +02:00
|
|
|
return TagTextEncoding::Latin1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Returns a text encoding byte for the specified \a textEncoding.
|
|
|
|
*/
|
2019-03-13 19:06:42 +01:00
|
|
|
std::uint8_t Id3v2Frame::makeTextEncodingByte(TagTextEncoding textEncoding)
|
2015-04-22 19:22:01 +02:00
|
|
|
{
|
2018-03-07 01:17:50 +01:00
|
|
|
switch (textEncoding) {
|
2015-04-22 19:22:01 +02:00
|
|
|
case TagTextEncoding::Latin1:
|
2017-05-20 23:34:45 +02:00
|
|
|
return Id3v2TextEncodingBytes::Ascii;
|
2015-04-22 19:22:01 +02:00
|
|
|
case TagTextEncoding::Utf8:
|
2017-05-20 23:34:45 +02:00
|
|
|
return Id3v2TextEncodingBytes::Utf8;
|
2015-04-22 19:22:01 +02:00
|
|
|
case TagTextEncoding::Utf16LittleEndian:
|
2017-05-20 23:34:45 +02:00
|
|
|
return Id3v2TextEncodingBytes::Utf16WithBom;
|
2015-04-22 19:22:01 +02:00
|
|
|
case TagTextEncoding::Utf16BigEndian:
|
2017-05-20 23:34:45 +02:00
|
|
|
return Id3v2TextEncodingBytes::Utf16WithBom;
|
2015-04-22 19:22:01 +02:00
|
|
|
default:
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
2018-07-01 02:04:29 +02:00
|
|
|
* \brief Parses a substring from the specified \a buffer.
|
2015-04-22 19:22:01 +02:00
|
|
|
*
|
|
|
|
* This method ensures that byte order marks and termination characters for the specified \a encoding are omitted.
|
2021-07-02 03:00:50 +02:00
|
|
|
* It might add a warning if the substring is not terminated.
|
2015-04-22 19:22:01 +02:00
|
|
|
*
|
|
|
|
* \param buffer Specifies a pointer to the possibly terminated string.
|
|
|
|
* \param bufferSize Specifies the size of the string in byte.
|
|
|
|
* \param encoding Specifies the encoding of the string. Might be adjusted if a byte order marks is found.
|
2018-03-05 17:49:29 +01:00
|
|
|
* \param addWarnings Specifies whether warnings should be added if the string is not terminated.
|
2015-04-22 19:22:01 +02:00
|
|
|
* \returns Returns the start offset, the length of the string (without termination) and the end offset (after termination).
|
|
|
|
* \remarks The length is always returned as the number of bytes, not as the number of characters (makes a difference for
|
2017-05-18 02:26:25 +02:00
|
|
|
* Unicode encodings).
|
2015-04-22 19:22:01 +02:00
|
|
|
*/
|
2018-03-07 01:17:50 +01:00
|
|
|
tuple<const char *, size_t, const char *> Id3v2Frame::parseSubstring(
|
|
|
|
const char *buffer, std::size_t bufferSize, TagTextEncoding &encoding, bool addWarnings, Diagnostics &diag)
|
2015-04-22 19:22:01 +02:00
|
|
|
{
|
|
|
|
tuple<const char *, size_t, const char *> res(buffer, 0, buffer + bufferSize);
|
2018-03-07 01:17:50 +01:00
|
|
|
switch (encoding) {
|
2017-05-20 23:34:45 +02:00
|
|
|
case TagTextEncoding::Unspecified:
|
|
|
|
case TagTextEncoding::Latin1:
|
|
|
|
case TagTextEncoding::Utf8: {
|
2019-06-10 22:49:11 +02:00
|
|
|
if ((bufferSize >= 3) && (BE::toUInt24(buffer) == 0x00EFBBBF)) {
|
2018-03-07 01:17:50 +01:00
|
|
|
if (encoding == TagTextEncoding::Latin1) {
|
|
|
|
diag.emplace_back(DiagLevel::Critical, "Denoted character set is Latin-1 but an UTF-8 BOM is present - assuming UTF-8.",
|
2018-07-11 11:08:48 +02:00
|
|
|
"parsing frame " + idToString());
|
2017-05-20 23:34:45 +02:00
|
|
|
encoding = TagTextEncoding::Utf8;
|
|
|
|
}
|
|
|
|
get<0>(res) += 3;
|
|
|
|
}
|
|
|
|
const char *pos = get<0>(res);
|
2018-03-07 01:17:50 +01:00
|
|
|
for (; *pos != 0x00; ++pos) {
|
|
|
|
if (pos < get<2>(res)) {
|
2017-05-20 23:34:45 +02:00
|
|
|
++get<1>(res);
|
|
|
|
} else {
|
2018-03-07 01:17:50 +01:00
|
|
|
if (addWarnings) {
|
|
|
|
diag.emplace_back(
|
2018-07-11 11:08:48 +02:00
|
|
|
DiagLevel::Warning, "String in frame is not terminated properly.", "parsing termination of frame " + idToString());
|
2017-05-20 23:34:45 +02:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
get<2>(res) = pos + 1;
|
|
|
|
break;
|
|
|
|
}
|
2015-04-22 19:22:01 +02:00
|
|
|
case TagTextEncoding::Utf16BigEndian:
|
|
|
|
case TagTextEncoding::Utf16LittleEndian: {
|
2018-03-07 01:17:50 +01:00
|
|
|
if (bufferSize >= 2) {
|
2019-06-10 22:49:11 +02:00
|
|
|
switch (LE::toUInt16(buffer)) {
|
2017-05-20 23:34:45 +02:00
|
|
|
case 0xFEFF:
|
2018-03-07 01:17:50 +01:00
|
|
|
if (encoding == TagTextEncoding::Utf16BigEndian) {
|
|
|
|
diag.emplace_back(DiagLevel::Critical,
|
|
|
|
"Denoted character set is UTF-16 Big Endian but UTF-16 Little Endian BOM is present - assuming UTF-16 LE.",
|
2018-07-11 11:08:48 +02:00
|
|
|
"parsing frame " + idToString());
|
2016-11-13 23:57:59 +01:00
|
|
|
encoding = TagTextEncoding::Utf16LittleEndian;
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
2016-11-13 23:57:59 +01:00
|
|
|
get<0>(res) += 2;
|
2017-05-20 23:34:45 +02:00
|
|
|
break;
|
|
|
|
case 0xFFFE:
|
|
|
|
encoding = TagTextEncoding::Utf16BigEndian;
|
2016-11-13 23:57:59 +01:00
|
|
|
get<0>(res) += 2;
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
2016-11-13 23:57:59 +01:00
|
|
|
}
|
2019-03-13 19:06:42 +01:00
|
|
|
const std::uint16_t *pos = reinterpret_cast<const std::uint16_t *>(get<0>(res));
|
2018-03-07 01:17:50 +01:00
|
|
|
for (; *pos != 0x0000; ++pos) {
|
2019-03-13 19:06:42 +01:00
|
|
|
if (pos < reinterpret_cast<const std::uint16_t *>(get<2>(res))) {
|
2016-11-13 23:57:59 +01:00
|
|
|
get<1>(res) += 2;
|
|
|
|
} else {
|
2018-03-07 01:17:50 +01:00
|
|
|
if (addWarnings) {
|
|
|
|
diag.emplace_back(
|
2018-07-11 11:08:48 +02:00
|
|
|
DiagLevel::Warning, "Wide string in frame is not terminated properly.", "parsing termination of frame " + idToString());
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
2016-11-13 23:57:59 +01:00
|
|
|
break;
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
|
|
|
}
|
2017-05-20 23:34:45 +02:00
|
|
|
get<2>(res) = reinterpret_cast<const char *>(pos + 1);
|
2016-11-13 23:57:59 +01:00
|
|
|
break;
|
|
|
|
}
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
2018-07-01 02:04:29 +02:00
|
|
|
* \brief Parses a substring from the specified \a buffer.
|
2015-04-22 19:22:01 +02:00
|
|
|
*
|
2015-12-22 23:54:35 +01:00
|
|
|
* Same as Id3v2Frame::parseSubstring() but returns the substring as string object.
|
2015-04-22 19:22:01 +02:00
|
|
|
*/
|
2018-03-05 17:49:29 +01:00
|
|
|
string Id3v2Frame::parseString(const char *buffer, size_t dataSize, TagTextEncoding &encoding, bool addWarnings, Diagnostics &diag)
|
2015-04-22 19:22:01 +02:00
|
|
|
{
|
2018-07-01 02:04:29 +02:00
|
|
|
return stringFromSubstring(parseSubstring(buffer, dataSize, encoding, addWarnings, diag));
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
2018-07-01 02:04:29 +02:00
|
|
|
* \brief Parses a substring from the specified \a buffer.
|
2015-04-22 19:22:01 +02:00
|
|
|
*
|
2016-11-13 23:56:49 +01:00
|
|
|
* Same as Id3v2Frame::parseSubstring() but returns the substring as u16string object
|
|
|
|
*
|
|
|
|
* \remarks Converts byte order to match host byte order (otherwise it wouldn't make much sense to use the resulting u16string).
|
2015-04-22 19:22:01 +02:00
|
|
|
*/
|
2018-03-05 17:49:29 +01:00
|
|
|
u16string Id3v2Frame::parseWideString(const char *buffer, size_t dataSize, TagTextEncoding &encoding, bool addWarnings, Diagnostics &diag)
|
2015-04-22 19:22:01 +02:00
|
|
|
{
|
2018-07-01 02:04:29 +02:00
|
|
|
return wideStringFromSubstring(parseSubstring(buffer, dataSize, encoding, addWarnings, diag), encoding);
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Parses a byte order mark from the specified \a buffer.
|
|
|
|
* \param buffer Specifies the buffer holding the byte order mark.
|
|
|
|
* \param maxSize Specifies the maximal number of bytes to read from the buffer.
|
|
|
|
* \param encoding Specifies the encoding of the string. Might be reset if a byte order mark is found.
|
|
|
|
* \remarks This method is not used anymore and might be deleted.
|
|
|
|
*/
|
2019-06-01 15:07:48 +02:00
|
|
|
void Id3v2Frame::parseBom(const char *buffer, std::size_t maxSize, TagTextEncoding &encoding, Diagnostics &diag)
|
2015-04-22 19:22:01 +02:00
|
|
|
{
|
2018-03-07 01:17:50 +01:00
|
|
|
switch (encoding) {
|
2015-04-22 19:22:01 +02:00
|
|
|
case TagTextEncoding::Utf16BigEndian:
|
|
|
|
case TagTextEncoding::Utf16LittleEndian:
|
2019-06-10 22:49:11 +02:00
|
|
|
if ((maxSize >= 2) && (BE::toUInt16(buffer) == 0xFFFE)) {
|
2015-04-22 19:22:01 +02:00
|
|
|
encoding = TagTextEncoding::Utf16LittleEndian;
|
2019-06-10 22:49:11 +02:00
|
|
|
} else if ((maxSize >= 2) && (BE::toUInt16(buffer) == 0xFEFF)) {
|
2015-04-22 19:22:01 +02:00
|
|
|
encoding = TagTextEncoding::Utf16BigEndian;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
2019-06-10 22:49:11 +02:00
|
|
|
if ((maxSize >= 3) && (BE::toUInt24(buffer) == 0x00EFBBBF)) {
|
2015-04-22 19:22:01 +02:00
|
|
|
encoding = TagTextEncoding::Utf8;
|
2021-07-02 03:00:50 +02:00
|
|
|
diag.emplace_back(DiagLevel::Warning, "UTF-8 byte order mark found in text frame.", "parsing byte order mark of frame " + idToString());
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
2015-09-19 22:34:07 +02:00
|
|
|
* \brief Parses the ID3v2.2 picture from the specified \a buffer.
|
|
|
|
* \param buffer Specifies the buffer holding the picture.
|
|
|
|
* \param maxSize Specifies the maximal number of bytes to read from the buffer.
|
|
|
|
* \param tagValue Specifies the tag value used to store the results.
|
|
|
|
* \param typeInfo Specifies a byte used to store the type info.
|
|
|
|
*/
|
2019-03-13 19:06:42 +01:00
|
|
|
void Id3v2Frame::parseLegacyPicture(const char *buffer, std::size_t maxSize, TagValue &tagValue, std::uint8_t &typeInfo, Diagnostics &diag)
|
2015-09-19 22:34:07 +02:00
|
|
|
{
|
|
|
|
static const string context("parsing ID3v2.2 picture frame");
|
2018-03-07 01:17:50 +01:00
|
|
|
if (maxSize < 6) {
|
2018-03-05 17:49:29 +01:00
|
|
|
diag.emplace_back(DiagLevel::Critical, "Picture frame is incomplete.", context);
|
2015-09-19 22:34:07 +02:00
|
|
|
throw TruncatedDataException();
|
|
|
|
}
|
|
|
|
const char *end = buffer + maxSize;
|
2019-03-13 19:06:42 +01:00
|
|
|
auto dataEncoding = parseTextEncodingByte(static_cast<std::uint8_t>(*buffer), diag); // the first byte stores the encoding
|
2015-09-19 22:34:07 +02:00
|
|
|
typeInfo = static_cast<unsigned char>(*(buffer + 4));
|
2018-06-02 22:56:08 +02:00
|
|
|
auto substr = parseSubstring(buffer + 5, static_cast<size_t>(end - 5 - buffer), dataEncoding, true, diag);
|
2015-09-19 22:34:07 +02:00
|
|
|
tagValue.setDescription(string(get<0>(substr), get<1>(substr)), dataEncoding);
|
2018-03-07 01:17:50 +01:00
|
|
|
if (get<2>(substr) >= end) {
|
2018-03-05 17:49:29 +01:00
|
|
|
diag.emplace_back(DiagLevel::Critical, "Picture frame is incomplete (actual data is missing).", context);
|
2015-09-19 22:34:07 +02:00
|
|
|
throw TruncatedDataException();
|
|
|
|
}
|
2018-06-02 22:56:08 +02:00
|
|
|
tagValue.assignData(get<2>(substr), static_cast<size_t>(end - get<2>(substr)), TagDataType::Picture, dataEncoding);
|
2015-09-19 22:34:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Parses the ID3v2.3 picture from the specified \a buffer.
|
2015-04-22 19:22:01 +02:00
|
|
|
* \param buffer Specifies the buffer holding the picture.
|
|
|
|
* \param maxSize Specifies the maximal number of bytes to read from the buffer.
|
|
|
|
* \param tagValue Specifies the tag value used to store the results.
|
|
|
|
* \param typeInfo Specifies a byte used to store the type info.
|
|
|
|
*/
|
2019-03-13 19:06:42 +01:00
|
|
|
void Id3v2Frame::parsePicture(const char *buffer, std::size_t maxSize, TagValue &tagValue, std::uint8_t &typeInfo, Diagnostics &diag)
|
2015-04-22 19:22:01 +02:00
|
|
|
{
|
2015-09-19 22:34:07 +02:00
|
|
|
static const string context("parsing ID3v2.3 picture frame");
|
2015-04-22 19:22:01 +02:00
|
|
|
const char *end = buffer + maxSize;
|
2019-03-13 19:06:42 +01:00
|
|
|
auto dataEncoding = parseTextEncodingByte(static_cast<std::uint8_t>(*buffer), diag); // the first byte stores the encoding
|
2015-09-19 22:34:07 +02:00
|
|
|
auto mimeTypeEncoding = TagTextEncoding::Latin1;
|
2018-03-05 17:49:29 +01:00
|
|
|
auto substr = parseSubstring(buffer + 1, maxSize - 1, mimeTypeEncoding, true, diag);
|
2018-03-07 01:17:50 +01:00
|
|
|
if (get<1>(substr)) {
|
2015-04-22 19:22:01 +02:00
|
|
|
tagValue.setMimeType(string(get<0>(substr), get<1>(substr)));
|
|
|
|
}
|
2018-03-07 01:17:50 +01:00
|
|
|
if (get<2>(substr) >= end) {
|
2018-03-05 17:49:29 +01:00
|
|
|
diag.emplace_back(DiagLevel::Critical, "Picture frame is incomplete (type info, description and actual data are missing).", context);
|
2015-04-22 19:22:01 +02:00
|
|
|
throw TruncatedDataException();
|
|
|
|
}
|
|
|
|
typeInfo = static_cast<unsigned char>(*get<2>(substr));
|
2018-03-07 01:17:50 +01:00
|
|
|
if (++get<2>(substr) >= end) {
|
2018-03-05 17:49:29 +01:00
|
|
|
diag.emplace_back(DiagLevel::Critical, "Picture frame is incomplete (description and actual data are missing).", context);
|
2015-04-22 19:22:01 +02:00
|
|
|
throw TruncatedDataException();
|
|
|
|
}
|
2018-06-02 22:56:08 +02:00
|
|
|
substr = parseSubstring(get<2>(substr), static_cast<size_t>(end - get<2>(substr)), dataEncoding, true, diag);
|
2015-04-22 19:22:01 +02:00
|
|
|
tagValue.setDescription(string(get<0>(substr), get<1>(substr)), dataEncoding);
|
2018-03-07 01:17:50 +01:00
|
|
|
if (get<2>(substr) >= end) {
|
2018-03-05 17:49:29 +01:00
|
|
|
diag.emplace_back(DiagLevel::Critical, "Picture frame is incomplete (actual data is missing).", context);
|
2015-04-22 19:22:01 +02:00
|
|
|
throw TruncatedDataException();
|
|
|
|
}
|
2018-06-02 22:56:08 +02:00
|
|
|
tagValue.assignData(get<2>(substr), static_cast<size_t>(end - get<2>(substr)), TagDataType::Picture, dataEncoding);
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
2016-03-18 21:43:09 +01:00
|
|
|
* \brief Parses the comment/unsynchronized lyrics from the specified \a buffer.
|
2015-04-22 19:22:01 +02:00
|
|
|
* \param buffer Specifies the buffer holding the picture.
|
|
|
|
* \param dataSize Specifies the maximal number of bytes to read from the buffer.
|
|
|
|
* \param tagValue Specifies the tag value used to store the results.
|
|
|
|
*/
|
2018-03-05 17:49:29 +01:00
|
|
|
void Id3v2Frame::parseComment(const char *buffer, std::size_t dataSize, TagValue &tagValue, Diagnostics &diag)
|
2015-04-22 19:22:01 +02:00
|
|
|
{
|
2016-03-18 21:43:09 +01:00
|
|
|
static const string context("parsing comment/unsynchronized lyrics frame");
|
2015-04-22 19:22:01 +02:00
|
|
|
const char *end = buffer + dataSize;
|
2018-03-07 01:17:50 +01:00
|
|
|
if (dataSize < 5) {
|
2018-03-05 17:49:29 +01:00
|
|
|
diag.emplace_back(DiagLevel::Critical, "Comment frame is incomplete.", context);
|
2015-04-22 19:22:01 +02:00
|
|
|
throw TruncatedDataException();
|
|
|
|
}
|
2019-03-13 19:06:42 +01:00
|
|
|
TagTextEncoding dataEncoding = parseTextEncodingByte(static_cast<std::uint8_t>(*buffer), diag);
|
2018-03-07 01:17:50 +01:00
|
|
|
if (*(++buffer)) {
|
2020-12-13 18:37:15 +01:00
|
|
|
tagValue.setLocale(Locale(std::string(buffer, 3), LocaleFormat::ISO_639_2_B)); // does standard say whether T or B?
|
2016-03-18 21:43:09 +01:00
|
|
|
}
|
2018-03-05 17:49:29 +01:00
|
|
|
auto substr = parseSubstring(buffer += 3, dataSize -= 4, dataEncoding, true, diag);
|
2015-04-22 19:22:01 +02:00
|
|
|
tagValue.setDescription(string(get<0>(substr), get<1>(substr)), dataEncoding);
|
2018-03-07 01:17:50 +01:00
|
|
|
if (get<2>(substr) > end) {
|
2018-03-05 17:49:29 +01:00
|
|
|
diag.emplace_back(DiagLevel::Critical, "Comment frame is incomplete (description not terminated?).", context);
|
2015-04-22 19:22:01 +02:00
|
|
|
throw TruncatedDataException();
|
|
|
|
}
|
2018-06-02 22:56:08 +02:00
|
|
|
substr = parseSubstring(get<2>(substr), static_cast<size_t>(end - get<2>(substr)), dataEncoding, false, diag);
|
2015-04-22 19:22:01 +02:00
|
|
|
tagValue.assignData(get<0>(substr), get<1>(substr), TagDataType::Text, dataEncoding);
|
|
|
|
}
|
|
|
|
|
2017-05-18 02:26:25 +02:00
|
|
|
/*!
|
|
|
|
* \brief Writes the BOM for the specified \a encoding to the specified \a buffer.
|
|
|
|
* \returns Returns the number of bytes written to the buffer.
|
|
|
|
*/
|
|
|
|
size_t Id3v2Frame::makeBom(char *buffer, TagTextEncoding encoding)
|
|
|
|
{
|
2018-03-07 01:17:50 +01:00
|
|
|
switch (encoding) {
|
2017-05-18 02:26:25 +02:00
|
|
|
case TagTextEncoding::Utf16LittleEndian:
|
2019-06-10 22:49:11 +02:00
|
|
|
LE::getBytes(static_cast<std::uint16_t>(0xFEFF), buffer);
|
2017-05-18 02:26:25 +02:00
|
|
|
return 2;
|
|
|
|
case TagTextEncoding::Utf16BigEndian:
|
2019-06-10 22:49:11 +02:00
|
|
|
BE::getBytes(static_cast<std::uint16_t>(0xFEFF), buffer);
|
2017-05-18 02:26:25 +02:00
|
|
|
return 2;
|
|
|
|
default:
|
|
|
|
return 0;
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
2017-05-29 14:05:15 +02:00
|
|
|
* \brief Writes the specified picture to the specified buffer (ID3v2.2 compatible).
|
2015-09-19 22:34:07 +02:00
|
|
|
*/
|
2019-06-01 22:26:16 +02:00
|
|
|
void Id3v2Frame::makeLegacyPicture(
|
|
|
|
unique_ptr<char[]> &buffer, std::uint32_t &bufferSize, const TagValue &picture, std::uint8_t typeInfo, Diagnostics &diag)
|
2015-09-19 22:34:07 +02:00
|
|
|
{
|
2017-05-20 23:34:45 +02:00
|
|
|
// determine description
|
|
|
|
TagTextEncoding descriptionEncoding = picture.descriptionEncoding();
|
|
|
|
StringData convertedDescription;
|
2018-03-07 01:17:50 +01:00
|
|
|
string::size_type descriptionSize = picture.description().find(
|
|
|
|
"\0\0", 0, descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? 2 : 1);
|
|
|
|
if (descriptionSize == string::npos) {
|
2017-05-29 14:05:15 +02:00
|
|
|
descriptionSize = picture.description().size();
|
|
|
|
}
|
2018-03-07 01:17:50 +01:00
|
|
|
if (descriptionEncoding == TagTextEncoding::Utf8) {
|
2017-05-20 23:34:45 +02:00
|
|
|
// UTF-8 is only supported by ID3v2.4, so convert back to UTF-16
|
|
|
|
descriptionEncoding = TagTextEncoding::Utf16LittleEndian;
|
2017-05-29 14:05:15 +02:00
|
|
|
convertedDescription = convertUtf8ToUtf16LE(picture.description().data(), descriptionSize);
|
2017-05-20 23:34:45 +02:00
|
|
|
descriptionSize = convertedDescription.second;
|
|
|
|
}
|
2019-02-13 21:43:56 +01:00
|
|
|
|
2015-09-19 22:34:07 +02:00
|
|
|
// calculate needed buffer size and create buffer
|
2019-02-13 21:43:56 +01:00
|
|
|
// note: encoding byte + image format + picture type byte + description size + 1 or 2 null bytes (depends on encoding) + data size
|
|
|
|
const auto requiredBufferSize = 1 + 3 + 1 + descriptionSize
|
|
|
|
+ (descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? 4 : 1)
|
|
|
|
+ picture.dataSize();
|
2019-03-13 19:06:42 +01:00
|
|
|
if (requiredBufferSize > numeric_limits<std::uint32_t>::max()) {
|
2019-06-01 22:26:16 +02:00
|
|
|
diag.emplace_back(DiagLevel::Critical, "Required size exceeds maximum.", "making legacy picture frame");
|
2019-02-13 21:43:56 +01:00
|
|
|
throw InvalidDataException();
|
|
|
|
}
|
2019-03-13 19:06:42 +01:00
|
|
|
buffer = make_unique<char[]>(bufferSize = static_cast<std::uint32_t>(requiredBufferSize));
|
2015-09-19 22:34:07 +02:00
|
|
|
char *offset = buffer.get();
|
2019-02-13 21:43:56 +01:00
|
|
|
|
2015-09-19 22:34:07 +02:00
|
|
|
// write encoding byte
|
2018-06-02 22:56:08 +02:00
|
|
|
*offset = static_cast<char>(makeTextEncodingByte(descriptionEncoding));
|
2019-02-13 21:43:56 +01:00
|
|
|
|
2015-09-19 22:34:07 +02:00
|
|
|
// write mime type
|
|
|
|
const char *imageFormat;
|
2018-03-07 01:17:50 +01:00
|
|
|
if (picture.mimeType() == "image/jpeg") {
|
2015-09-19 22:34:07 +02:00
|
|
|
imageFormat = "JPG";
|
2018-03-07 01:17:50 +01:00
|
|
|
} else if (picture.mimeType() == "image/png") {
|
2015-09-19 22:34:07 +02:00
|
|
|
imageFormat = "PNG";
|
2018-03-07 01:17:50 +01:00
|
|
|
} else if (picture.mimeType() == "image/gif") {
|
2015-09-19 22:34:07 +02:00
|
|
|
imageFormat = "GIF";
|
2018-03-07 01:17:50 +01:00
|
|
|
} else if (picture.mimeType() == "-->") {
|
2015-09-19 22:34:07 +02:00
|
|
|
imageFormat = picture.mimeType().data();
|
|
|
|
} else {
|
|
|
|
imageFormat = "UND";
|
|
|
|
}
|
|
|
|
strncpy(++offset, imageFormat, 3);
|
2019-02-13 21:43:56 +01:00
|
|
|
|
2015-09-19 22:34:07 +02:00
|
|
|
// write picture type
|
2018-06-02 22:56:08 +02:00
|
|
|
*(offset += 3) = static_cast<char>(typeInfo);
|
2019-02-13 21:43:56 +01:00
|
|
|
|
2015-09-19 22:34:07 +02:00
|
|
|
// write description
|
2017-05-18 02:26:25 +02:00
|
|
|
offset += makeBom(offset + 1, descriptionEncoding);
|
2018-03-07 01:17:50 +01:00
|
|
|
if (convertedDescription.first) {
|
2017-05-20 23:34:45 +02:00
|
|
|
copy(convertedDescription.first.get(), convertedDescription.first.get() + descriptionSize, ++offset);
|
|
|
|
} else {
|
|
|
|
picture.description().copy(++offset, descriptionSize);
|
|
|
|
}
|
|
|
|
*(offset += descriptionSize) = 0x00; // terminate description and increase data offset
|
2018-03-07 01:17:50 +01:00
|
|
|
if (descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian) {
|
2015-09-19 22:34:07 +02:00
|
|
|
*(++offset) = 0x00;
|
|
|
|
}
|
2019-02-13 21:43:56 +01:00
|
|
|
|
2015-09-19 22:34:07 +02:00
|
|
|
// write actual data
|
|
|
|
copy(picture.dataPointer(), picture.dataPointer() + picture.dataSize(), ++offset);
|
|
|
|
}
|
|
|
|
|
2017-05-29 14:05:15 +02:00
|
|
|
/*!
|
|
|
|
* \brief Writes the specified picture to the specified buffer.
|
|
|
|
*/
|
2019-06-01 22:26:16 +02:00
|
|
|
void Id3v2Frame::makePicture(std::unique_ptr<char[]> &buffer, std::uint32_t &bufferSize, const TagValue &picture, std::uint8_t typeInfo,
|
|
|
|
std::uint8_t version, Diagnostics &diag)
|
2017-05-29 14:05:15 +02:00
|
|
|
{
|
2018-03-07 01:17:50 +01:00
|
|
|
if (version < 3) {
|
2019-06-01 22:26:16 +02:00
|
|
|
makeLegacyPicture(buffer, bufferSize, picture, typeInfo, diag);
|
2017-05-29 14:05:15 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-05-20 23:34:45 +02:00
|
|
|
// determine description
|
|
|
|
TagTextEncoding descriptionEncoding = picture.descriptionEncoding();
|
|
|
|
StringData convertedDescription;
|
2018-03-07 01:17:50 +01:00
|
|
|
string::size_type descriptionSize = picture.description().find(
|
|
|
|
"\0\0", 0, descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? 2 : 1);
|
|
|
|
if (descriptionSize == string::npos) {
|
2017-05-29 14:05:15 +02:00
|
|
|
descriptionSize = picture.description().size();
|
|
|
|
}
|
2018-03-07 01:17:50 +01:00
|
|
|
if (version < 4 && descriptionEncoding == TagTextEncoding::Utf8) {
|
2017-05-20 23:34:45 +02:00
|
|
|
// UTF-8 is only supported by ID3v2.4, so convert back to UTF-16
|
|
|
|
descriptionEncoding = TagTextEncoding::Utf16LittleEndian;
|
2017-05-29 14:05:15 +02:00
|
|
|
convertedDescription = convertUtf8ToUtf16LE(picture.description().data(), descriptionSize);
|
2017-05-20 23:34:45 +02:00
|
|
|
descriptionSize = convertedDescription.second;
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
2017-05-20 23:34:45 +02:00
|
|
|
// determine mime-type
|
|
|
|
string::size_type mimeTypeSize = picture.mimeType().find('\0');
|
2018-03-07 01:17:50 +01:00
|
|
|
if (mimeTypeSize == string::npos) {
|
2017-05-20 23:34:45 +02:00
|
|
|
mimeTypeSize = picture.mimeType().length();
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
2019-06-10 22:49:11 +02:00
|
|
|
|
2017-05-20 23:34:45 +02:00
|
|
|
// calculate needed buffer size and create buffer
|
2019-06-01 22:26:16 +02:00
|
|
|
// note: encoding byte + mime type size + 0 byte + picture type byte + description size + 1 or 4 null bytes (depends on encoding) + data size
|
|
|
|
const auto requiredBufferSize = 1 + mimeTypeSize + 1 + 1 + descriptionSize
|
|
|
|
+ (descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? 4 : 1)
|
|
|
|
+ picture.dataSize();
|
|
|
|
if (requiredBufferSize > numeric_limits<uint32_t>::max()) {
|
|
|
|
diag.emplace_back(DiagLevel::Critical, "Required size exceeds maximum.", "making picture frame");
|
|
|
|
throw InvalidDataException();
|
|
|
|
}
|
|
|
|
buffer = make_unique<char[]>(bufferSize = static_cast<uint32_t>(requiredBufferSize));
|
2015-08-16 23:39:42 +02:00
|
|
|
char *offset = buffer.get();
|
2019-06-10 22:49:11 +02:00
|
|
|
|
2015-04-22 19:22:01 +02:00
|
|
|
// write encoding byte
|
2018-06-02 22:56:08 +02:00
|
|
|
*offset = static_cast<char>(makeTextEncodingByte(descriptionEncoding));
|
2019-06-10 22:49:11 +02:00
|
|
|
|
2015-04-22 19:22:01 +02:00
|
|
|
// write mime type
|
2017-05-20 23:34:45 +02:00
|
|
|
picture.mimeType().copy(++offset, mimeTypeSize);
|
2019-06-10 22:49:11 +02:00
|
|
|
|
2017-05-20 23:34:45 +02:00
|
|
|
*(offset += mimeTypeSize) = 0x00; // terminate mime type
|
2015-04-22 19:22:01 +02:00
|
|
|
// write picture type
|
2018-06-02 22:56:08 +02:00
|
|
|
*(++offset) = static_cast<char>(typeInfo);
|
2019-06-10 22:49:11 +02:00
|
|
|
|
2015-04-22 19:22:01 +02:00
|
|
|
// write description
|
2017-05-18 02:26:25 +02:00
|
|
|
offset += makeBom(offset + 1, descriptionEncoding);
|
2018-03-07 01:17:50 +01:00
|
|
|
if (convertedDescription.first) {
|
2017-05-20 23:34:45 +02:00
|
|
|
copy(convertedDescription.first.get(), convertedDescription.first.get() + descriptionSize, ++offset);
|
|
|
|
} else {
|
|
|
|
picture.description().copy(++offset, descriptionSize);
|
|
|
|
}
|
|
|
|
*(offset += descriptionSize) = 0x00; // terminate description and increase data offset
|
2018-03-07 01:17:50 +01:00
|
|
|
if (descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian) {
|
2015-04-22 19:22:01 +02:00
|
|
|
*(++offset) = 0x00;
|
|
|
|
}
|
2019-06-10 22:49:11 +02:00
|
|
|
|
2015-04-22 19:22:01 +02:00
|
|
|
// write actual data
|
|
|
|
copy(picture.dataPointer(), picture.dataPointer() + picture.dataSize(), ++offset);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Writes the specified comment to the specified buffer.
|
|
|
|
*/
|
2019-03-13 19:06:42 +01:00
|
|
|
void Id3v2Frame::makeComment(unique_ptr<char[]> &buffer, std::uint32_t &bufferSize, const TagValue &comment, std::uint8_t version, Diagnostics &diag)
|
2015-04-22 19:22:01 +02:00
|
|
|
{
|
|
|
|
static const string context("making comment frame");
|
2019-06-10 22:49:11 +02:00
|
|
|
|
2019-06-01 22:26:16 +02:00
|
|
|
// check whether type and other values are valid
|
2015-04-22 19:22:01 +02:00
|
|
|
TagTextEncoding encoding = comment.dataEncoding();
|
2018-03-07 01:17:50 +01:00
|
|
|
if (!comment.description().empty() && encoding != comment.descriptionEncoding()) {
|
2019-06-01 22:26:16 +02:00
|
|
|
diag.emplace_back(DiagLevel::Critical, "Data encoding and description encoding aren't equal.", context);
|
2015-04-22 19:22:01 +02:00
|
|
|
throw InvalidDataException();
|
|
|
|
}
|
2020-12-13 18:37:15 +01:00
|
|
|
const string &language = comment.locale().abbreviatedName(LocaleFormat::ISO_639_2_B, LocaleFormat::ISO_639_2_T, LocaleFormat::Unknown);
|
|
|
|
if (language.length() > 3) {
|
2018-03-05 17:49:29 +01:00
|
|
|
diag.emplace_back(DiagLevel::Critical, "The language must be 3 bytes long (ISO-639-2).", context);
|
2015-04-22 19:22:01 +02:00
|
|
|
throw InvalidDataException();
|
|
|
|
}
|
2017-05-20 23:34:45 +02:00
|
|
|
StringData convertedDescription;
|
2018-03-07 01:17:50 +01:00
|
|
|
string::size_type descriptionSize = comment.description().find(
|
|
|
|
"\0\0", 0, encoding == TagTextEncoding::Utf16BigEndian || encoding == TagTextEncoding::Utf16LittleEndian ? 2 : 1);
|
|
|
|
if (descriptionSize == string::npos) {
|
2017-05-29 14:05:15 +02:00
|
|
|
descriptionSize = comment.description().size();
|
|
|
|
}
|
2018-03-07 01:17:50 +01:00
|
|
|
if (version < 4 && encoding == TagTextEncoding::Utf8) {
|
2017-05-20 23:34:45 +02:00
|
|
|
// UTF-8 is only supported by ID3v2.4, so convert back to UTF-16
|
|
|
|
encoding = TagTextEncoding::Utf16LittleEndian;
|
2017-05-29 14:05:15 +02:00
|
|
|
convertedDescription = convertUtf8ToUtf16LE(comment.description().data(), descriptionSize);
|
2017-05-20 23:34:45 +02:00
|
|
|
descriptionSize = convertedDescription.second;
|
2016-08-05 01:22:46 +02:00
|
|
|
}
|
2019-06-10 22:49:11 +02:00
|
|
|
|
2017-05-20 23:34:45 +02:00
|
|
|
// calculate needed buffer size and create buffer
|
2019-06-01 22:26:16 +02:00
|
|
|
// note: encoding byte + language + description size + actual data size + BOMs and termination
|
2017-05-20 23:34:45 +02:00
|
|
|
const auto data = comment.toString(encoding);
|
2019-06-01 22:26:16 +02:00
|
|
|
const auto requiredBufferSize = 1 + 3 + descriptionSize + data.size()
|
|
|
|
+ (encoding == TagTextEncoding::Utf16BigEndian || encoding == TagTextEncoding::Utf16LittleEndian ? 6 : 1) + data.size();
|
|
|
|
if (requiredBufferSize > numeric_limits<uint32_t>::max()) {
|
|
|
|
diag.emplace_back(DiagLevel::Critical, "Required size exceeds maximum.", context);
|
|
|
|
throw InvalidDataException();
|
|
|
|
}
|
|
|
|
buffer = make_unique<char[]>(bufferSize = static_cast<uint32_t>(requiredBufferSize));
|
2015-08-16 23:39:42 +02:00
|
|
|
char *offset = buffer.get();
|
2019-06-10 22:49:11 +02:00
|
|
|
|
2015-04-22 19:22:01 +02:00
|
|
|
// write encoding
|
2018-06-02 22:56:08 +02:00
|
|
|
*offset = static_cast<char>(makeTextEncodingByte(encoding));
|
2019-06-10 22:49:11 +02:00
|
|
|
|
2015-04-22 19:22:01 +02:00
|
|
|
// write language
|
2018-03-07 01:17:50 +01:00
|
|
|
for (unsigned int i = 0; i < 3; ++i) {
|
2020-12-13 18:37:15 +01:00
|
|
|
*(++offset) = (language.length() > i) ? language[i] : 0x00;
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
2019-06-10 22:49:11 +02:00
|
|
|
|
2015-04-22 19:22:01 +02:00
|
|
|
// write description
|
2017-05-18 02:26:25 +02:00
|
|
|
offset += makeBom(offset + 1, encoding);
|
2018-03-07 01:17:50 +01:00
|
|
|
if (convertedDescription.first) {
|
2017-05-20 23:34:45 +02:00
|
|
|
copy(convertedDescription.first.get(), convertedDescription.first.get() + descriptionSize, ++offset);
|
|
|
|
} else {
|
|
|
|
comment.description().copy(++offset, descriptionSize);
|
|
|
|
}
|
|
|
|
offset += descriptionSize;
|
2015-04-22 19:22:01 +02:00
|
|
|
*offset = 0x00; // terminate description and increase data offset
|
2018-03-07 01:17:50 +01:00
|
|
|
if (encoding == TagTextEncoding::Utf16BigEndian || encoding == TagTextEncoding::Utf16LittleEndian) {
|
2015-04-22 19:22:01 +02:00
|
|
|
*(++offset) = 0x00;
|
|
|
|
}
|
2019-06-10 22:49:11 +02:00
|
|
|
|
2015-04-22 19:22:01 +02:00
|
|
|
// write actual data
|
2017-05-18 02:26:25 +02:00
|
|
|
offset += makeBom(offset + 1, encoding);
|
2016-08-05 01:22:46 +02:00
|
|
|
data.copy(++offset, data.size());
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
|
|
|
|
2018-03-07 01:17:50 +01:00
|
|
|
} // namespace TagParser
|