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 {
36 Id3v2Frame::Id3v2Frame()
67 for (
auto c : denotation) {
78 if (c >=
'0' && c <=
'9') {
91 if (c >=
'0' && c <=
'9') {
92 index = index * 10 + c -
'0';
114 static const string defaultContext(
"parsing ID3v2 frame");
121 setId(reader.readUInt24BE());
122 if (
id() & 0xFFFF0000u) {
127 diag.emplace_back(
DiagLevel::Debug,
"Frame ID starts with null-byte -> padding reached.", defaultContext);
135 m_dataSize = reader.readUInt24BE();
136 m_totalSize = m_dataSize + 6;
137 if (m_totalSize > maximalSize) {
138 diag.emplace_back(
DiagLevel::Warning,
"The frame is truncated and will be ignored.", context);
149 setId(reader.readUInt32BE());
150 if (
id() & 0xFF000000u) {
155 diag.emplace_back(
DiagLevel::Debug,
"Frame ID starts with null-byte -> padding reached.", defaultContext);
163 m_dataSize =
version >= 4 ? reader.readSynchsafeUInt32BE() : reader.readUInt32BE();
164 m_totalSize = m_dataSize + 10;
165 if (m_totalSize > maximalSize) {
166 diag.emplace_back(
DiagLevel::Warning,
"The frame is truncated and will be ignored.", context);
171 m_flag = reader.readUInt16BE();
181 if (m_dataSize <= 0) {
187 unique_ptr<char[]> buffer;
191 uLongf decompressedSize =
version >= 4 ? reader.readSynchsafeUInt32BE() : reader.readUInt32BE();
192 if (decompressedSize < m_dataSize) {
193 diag.emplace_back(
DiagLevel::Critical,
"The decompressed size is smaller than the compressed size.", context);
196 const auto bufferCompressed = make_unique<char[]>(m_dataSize);
197 reader.read(bufferCompressed.get(), m_dataSize);
198 buffer = make_unique<char[]>(decompressedSize);
200 uncompress(reinterpret_cast<Bytef *>(buffer.get()), &decompressedSize, reinterpret_cast<Bytef *>(bufferCompressed.get()), m_dataSize)) {
202 diag.emplace_back(
DiagLevel::Critical,
"Decompressing failed. The source buffer was too small.", context);
205 diag.emplace_back(
DiagLevel::Critical,
"Decompressing failed. The destination buffer was too small.", context);
208 diag.emplace_back(
DiagLevel::Critical,
"Decompressing failed. The input data was corrupted or incomplete.", context);
216 m_dataSize = decompressedSize;
218 buffer = make_unique<char[]>(m_dataSize);
219 reader.read(buffer.get(), m_dataSize);
235 }
catch (
const ConversionException &) {
236 diag.emplace_back(
DiagLevel::Warning,
"The value of track/disk position frame is not numeric and will be ignored.", context);
244 const auto parsedStringRef =
parseSubstring(buffer.get() + 1, m_dataSize - 1, dataEncoding,
false, diag);
246 ? convertUtf16BEToUtf8(get<0>(parsedStringRef), get<1>(parsedStringRef))
247 : convertUtf16LEToUtf8(get<0>(parsedStringRef), get<1>(parsedStringRef));
248 milliseconds = string(convertedStringData.first.get(), convertedStringData.second);
250 milliseconds =
parseString(buffer.get() + 1, m_dataSize - 1, dataEncoding,
false, diag);
253 }
catch (
const ConversionException &) {
254 diag.emplace_back(
DiagLevel::Warning,
"The value of the length frame is not numeric and will be ignored.", context);
261 const auto genreDenotation =
parseWideString(buffer.get() + 1, m_dataSize - 1, dataEncoding,
false, diag);
264 const auto genreDenotation =
parseString(buffer.get() + 1, m_dataSize - 1, dataEncoding,
false, diag);
267 if (genreIndex != -1) {
273 const auto substr =
parseSubstring(buffer.get() + 1, m_dataSize - 1, dataEncoding,
false, diag);
278 const auto substr =
parseSubstring(buffer.get() + 1, m_dataSize - 1, dataEncoding,
false, diag);
336 void Id3v2Frame::reset()
357 Id3v2FrameMaker::Id3v2FrameMaker(Id3v2Frame &frame, byte
version, Diagnostics &diag)
359 , m_frameId(m_frame.id())
362 const string context(
"making " % m_frame.frameIdString() +
" frame");
365 if (m_frame.value().isEmpty()) {
367 throw InvalidDataException();
369 if (m_frame.isEncrypted()) {
370 diag.emplace_back(
DiagLevel::Critical,
"Cannot make an encrypted frame (isn't supported by this tagging library).", context);
371 throw InvalidDataException();
373 if (m_frame.hasPaddingReached()) {
374 diag.emplace_back(
DiagLevel::Critical,
"Cannot make a frame which is marked as padding.", context);
375 throw InvalidDataException();
377 if (
version < 3 && m_frame.isCompressed()) {
378 diag.emplace_back(
DiagLevel::Warning,
"Compression is not supported by the version of ID3v2 and won't be applied.", context);
380 if (
version < 3 && (m_frame.flag() || m_frame.group())) {
382 "The existing flag and group information is not supported by the version of ID3v2 and will be ignored/discarted.", context);
391 "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.",
393 throw InvalidDataException();
401 "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.",
403 throw InvalidDataException();
418 m_frame.makeString(m_data, m_decompressedSize, ConversionUtilities::numberToString(m_frame.value().toTimeSpan().totalMilliseconds()),
424 m_data, m_decompressedSize, ConversionUtilities::numberToString(m_frame.value().toStandardGenreIndex()),
TagTextEncoding::Latin1);
433 m_frame.makeString(m_data, m_decompressedSize, m_frame.value().toString(), m_frame.value().dataEncoding());
439 m_frame.makePicture(m_data, m_decompressedSize, m_frame.value(), m_frame.isTypeInfoAssigned() ? m_frame.typeInfo() : 0,
version);
445 m_frame.makeComment(m_data, m_decompressedSize, m_frame.value(),
version, diag);
449 m_data = make_unique<char[]>(m_decompressedSize = m_frame.value().dataSize());
450 copy(m_frame.value().dataPointer(), m_frame.value().dataPointer() + m_decompressedSize, m_data.get());
452 }
catch (
const ConversionException &) {
453 diag.emplace_back(
DiagLevel::Critical,
"Assigned value can not be converted appropriately.", context);
454 throw InvalidDataException();
458 if (
version >= 3 && m_frame.isCompressed()) {
459 m_dataSize = compressBound(m_decompressedSize);
460 auto compressedData = make_unique<char[]>(m_decompressedSize);
461 switch (compress(reinterpret_cast<Bytef *>(compressedData.get()), reinterpret_cast<uLongf *>(&m_dataSize),
462 reinterpret_cast<Bytef *
>(m_data.get()), m_decompressedSize)) {
464 diag.emplace_back(
DiagLevel::Critical,
"Decompressing failed. The source buffer was too small.", context);
465 throw InvalidDataException();
467 diag.emplace_back(
DiagLevel::Critical,
"Decompressing failed. The destination buffer was too small.", context);
468 throw InvalidDataException();
471 m_data.swap(compressedData);
473 m_dataSize = m_decompressedSize;
478 m_requiredSize = m_dataSize;
484 m_requiredSize += 10;
486 if (m_frame.hasGroupInformation()) {
490 if (
version >= 3 && m_frame.isCompressed()) {
506 writer.writeUInt24BE(m_frameId);
507 writer.writeUInt24BE(m_dataSize);
509 writer.writeUInt32BE(m_frameId);
510 if (m_version >= 4) {
511 writer.writeSynchsafeUInt32BE(m_dataSize);
513 writer.writeUInt32BE(m_dataSize);
515 writer.writeUInt16BE(m_frame.
flag());
517 writer.writeByte(m_frame.
group());
520 if (m_version >= 4) {
521 writer.writeSynchsafeUInt32BE(m_decompressedSize);
523 writer.writeUInt32BE(m_decompressedSize);
527 writer.write(m_data.get(), m_dataSize);
538 switch (textEncodingByte) {
559 switch (textEncoding) {
590 tuple<const char *, size_t, const char *> res(buffer, 0, buffer + bufferSize);
595 if ((bufferSize >= 3) && (ConversionUtilities::BE::toUInt24(buffer) == 0x00EFBBBF)) {
597 diag.emplace_back(
DiagLevel::Critical,
"Denoted character set is Latin-1 but an UTF-8 BOM is present - assuming UTF-8.",
603 const char *pos = get<0>(res);
604 for (; *pos != 0x00; ++pos) {
605 if (pos < get<2>(res)) {
615 get<2>(res) = pos + 1;
620 if (bufferSize >= 2) {
621 switch (ConversionUtilities::LE::toUInt16(buffer)) {
625 "Denoted character set is UTF-16 Big Endian but UTF-16 Little Endian BOM is present - assuming UTF-16 LE.",
636 const uint16 *pos =
reinterpret_cast<const uint16 *
>(get<0>(res));
637 for (; *pos != 0x0000; ++pos) {
638 if (pos < reinterpret_cast<const uint16 *>(get<2>(res))) {
648 get<2>(res) = reinterpret_cast<const char *>(pos + 1);
663 return string(get<0>(substr), get<1>(substr));
676 u16string res(reinterpret_cast<u16string::const_pointer>(get<0>(substr)), get<1>(substr) / 2);
695 if ((maxSize >= 2) && (ConversionUtilities::BE::toUInt16(buffer) == 0xFFFE)) {
697 }
else if ((maxSize >= 2) && (ConversionUtilities::BE::toUInt16(buffer) == 0xFEFF)) {
702 if ((maxSize >= 3) && (ConversionUtilities::BE::toUInt24(buffer) == 0x00EFBBBF)) {
718 static const string context(
"parsing ID3v2.2 picture frame");
723 const char *end = buffer + maxSize;
725 typeInfo =
static_cast<unsigned char>(*(buffer + 4));
726 auto substr =
parseSubstring(buffer + 5, end - 5 - buffer, dataEncoding,
true, diag);
727 tagValue.
setDescription(
string(get<0>(substr), get<1>(substr)), dataEncoding);
728 if (get<2>(substr) >= end) {
729 diag.emplace_back(
DiagLevel::Critical,
"Picture frame is incomplete (actual data is missing).", context);
744 static const string context(
"parsing ID3v2.3 picture frame");
745 const char *end = buffer + maxSize;
748 auto substr =
parseSubstring(buffer + 1, maxSize - 1, mimeTypeEncoding,
true, diag);
749 if (get<1>(substr)) {
750 tagValue.
setMimeType(
string(get<0>(substr), get<1>(substr)));
752 if (get<2>(substr) >= end) {
753 diag.emplace_back(
DiagLevel::Critical,
"Picture frame is incomplete (type info, description and actual data are missing).", context);
756 typeInfo =
static_cast<unsigned char>(*get<2>(substr));
757 if (++get<2>(substr) >= end) {
758 diag.emplace_back(
DiagLevel::Critical,
"Picture frame is incomplete (description and actual data are missing).", context);
761 substr =
parseSubstring(get<2>(substr), end - get<2>(substr), dataEncoding,
true, diag);
762 tagValue.
setDescription(
string(get<0>(substr), get<1>(substr)), dataEncoding);
763 if (get<2>(substr) >= end) {
764 diag.emplace_back(
DiagLevel::Critical,
"Picture frame is incomplete (actual data is missing).", context);
778 static const string context(
"parsing comment/unsynchronized lyrics frame");
779 const char *end = buffer +
dataSize;
789 tagValue.
setDescription(
string(get<0>(substr), get<1>(substr)), dataEncoding);
790 if (get<2>(substr) > end) {
791 diag.emplace_back(
DiagLevel::Critical,
"Comment frame is incomplete (description not terminated?).", context);
794 substr =
parseSubstring(get<2>(substr), end - get<2>(substr), dataEncoding,
false, diag);
819 std::unique_ptr<
char[]> &buffer, uint32 &bufferSize,
TagTextEncoding encoding,
const char *data, std::size_t dataSize)
825 char *bufferDataAddress;
831 buffer = make_unique<char[]>(bufferSize = 1 +
dataSize + 1);
833 bufferDataAddress = buffer.get() + 1;
838 buffer = make_unique<char[]>(bufferSize = 1 + 2 +
dataSize + 2);
840 ConversionUtilities::LE::getBytes(
842 bufferDataAddress = buffer.get() + 3;
850 copy(data, data +
dataSize, bufferDataAddress);
862 ConversionUtilities::LE::getBytes(static_cast<uint16>(0xFEFF), buffer);
865 ConversionUtilities::BE::getBytes(static_cast<uint16>(0xFEFF), buffer);
879 StringData convertedDescription;
880 string::size_type descriptionSize = picture.
description().find(
882 if (descriptionSize == string::npos) {
888 convertedDescription = convertUtf8ToUtf16LE(picture.
description().data(), descriptionSize);
889 descriptionSize = convertedDescription.second;
893 buffer = make_unique<char[]>(bufferSize = 1 + 3 + 1 + descriptionSize
897 char *offset = buffer.get();
901 const char *imageFormat;
902 if (picture.
mimeType() ==
"image/jpeg") {
904 }
else if (picture.
mimeType() ==
"image/png") {
906 }
else if (picture.
mimeType() ==
"image/gif") {
908 }
else if (picture.
mimeType() ==
"-->") {
909 imageFormat = picture.
mimeType().data();
913 strncpy(++offset, imageFormat, 3);
917 offset +=
makeBom(offset + 1, descriptionEncoding);
918 if (convertedDescription.first) {
919 copy(convertedDescription.first.get(), convertedDescription.first.get() + descriptionSize, ++offset);
921 picture.
description().copy(++offset, descriptionSize);
923 *(offset += descriptionSize) = 0x00;
943 StringData convertedDescription;
944 string::size_type descriptionSize = picture.
description().find(
946 if (descriptionSize == string::npos) {
952 convertedDescription = convertUtf8ToUtf16LE(picture.
description().data(), descriptionSize);
953 descriptionSize = convertedDescription.second;
956 string::size_type mimeTypeSize = picture.
mimeType().find(
'\0');
957 if (mimeTypeSize == string::npos) {
958 mimeTypeSize = picture.
mimeType().length();
962 buffer = make_unique<char[]>(bufferSize = 1 + mimeTypeSize + 1 + 1 + descriptionSize
966 char *offset = buffer.get();
970 picture.
mimeType().copy(++offset, mimeTypeSize);
971 *(offset += mimeTypeSize) = 0x00;
975 offset +=
makeBom(offset + 1, descriptionEncoding);
976 if (convertedDescription.first) {
977 copy(convertedDescription.first.get(), convertedDescription.first.get() + descriptionSize, ++offset);
979 picture.
description().copy(++offset, descriptionSize);
981 *(offset += descriptionSize) = 0x00;
994 static const string context(
"making comment frame");
997 if (!
comment.description().empty() && encoding !=
comment.descriptionEncoding()) {
998 diag.emplace_back(
DiagLevel::Critical,
"Data enoding and description encoding aren't equal.", context);
1001 const string &lng =
comment.language();
1002 if (lng.length() > 3) {
1003 diag.emplace_back(
DiagLevel::Critical,
"The language must be 3 bytes long (ISO-639-2).", context);
1006 StringData convertedDescription;
1007 string::size_type descriptionSize =
comment.description().find(
1009 if (descriptionSize == string::npos) {
1010 descriptionSize =
comment.description().size();
1015 convertedDescription = convertUtf8ToUtf16LE(
comment.description().data(), descriptionSize);
1016 descriptionSize = convertedDescription.second;
1019 const auto data =
comment.toString(encoding);
1020 buffer = make_unique<char[]>(bufferSize = 1 + 3 + descriptionSize + data.size()
1023 char *offset = buffer.get();
1027 for (
unsigned int i = 0; i < 3; ++i) {
1028 *(++offset) = (lng.length() > i) ? lng[i] : 0x00;
1031 offset +=
makeBom(offset + 1, encoding);
1032 if (convertedDescription.first) {
1033 copy(convertedDescription.first.get(), convertedDescription.first.get() + descriptionSize, ++offset);
1035 comment.description().copy(++offset, descriptionSize);
1037 offset += descriptionSize;
1043 offset +=
makeBom(offset + 1, encoding);
1044 data.copy(++offset, data.size());
uint16 flag() const
Returns the flags.
bool isShortId(uint32 id)
Returns an indication whether the specified id is a short frame id.
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.
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.
std::size_t dataSize() const
Returns the size of the assigned value in bytes.
uint32 convertToLongId(uint32 id)
Converts the specified short frame ID to the equivalent long frame ID.
TAG_PARSER_EXPORT const char * comment()
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.
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.
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.
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.
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.
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.
const IdentifierType & id() const
Returns the id of the current TagField.
std::size_t makeBom(char *buffer, TagTextEncoding encoding)
Writes the BOM for the specified encoding to the specified buffer.
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.
Id3v2Frame()
Constructs a new Id3v2Frame.
void setDescription(const std::string &value, TagTextEncoding encoding=TagTextEncoding::Latin1)
Sets the description.
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.
uint32 dataSize() const
Returns the size of the data stored in the frame in bytes.
void assignData(const char *data, size_t length, TagDataType type=TagDataType::Binary, TagTextEncoding encoding=TagTextEncoding::Latin1)
Assigns a copy of the given data.
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.
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...
TagValue & value()
Returns the value of the current TagField.