5 #include "../diagnostics.h" 6 #include "../exceptions.h" 8 #include <c++utilities/conversion/stringbuilder.h> 9 #include <c++utilities/conversion/stringconversion.h> 24 namespace Id3v2TextEncodingBytes {
39 Id3v2Frame::Id3v2Frame()
70 for (
auto c : denotation) {
81 if (c >=
'0' && c <=
'9') {
94 if (c >=
'0' && c <=
'9') {
95 index = index * 10 + c -
'0';
117 static const string defaultContext(
"parsing ID3v2 frame");
124 setId(reader.readUInt24BE());
125 if (
id() & 0xFFFF0000u) {
130 diag.emplace_back(
DiagLevel::Debug,
"Frame ID starts with null-byte -> padding reached.", defaultContext);
138 m_dataSize = reader.readUInt24BE();
139 m_totalSize = m_dataSize + 6;
140 if (m_totalSize > maximalSize) {
141 diag.emplace_back(
DiagLevel::Warning,
"The frame is truncated and will be ignored.", context);
152 setId(reader.readUInt32BE());
153 if (
id() & 0xFF000000u) {
158 diag.emplace_back(
DiagLevel::Debug,
"Frame ID starts with null-byte -> padding reached.", defaultContext);
166 m_dataSize =
version >= 4 ? reader.readSynchsafeUInt32BE() : reader.readUInt32BE();
167 m_totalSize = m_dataSize + 10;
168 if (m_totalSize > maximalSize) {
169 diag.emplace_back(
DiagLevel::Warning,
"The frame is truncated and will be ignored.", context);
174 m_flag = reader.readUInt16BE();
184 if (m_dataSize <= 0) {
190 unique_ptr<char[]> buffer;
194 uLongf decompressedSize =
version >= 4 ? reader.readSynchsafeUInt32BE() : reader.readUInt32BE();
195 if (decompressedSize < m_dataSize) {
196 diag.emplace_back(
DiagLevel::Critical,
"The decompressed size is smaller than the compressed size.", context);
199 const auto bufferCompressed = make_unique<char[]>(m_dataSize);
200 reader.read(bufferCompressed.get(), m_dataSize);
201 buffer = make_unique<char[]>(decompressedSize);
203 uncompress(reinterpret_cast<Bytef *>(buffer.get()), &decompressedSize, reinterpret_cast<Bytef *>(bufferCompressed.get()), m_dataSize)) {
205 diag.emplace_back(
DiagLevel::Critical,
"Decompressing failed. The source buffer was too small.", context);
208 diag.emplace_back(
DiagLevel::Critical,
"Decompressing failed. The destination buffer was too small.", context);
211 diag.emplace_back(
DiagLevel::Critical,
"Decompressing failed. The input data was corrupted or incomplete.", context);
220 diag.emplace_back(
DiagLevel::Critical,
"The decompressed data exceeds the maximum supported frame size.", context);
223 m_dataSize =
static_cast<uint32
>(decompressedSize);
225 buffer = make_unique<char[]>(m_dataSize);
226 reader.read(buffer.get(), m_dataSize);
242 }
catch (
const ConversionException &) {
243 diag.emplace_back(
DiagLevel::Warning,
"The value of track/disk position frame is not numeric and will be ignored.", context);
251 const auto parsedStringRef =
parseSubstring(buffer.get() + 1, m_dataSize - 1, dataEncoding,
false, diag);
253 ? convertUtf16BEToUtf8(get<0>(parsedStringRef), get<1>(parsedStringRef))
254 : convertUtf16LEToUtf8(get<0>(parsedStringRef), get<1>(parsedStringRef));
255 milliseconds = string(convertedStringData.first.get(), convertedStringData.second);
257 milliseconds =
parseString(buffer.get() + 1, m_dataSize - 1, dataEncoding,
false, diag);
260 }
catch (
const ConversionException &) {
261 diag.emplace_back(
DiagLevel::Warning,
"The value of the length frame is not numeric and will be ignored.", context);
268 const auto genreDenotation =
parseWideString(buffer.get() + 1, m_dataSize - 1, dataEncoding,
false, diag);
271 const auto genreDenotation =
parseString(buffer.get() + 1, m_dataSize - 1, dataEncoding,
false, diag);
274 if (genreIndex != -1) {
280 const auto substr =
parseSubstring(buffer.get() + 1, m_dataSize - 1, dataEncoding,
false, diag);
285 const auto substr =
parseSubstring(buffer.get() + 1, m_dataSize - 1, dataEncoding,
false, diag);
343 void Id3v2Frame::reset()
364 Id3v2FrameMaker::Id3v2FrameMaker(Id3v2Frame &frame, byte
version, Diagnostics &diag)
366 , m_frameId(m_frame.id())
369 const string context(
"making " % m_frame.frameIdString() +
" frame");
372 const auto value(m_frame.value());
375 throw InvalidDataException();
377 if (m_frame.isEncrypted()) {
378 diag.emplace_back(
DiagLevel::Critical,
"Cannot make an encrypted frame (isn't supported by this tagging library).", context);
379 throw InvalidDataException();
381 if (m_frame.hasPaddingReached()) {
382 diag.emplace_back(
DiagLevel::Critical,
"Cannot make a frame which is marked as padding.", context);
383 throw InvalidDataException();
385 if (
version < 3 && m_frame.isCompressed()) {
386 diag.emplace_back(
DiagLevel::Warning,
"Compression is not supported by the version of ID3v2 and won't be applied.", context);
388 if (
version < 3 && (m_frame.flag() || m_frame.group())) {
390 "The existing flag and group information is not supported by the version of ID3v2 and will be ignored/discarted.", context);
399 "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.",
401 throw InvalidDataException();
409 "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.",
411 throw InvalidDataException();
429 }
catch (
const ConversionException &) {
431 argsToString(
"The track/disk number \"", positionStr,
"\" is not of the expected form, eg. \"4/10\"."), context);
440 throw InvalidDataException();
442 m_frame.makeString(m_data, m_decompressedSize, ConversionUtilities::numberToString(static_cast<uint64>(
duration.totalMilliseconds())),
463 m_frame.makePicture(m_data, m_decompressedSize,
value, m_frame.isTypeInfoAssigned() ? m_frame.typeInfo() : 0,
version);
469 m_frame.makeComment(m_data, m_decompressedSize,
value,
version, diag);
475 throw InvalidDataException();
477 m_data = make_unique<char[]>(m_decompressedSize =
static_cast<uint32
>(
value.
dataSize()));
480 }
catch (
const ConversionException &) {
484 }
catch (
const ConversionException &) {
485 diag.emplace_back(
DiagLevel::Critical,
"Assigned value can not be converted appropriately.", context);
487 throw InvalidDataException();
491 if (
version >= 3 && m_frame.isCompressed()) {
492 auto compressedSize = compressBound(m_decompressedSize);
493 auto compressedData = make_unique<char[]>(compressedSize);
494 switch (compress(reinterpret_cast<Bytef *>(compressedData.get()), reinterpret_cast<uLongf *>(&compressedSize),
495 reinterpret_cast<Bytef *
>(m_data.get()), m_decompressedSize)) {
497 diag.emplace_back(
DiagLevel::Critical,
"Decompressing failed. The source buffer was too small.", context);
498 throw InvalidDataException();
500 diag.emplace_back(
DiagLevel::Critical,
"Decompressing failed. The destination buffer was too small.", context);
501 throw InvalidDataException();
505 diag.emplace_back(
DiagLevel::Critical,
"Compressed size exceeds maximum data size.", context);
506 throw InvalidDataException();
508 m_data.swap(compressedData);
509 m_dataSize =
static_cast<uint32
>(compressedSize);
511 m_dataSize = m_decompressedSize;
516 m_requiredSize = m_dataSize;
522 m_requiredSize += 10;
524 if (m_frame.hasGroupInformation()) {
528 if (
version >= 3 && m_frame.isCompressed()) {
544 writer.writeUInt24BE(m_frameId);
545 writer.writeUInt24BE(m_dataSize);
547 writer.writeUInt32BE(m_frameId);
548 if (m_version >= 4) {
549 writer.writeSynchsafeUInt32BE(m_dataSize);
551 writer.writeUInt32BE(m_dataSize);
553 writer.writeUInt16BE(m_frame.
flag());
555 writer.writeByte(m_frame.
group());
558 if (m_version >= 4) {
559 writer.writeSynchsafeUInt32BE(m_decompressedSize);
561 writer.writeUInt32BE(m_decompressedSize);
565 writer.write(m_data.get(), m_dataSize);
576 switch (textEncodingByte) {
597 switch (textEncoding) {
628 tuple<const char *, size_t, const char *> res(buffer, 0, buffer + bufferSize);
633 if ((bufferSize >= 3) && (ConversionUtilities::BE::toUInt24(buffer) == 0x00EFBBBF)) {
635 diag.emplace_back(
DiagLevel::Critical,
"Denoted character set is Latin-1 but an UTF-8 BOM is present - assuming UTF-8.",
641 const char *pos = get<0>(res);
642 for (; *pos != 0x00; ++pos) {
643 if (pos < get<2>(res)) {
653 get<2>(res) = pos + 1;
658 if (bufferSize >= 2) {
659 switch (ConversionUtilities::LE::toUInt16(buffer)) {
663 "Denoted character set is UTF-16 Big Endian but UTF-16 Little Endian BOM is present - assuming UTF-16 LE.",
674 const uint16 *pos =
reinterpret_cast<const uint16 *
>(get<0>(res));
675 for (; *pos != 0x0000; ++pos) {
676 if (pos < reinterpret_cast<const uint16 *>(get<2>(res))) {
686 get<2>(res) = reinterpret_cast<const char *>(pos + 1);
701 return string(get<0>(substr), get<1>(substr));
714 u16string res(reinterpret_cast<u16string::const_pointer>(get<0>(substr)), get<1>(substr) / 2);
733 if ((maxSize >= 2) && (ConversionUtilities::BE::toUInt16(buffer) == 0xFFFE)) {
735 }
else if ((maxSize >= 2) && (ConversionUtilities::BE::toUInt16(buffer) == 0xFEFF)) {
740 if ((maxSize >= 3) && (ConversionUtilities::BE::toUInt24(buffer) == 0x00EFBBBF)) {
756 static const string context(
"parsing ID3v2.2 picture frame");
761 const char *end = buffer + maxSize;
763 typeInfo =
static_cast<unsigned char>(*(buffer + 4));
764 auto substr =
parseSubstring(buffer + 5, static_cast<size_t>(end - 5 - buffer), dataEncoding,
true, diag);
765 tagValue.
setDescription(
string(get<0>(substr), get<1>(substr)), dataEncoding);
766 if (get<2>(substr) >= end) {
767 diag.emplace_back(
DiagLevel::Critical,
"Picture frame is incomplete (actual data is missing).", context);
782 static const string context(
"parsing ID3v2.3 picture frame");
783 const char *end = buffer + maxSize;
786 auto substr =
parseSubstring(buffer + 1, maxSize - 1, mimeTypeEncoding,
true, diag);
787 if (get<1>(substr)) {
788 tagValue.
setMimeType(
string(get<0>(substr), get<1>(substr)));
790 if (get<2>(substr) >= end) {
791 diag.emplace_back(
DiagLevel::Critical,
"Picture frame is incomplete (type info, description and actual data are missing).", context);
794 typeInfo =
static_cast<unsigned char>(*get<2>(substr));
795 if (++get<2>(substr) >= end) {
796 diag.emplace_back(
DiagLevel::Critical,
"Picture frame is incomplete (description and actual data are missing).", context);
799 substr =
parseSubstring(get<2>(substr), static_cast<size_t>(end - get<2>(substr)), dataEncoding,
true, diag);
800 tagValue.
setDescription(
string(get<0>(substr), get<1>(substr)), dataEncoding);
801 if (get<2>(substr) >= end) {
802 diag.emplace_back(
DiagLevel::Critical,
"Picture frame is incomplete (actual data is missing).", context);
816 static const string context(
"parsing comment/unsynchronized lyrics frame");
817 const char *end = buffer +
dataSize;
827 tagValue.
setDescription(
string(get<0>(substr), get<1>(substr)), dataEncoding);
828 if (get<2>(substr) > end) {
829 diag.emplace_back(
DiagLevel::Critical,
"Comment frame is incomplete (description not terminated?).", context);
832 substr =
parseSubstring(get<2>(substr), static_cast<size_t>(end - get<2>(substr)), dataEncoding,
false, diag);
857 std::unique_ptr<
char[]> &buffer, uint32 &bufferSize,
TagTextEncoding encoding,
const char *data, std::size_t dataSize)
863 char *bufferDataAddress;
869 buffer = make_unique<char[]>(bufferSize = 1 +
dataSize + 1);
871 bufferDataAddress = buffer.get() + 1;
876 buffer = make_unique<char[]>(bufferSize = 1 + 2 +
dataSize + 2);
878 ConversionUtilities::LE::getBytes(
880 bufferDataAddress = buffer.get() + 3;
888 copy(data, data +
dataSize, bufferDataAddress);
900 ConversionUtilities::LE::getBytes(static_cast<uint16>(0xFEFF), buffer);
903 ConversionUtilities::BE::getBytes(static_cast<uint16>(0xFEFF), buffer);
917 StringData convertedDescription;
918 string::size_type descriptionSize = picture.
description().find(
920 if (descriptionSize == string::npos) {
926 convertedDescription = convertUtf8ToUtf16LE(picture.
description().data(), descriptionSize);
927 descriptionSize = convertedDescription.second;
931 buffer = make_unique<char[]>(bufferSize = 1 + 3 + 1 + descriptionSize
935 char *offset = buffer.get();
939 const char *imageFormat;
940 if (picture.
mimeType() ==
"image/jpeg") {
942 }
else if (picture.
mimeType() ==
"image/png") {
944 }
else if (picture.
mimeType() ==
"image/gif") {
946 }
else if (picture.
mimeType() ==
"-->") {
947 imageFormat = picture.
mimeType().data();
951 strncpy(++offset, imageFormat, 3);
953 *(offset += 3) = static_cast<char>(
typeInfo);
955 offset +=
makeBom(offset + 1, descriptionEncoding);
956 if (convertedDescription.first) {
957 copy(convertedDescription.first.get(), convertedDescription.first.get() + descriptionSize, ++offset);
959 picture.
description().copy(++offset, descriptionSize);
961 *(offset += descriptionSize) = 0x00;
981 StringData convertedDescription;
982 string::size_type descriptionSize = picture.
description().find(
984 if (descriptionSize == string::npos) {
990 convertedDescription = convertUtf8ToUtf16LE(picture.
description().data(), descriptionSize);
991 descriptionSize = convertedDescription.second;
994 string::size_type mimeTypeSize = picture.
mimeType().find(
'\0');
995 if (mimeTypeSize == string::npos) {
996 mimeTypeSize = picture.
mimeType().length();
1000 buffer = make_unique<char[]>(bufferSize = 1 + mimeTypeSize + 1 + 1 + descriptionSize
1004 char *offset = buffer.get();
1008 picture.
mimeType().copy(++offset, mimeTypeSize);
1009 *(offset += mimeTypeSize) = 0x00;
1011 *(++offset) = static_cast<char>(
typeInfo);
1013 offset +=
makeBom(offset + 1, descriptionEncoding);
1014 if (convertedDescription.first) {
1015 copy(convertedDescription.first.get(), convertedDescription.first.get() + descriptionSize, ++offset);
1017 picture.
description().copy(++offset, descriptionSize);
1019 *(offset += descriptionSize) = 0x00;
1032 static const string context(
"making comment frame");
1035 if (!
comment.description().empty() && encoding !=
comment.descriptionEncoding()) {
1036 diag.emplace_back(
DiagLevel::Critical,
"Data enoding and description encoding aren't equal.", context);
1039 const string &lng =
comment.language();
1040 if (lng.length() > 3) {
1041 diag.emplace_back(
DiagLevel::Critical,
"The language must be 3 bytes long (ISO-639-2).", context);
1044 StringData convertedDescription;
1045 string::size_type descriptionSize =
comment.description().find(
1047 if (descriptionSize == string::npos) {
1048 descriptionSize =
comment.description().size();
1053 convertedDescription = convertUtf8ToUtf16LE(
comment.description().data(), descriptionSize);
1054 descriptionSize = convertedDescription.second;
1057 const auto data =
comment.toString(encoding);
1058 buffer = make_unique<char[]>(bufferSize = 1 + 3 + descriptionSize + data.size()
1061 char *offset = buffer.get();
1065 for (
unsigned int i = 0; i < 3; ++i) {
1066 *(++offset) = (lng.length() > i) ? lng[i] : 0x00;
1069 offset +=
makeBom(offset + 1, encoding);
1070 if (convertedDescription.first) {
1071 copy(convertedDescription.first.get(), convertedDescription.first.get() + descriptionSize, ++offset);
1073 comment.description().copy(++offset, descriptionSize);
1075 offset += descriptionSize;
1081 offset +=
makeBom(offset + 1, encoding);
1082 data.copy(++offset, data.size());
TagTextEncoding dataEncoding() const
Returns the data encoding.
uint16 flag() const
Returns the flags.
bool isShortId(uint32 id)
Returns an indication whether the specified id is a short frame id.
The exception that is thrown when the data to be parsed is truncated and therefore can not be parsed ...
void setTypeInfo(const TypeInfoType &typeInfo)
Sets the type info of the current TagField.
void makeLegacyPicture(std::unique_ptr< char[]> &buffer, uint32 &bufferSize, const TagValue &picture, byte typeInfo)
Writes the specified picture to the specified buffer (ID3v2.2 compatible).
void makeComment(std::unique_ptr< char[]> &buffer, uint32 &bufferSize, const TagValue &comment, byte version, Diagnostics &diag)
Writes the specified comment to the specified buffer.
bool hasGroupInformation() const
Returns whether the frame contains group information.
byte group() const
Returns the group.
TagTextEncoding descriptionEncoding() const
Returns the description encoding.
const std::string & description() const
Returns the description.
void setMimeType(const std::string &mimeType)
Sets the MIME type.
byte makeTextEncodingByte(TagTextEncoding textEncoding)
Returns a text encoding byte for the specified textEncoding.
The exception that is thrown when an operation fails because the detected or specified version is not...
TagTextEncoding parseTextEncodingByte(byte textEncodingByte, Diagnostics &diag)
Returns the text encoding for the specified textEncodingByte.
void make(IoUtilities::BinaryWriter &writer, byte version, Diagnostics &diag)
Writes the frame to a stream using the specified writer and the specified ID3v2 version.
std::u16string parseWideString(const char *buffer, std::size_t dataSize, TagTextEncoding &encoding, bool addWarnings, Diagnostics &diag)
Parses a substring in the specified buffer.
int toStandardGenreIndex() const
Converts the value of the current TagValue object to its equivalent standard genre index...
uint32 convertToLongId(uint32 id)
Converts the specified short frame ID to the equivalent long frame ID.
TAG_PARSER_EXPORT const char * comment()
constexpr auto maxId3v2FrameDataSize(numeric_limits< uint32 >::max() - 15)
The maximum (supported) size of an ID3v2Frame.
std::string parseString(const char *buffer, std::size_t maxSize, TagTextEncoding &encoding, bool addWarnings, Diagnostics &diag)
Parses a substring in the specified buffer.
bool isCompressed() const
Returns whether the frame is compressed.
void makePicture(std::unique_ptr< char[]> &buffer, uint32 &bufferSize, const TagValue &picture, byte typeInfo, byte version)
Writes the specified picture to the specified buffer.
The Id3v2Frame class is used by Id3v2Tag to store the fields.
bool isEmpty() const
Returns an indication whether an value is assigned.
const std::string & mimeType() const
Returns the MIME type.
void parseLegacyPicture(const char *buffer, std::size_t maxSize, TagValue &tagValue, byte &typeInfo, Diagnostics &diag)
Parses the ID3v2.2 picture from the specified buffer.
std::size_t dataSize() const
Returns the size of the assigned value in bytes.
void parseBom(const char *buffer, std::size_t maxSize, TagTextEncoding &encoding, Diagnostics &diag)
Parses a byte order mark from the specified buffer.
void makeString(std::unique_ptr< char[]> &buffer, uint32 &bufferSize, const std::string &value, TagTextEncoding encoding)
Writes an encoding denoation and the specified string value to a buffer.
uint32 convertToShortId(uint32 id)
Converts the specified long frame ID to the equivalent short frame ID.
void makeEncodingAndData(std::unique_ptr< char[]> &buffer, uint32 &bufferSize, TagTextEncoding encoding, const char *data, std::size_t m_dataSize)
Writes an encoding denoation and the specified data to a buffer.
ChronoUtilities::TimeSpan toTimeSpan() const
Converts the value of the current TagValue object to its equivalent TimeSpan representation.
bool isLongId(uint32 id)
Returns an indication whether the specified id is a long frame id.
Contains utility classes helping to read and write streams.
bool isEncrypted() const
Returns whether the frame is encrypted.
std::string frameIdString() const
Returns the frame ID as string.
void assignPosition(PositionInSet value)
Assigns the given PositionInSet value.
PositionInSet toPositionInSet() const
Converts the value of the current TagValue object to its equivalent PositionInSet representation...
void parseComment(const char *buffer, std::size_t maxSize, TagValue &tagValue, Diagnostics &diag)
Parses the comment/unsynchronized lyrics from the specified buffer.
void assignStandardGenreIndex(int index)
Assigns the given standard genre index to be assigned.
const TypeInfoType & typeInfo() const
Returns the type info of the current TagField.
The exception that is thrown when the data to be parsed holds no parsable information.
The Id3v2FrameMaker class helps making ID3v2 frames.
void parsePicture(const char *buffer, std::size_t maxSize, TagValue &tagValue, byte &typeInfo, Diagnostics &diag)
Parses the ID3v2.3 picture from the specified buffer.
std::tuple< const char *, size_t, const char * > parseSubstring(const char *buffer, std::size_t maxSize, TagTextEncoding &encoding, bool addWarnings, Diagnostics &diag)
Parses a substring in the specified buffer.
The TagField class is used by FieldMapBasedTag to store the fields.
int parseGenreIndex(const stringtype &denotation)
Helper function to parse the genre index.
void setLanguage(const std::string &language)
Sets the language.
char * dataPointer()
Returns a pointer to the raw data assigned to the current instance.
std::size_t makeBom(char *buffer, TagTextEncoding encoding)
Writes the BOM for the specified encoding to the specified buffer.
TagDataType type() const
Returns the type of the assigned value.
bool isTextFrame(uint32 id)
Returns an indication whether the specified id is a text frame id.
void make(IoUtilities::BinaryWriter &writer)
Saves the frame (specified when constructing the object) using the specified writer.
TAG_PARSER_EXPORT const char * duration()
Id3v2Frame()
Constructs a new Id3v2Frame.
The exception that is thrown when the data to be parsed or to be made seems invalid and therefore can...
void setDescription(const std::string &value, TagTextEncoding encoding=TagTextEncoding::Latin1)
Sets the description.
void assignData(const char *data, std::size_t length, TagDataType type=TagDataType::Binary, TagTextEncoding encoding=TagTextEncoding::Latin1)
std::string toString(TagTextEncoding encoding=TagTextEncoding::Unspecified) const
Converts the value of the current TagValue object to its equivalent std::string representation.
void assignTimeSpan(ChronoUtilities::TimeSpan value)
Assigns the given TimeSpan value.
constexpr int characterSize(TagTextEncoding encoding)
Returns the size of one character for the specified encoding in bytes.
The TagValue class wraps values of different types.
uint32 dataSize() const
Returns the size of the data stored in the frame in bytes.
Id3v2FrameMaker prepareMaking(byte version, Diagnostics &diag)
Prepares making.
TagTextEncoding
Specifies the text encoding.
void parse(IoUtilities::BinaryReader &reader, uint32 version, uint32 maximalSize, Diagnostics &diag)
Parses a frame from the stream read using the specified reader.
Contains all classes and functions of the TagInfo library.
void setId(const IdentifierType &id)
Sets the id of the current Tag Field.
static void ensureHostByteOrder(std::u16string &u16str, TagTextEncoding currentEncoding)
Ensures the byte-order of the specified UTF-16 string matches the byte-order of the machine...
The Diagnostics class is a container for DiagMessage.
TagValue & value()
Returns the value of the current TagField.