diff options
Diffstat (limited to 'Src/replicant/nsid3v2')
37 files changed, 5737 insertions, 0 deletions
diff --git a/Src/replicant/nsid3v2/extendedheader.cpp b/Src/replicant/nsid3v2/extendedheader.cpp new file mode 100644 index 00000000..b0440ae7 --- /dev/null +++ b/Src/replicant/nsid3v2/extendedheader.cpp @@ -0,0 +1,108 @@ +#include "extendedheader.h" +#include "util.h" +#include <string.h> +#include <stdlib.h> +#include "foundation/error.h" + +ID3v2_21::ExtendedHeaderBase::ExtendedHeaderBase(const ID3v2::Header &_tagHeader) : tagHeader(_tagHeader) +{ + memset(&headerData, 0, sizeof(ExtendedHeaderData)); + data = 0; + data_size = 0; +} + +uint32_t ID3v2_21::ExtendedHeaderBase::Size() const +{ + return headerData.size; +} + +int ID3v2_21::ExtendedHeaderBase::Parse(const void *_data, size_t len, size_t *bytes_read) +{ + if (len < SIZE) + return 1; + + if (tagHeader.Unsynchronised()) + { + *bytes_read = ID3v2::Util::UnsynchroniseTo(&headerData, _data, SIZE); + } + else + { + memcpy(&headerData, _data, SIZE); + *bytes_read = SIZE; + } + + _data = (const uint8_t *)_data+SIZE; + + /* read any data after the header */ + data_size = Size(); + if (data_size) + { + /* sanity check size */ + if (tagHeader.Unsynchronised()) + { + if (ID3v2::Util::UnsynchronisedInputSize(_data, data_size) > len) + return 1; + } + else if (data_size > len) + return 1; + + /* allocate and read data */ + data = malloc(data_size); + if (tagHeader.Unsynchronised()) + { + *bytes_read += ID3v2::Util::UnsynchroniseTo(data, _data, data_size); + } + else + { + memcpy(data, _data, data_size); + *bytes_read += data_size; + } + } + + return 0; +} + +/* === ID3v2.3 === */ +ID3v2_3::ExtendedHeader::ExtendedHeader(const ID3v2::Header &_tagHeader) : ID3v2_21::ExtendedHeaderBase(_tagHeader) +{ +} + +/* === ID3v2.4 === */ +ID3v2_4::ExtendedHeader::ExtendedHeader(const ID3v2::Header &_tagHeader) : ID3v2_21::ExtendedHeaderBase(_tagHeader) +{ +} + +uint32_t ID3v2_4::ExtendedHeader::Size() const +{ + return ID3v2::Util::Int28To32(headerData.size); +} + +int ID3v2_4::ExtendedHeader::Parse(const void *_data, size_t len, size_t *bytes_read) +{ + if (len < SIZE) + return 1; + + memcpy(&headerData, _data, SIZE); + *bytes_read = SIZE; + + + _data = (const uint8_t *)_data+SIZE; + + /* read any data after the header */ + data_size = Size(); + if (data_size) + { + /* sanity check size */ + if (data_size > len) + return 1; + + /* allocate and read data */ + data = malloc(data_size); + if (!data) + return NErr_OutOfMemory; + memcpy(data, _data, data_size); + *bytes_read += data_size; + } + + return 0; +} diff --git a/Src/replicant/nsid3v2/extendedheader.h b/Src/replicant/nsid3v2/extendedheader.h new file mode 100644 index 00000000..fe85b614 --- /dev/null +++ b/Src/replicant/nsid3v2/extendedheader.h @@ -0,0 +1,52 @@ +#pragma once +#include "header.h" + +namespace ID3v2_21 +{ +#pragma pack(push, 1) + struct ExtendedHeaderData + { + uint32_t size; + }; +#pragma pack(pop) + + class ExtendedHeaderBase + { + public: + ExtendedHeaderBase(const ID3v2::Header &_tagHeader); + int Parse(const void *_data, size_t len, size_t *bytes_read); + enum + { + SIZE=4, + }; + protected: + uint32_t Size() const; + void *data; + size_t data_size; + ExtendedHeaderData headerData; + const ID3v2::Header &tagHeader; + }; +} + +namespace ID3v2_3 +{ + class ExtendedHeader : public ID3v2_21::ExtendedHeaderBase + { + public: + ExtendedHeader(const ID3v2::Header &_tagHeader); + }; +} + +namespace ID3v2_4 +{ + class ExtendedHeader : public ID3v2_21::ExtendedHeaderBase + { + public: + ExtendedHeader(const ID3v2::Header &_tagHeader); + int Parse(const void *_data, size_t len, size_t *bytes_read); + + protected: + uint32_t Size() const; + + }; +} diff --git a/Src/replicant/nsid3v2/frame.cpp b/Src/replicant/nsid3v2/frame.cpp new file mode 100644 index 00000000..12cdd1c6 --- /dev/null +++ b/Src/replicant/nsid3v2/frame.cpp @@ -0,0 +1,786 @@ +#include "frame.h" +#include "util.h" +#ifdef _WIN32 +#include "zlib/zlib.h" +#else +#include "zlib/zlib.h" +#endif +#include "frames.h" +#include <string.h> +#include <stdlib.h> +#include "nu/ByteReader.h" +#include "nu/ByteWriter.h" +#include "foundation/error.h" +#include "nsid3v2.h" + +/* === ID3v2 common === */ +ID3v2::Frame::Frame() +{ + data = 0; + data_size = 0; +} + +ID3v2::Frame::~Frame() +{ + free(data); +} + +int ID3v2::Frame::GetData(const void **_data, size_t *data_len) const +{ + if (data) + { + *_data = data; + *data_len = data_size; + return NErr_Success; + } + else + return NErr_NullPointer; +} + +size_t ID3v2::Frame::GetDataSize() const +{ + return data_size; +} + +int ID3v2::Frame::NewData(size_t new_len, void **_data, size_t *_data_len) +{ + // we DO NOT update the header, as its meant to hold the original data + void *new_data = realloc(data, new_len); + if (new_data) + { + data = new_data; + data_size = new_len; + *_data = data; + *_data_len = data_size; + return NErr_Success; + } + else + return NErr_OutOfMemory; +} + +bool ID3v2::Frame::Encrypted() const +{ + return false; +} + +bool ID3v2::Frame::Compressed() const +{ + return false; +} + +bool ID3v2::Frame::Grouped() const +{ + return false; +} + +bool ID3v2::Frame::ReadOnly() const +{ + return false; +} + +bool ID3v2::Frame::FrameUnsynchronised() const +{ + return false; +} + +bool ID3v2::Frame::DataLengthIndicated() const +{ + return false; +} + +bool ID3v2::Frame::TagAlterPreservation() const +{ + return false; +} + +bool ID3v2::Frame::FileAlterPreservation() const +{ + return false; +} + +static inline void Advance(const void *&data, size_t &len, size_t amount) +{ + data = (const uint8_t *)data + amount; + len -= amount; +} + +static inline void AdvanceBoth(const void *&data, size_t &len, size_t &len2, size_t amount) +{ + data = (const uint8_t *)data + amount; + len -= amount; + len2 -= amount; +} + + +/* === ID3v2.2 === */ +ID3v2_2::Frame::Frame(const ID3v2::Header &_header, const int8_t *id, int flags) : header(_header, id, flags) +{ +} + +ID3v2_2::Frame::Frame(const FrameHeader &_header) : header(_header) +{ +} + +int ID3v2_2::Frame::Parse(const void *_data, size_t len, size_t *read) +{ + *read = 0; + data_size = header.FrameSize(); // size of frame AFTER re-synchronization + + /* check to make sure that we have enough input data to read the data */ + if (header.Unsynchronised()) + { + /* this is tricky, because the stored size reflects after re-synchronization, + but the incoming data is unsynchronized */ + if (ID3v2::Util::UnsynchronisedInputSize(_data, data_size) > len) + return 1; + } + else if (data_size > len) + return 1; + + /* allocate memory (real data_size) */ + data = malloc(data_size); + if (!data) + return 1; + + /* === Read the data === */ + if (header.Unsynchronised()) + { + *read += ID3v2::Util::UnsynchroniseTo(data, _data, data_size); + } + else // normal data + { + memcpy(data, _data, data_size); + *read += data_size; + } + + return NErr_Success; +} + + +int ID3v2_2::Frame::SerializedSize(uint32_t *length, const ID3v2::Header &tag_header, int flags) const +{ + ID3v2_2::FrameHeader new_header(header, tag_header); + // TODO: for now, we're not going to deal with compression + new_header.SetSize(data_size); + + uint32_t current_length=0; + new_header.SerializedSize(¤t_length); + + if (new_header.Unsynchronised()) + { + current_length += ID3v2::Util::SynchronisedSize(data, data_size); + } + else + { + current_length += new_header.FrameSize(); + } + + *length = current_length; + return NErr_Success; +} + +int ID3v2_2::Frame::Serialize(void *output, uint32_t *written, const ID3v2::Header &tag_header, int flags) const +{ + size_t current_length = FrameHeader::SIZE; + uint8_t *data_ptr = (uint8_t *)output; + ID3v2_2::FrameHeader new_header(header, tag_header); + new_header.SetSize(data_size); + + // write frame header + new_header.Serialize(data_ptr); + data_ptr += FrameHeader::SIZE; + if (new_header.Unsynchronised()) + { + current_length += ID3v2::Util::SynchroniseTo(data_ptr, data, data_size); + } + else + { + memcpy(data_ptr, data, data_size); + current_length += data_size; + } + *written = current_length; + return NErr_Success; +} + + +const int8_t *ID3v2_2::Frame::GetIdentifier() const +{ + return header.GetIdentifier(); +} + + +/* === ID3v2.3 === */ +ID3v2_3::Frame::Frame(const ID3v2::Header &_header, const int8_t *id, int flags) : header(_header, id, flags) +{ +} + +ID3v2_3::Frame::Frame(const FrameHeader &_header) : header(_header) +{ +} + +/* helper function +reads num_bytes from input into output, dealing with re-synchronization and length checking +increments input pointer +increments bytes_read value by number of input bytes read (different from num_bytes when data is unsynchronized +decrements input_len by bytes read +decrements output_len by bytes written +*/ +bool ID3v2_3::Frame::ReadData(void *output, const void *&input, size_t &input_len, size_t &frame_len, size_t num_bytes, size_t *bytes_read) const +{ + /* verify that we have enough data in the frame */ + if (num_bytes > frame_len) + return false; + + /* verify that we have enough data in the buffer */ + size_t bytes_to_read; + if (header.Unsynchronised()) + bytes_to_read = ID3v2::Util::UnsynchronisedInputSize(input, num_bytes); + else + bytes_to_read = num_bytes; + + if (bytes_to_read > input_len) + return false; + + /* read data */ + if (header.Unsynchronised()) + { + *bytes_read += ID3v2::Util::SynchroniseTo(&output, input, num_bytes); + } + else + { + *bytes_read += num_bytes; + memcpy(output, input, num_bytes); + } + + /* increment input pointer */ + input = (const uint8_t *)input + bytes_to_read; + + /* decrement sizes */ + frame_len -= num_bytes; + input_len -= bytes_to_read; + return true; +} + +/* benski> this function is a bit complex +we have two things to worry about, and can have any combination of the two +1) Is the data 'unsynchronized' +2) Is the data compressed (zlib) + +we keep track of three sizes: +len - number of bytes in input buffer +data_size - number of bytes of output data buffer +frame_size - number of bytes of data in frame AFTER re-synchronization + +frame_size==data_size when compression is OFF +*/ +int ID3v2_3::Frame::Parse(const void *_data, size_t len, size_t *read) +{ + *read = 0; + size_t frame_size = header.FrameSize(); // size of frame AFTER re-synchronization + + if (header.Compressed()) + { + // read 4 bytes of decompressed size + uint8_t raw_size[4]; + if (ReadData(raw_size, _data, len, frame_size, 4, read) == false) + return 1; + + bytereader_value_t byte_reader; + bytereader_init(&byte_reader, raw_size, 4); + + data_size = bytereader_read_u32_be(&byte_reader); + } + + /* Check for group identity. If this exists, we'll store it separate from the raw data */ + if (header.Grouped()) + { + // read 1 byte for group identity + if (ReadData(&group_identity, _data, len, frame_size, 1, read) == false) + return 1; + } + + if (!header.Compressed()) + { + data_size = frame_size; + } + + /* check to make sure that we have enough input data to read the data */ + if (!header.Compressed() && header.Unsynchronised()) + { + /* this is tricky, because the stored size reflects after re-synchronization, + but the incoming data is unsynchronized */ + if (ID3v2::Util::UnsynchronisedInputSize(_data, data_size) > len) + return 1; + } + else if (frame_size > len) + return 1; + + /* allocate memory (real data_size) */ + data = malloc(data_size); + if (!data) + return NErr_OutOfMemory; + + /* === Read the data === */ + if (header.Compressed()) + { + if (header.Unsynchronised()) // compressed AND unsynchronized.. what a pain!! + { + // TODO: combined re-synchronization + inflation + void *temp = malloc(frame_size); + if (!temp) + return NErr_OutOfMemory; + + *read += ID3v2::Util::UnsynchroniseTo(temp, _data, frame_size); + + uLongf uncompressedSize = data_size; + int ret = uncompress((Bytef *)data, &uncompressedSize, (const Bytef *)temp, frame_size); + free(temp); + if (ret != Z_OK) + return 1; + } + else + { + uLongf uncompressedSize = data_size; + if (uncompress((Bytef *)data, &uncompressedSize, (const Bytef *)_data, frame_size) != Z_OK) + return 1; + *read += frame_size; + } + } + else if (header.Unsynchronised()) + { + *read += ID3v2::Util::UnsynchroniseTo(data, _data, data_size); + } + else // normal data + { + memcpy(data, _data, data_size); + *read += data_size; + } + + return NErr_Success; +} + +int ID3v2_3::Frame::SerializedSize(uint32_t *length, const ID3v2::Header &tag_header, int flags) const +{ + ID3v2_3::FrameHeader new_header(header, tag_header); + // TODO: for now, we're not going to deal with compression + new_header.ClearCompressed(); + new_header.SetSize(data_size); + + uint32_t current_length=0; + new_header.SerializedSize(¤t_length); + + if (new_header.Unsynchronised()) + { + if (new_header.Compressed()) + { + uint8_t data_length[4]; + bytewriter_s byte_writer; + bytewriter_init(&byte_writer, data_length, 4); + bytewriter_write_u32_be(&byte_writer, data_size); + current_length += ID3v2::Util::SynchronisedSize(&data_length, 4); + } + + if (new_header.Grouped()) + current_length += ID3v2::Util::SynchronisedSize(&group_identity, 1); + current_length += ID3v2::Util::SynchronisedSize(data, data_size); + } + else + { + current_length += new_header.FrameSize(); + } + + *length = current_length; + return NErr_Success; +} + +int ID3v2_3::Frame::Serialize(void *output, uint32_t *written, const ID3v2::Header &tag_header, int flags) const +{ + size_t current_length = FrameHeaderBase::SIZE; + uint8_t *data_ptr = (uint8_t *)output; + ID3v2_3::FrameHeader new_header(header, tag_header); + // TODO: for now, we're not going to deal with compression + new_header.ClearCompressed(); + new_header.SetSize(data_size); + + // write frame header + uint32_t header_size; + new_header.Serialize(data_ptr, &header_size); + data_ptr += header_size; + if (new_header.Unsynchronised()) + { + if (new_header.Compressed()) + { + uint8_t data_length[4]; + bytewriter_s byte_writer; + bytewriter_init(&byte_writer, data_length, 4); + bytewriter_write_u32_be(&byte_writer, data_size); + current_length += ID3v2::Util::SynchroniseTo(data_ptr, &data_length, 4); + data_ptr+=4; + } + + if (new_header.Grouped()) + current_length += ID3v2::Util::SynchroniseTo(data_ptr++, &group_identity, 1); + current_length += ID3v2::Util::SynchroniseTo(data_ptr, data, data_size); + } + else + { + if (new_header.Compressed()) + { + bytewriter_s byte_writer; + bytewriter_init(&byte_writer, data_ptr, 4); + bytewriter_write_u32_be(&byte_writer, data_size); + data_ptr+=4; + } + + if (new_header.Grouped()) + { + *data_ptr++ = group_identity; + current_length++; + } + memcpy(data_ptr, data, data_size); + current_length += data_size; + } + *written = current_length; + return NErr_Success; +} + +const int8_t *ID3v2_3::Frame::GetIdentifier() const +{ + return header.GetIdentifier(); +} + +bool ID3v2_3::Frame::Encrypted() const +{ + return header.Encrypted(); +} + +bool ID3v2_3::Frame::Compressed() const +{ + return header.Compressed(); +} + +bool ID3v2_3::Frame::Grouped() const +{ + return header.Grouped(); +} + +bool ID3v2_3::Frame::ReadOnly() const +{ + return header.ReadOnly(); +} + +bool ID3v2_3::Frame::TagAlterPreservation() const +{ + return header.TagAlterPreservation(); +} + +bool ID3v2_3::Frame::FileAlterPreservation() const +{ + return header.FileAlterPreservation(); +} + + +/* === ID3v2.4 === */ +ID3v2_4::Frame::Frame(const ID3v2::Header &_header, const int8_t *id, int flags) : header(_header, id, flags) +{ +} + +ID3v2_4::Frame::Frame(const FrameHeader &_header) : header(_header) +{ +} + +/* helper function +reads num_bytes from input into output, dealing with re-synchronization and length checking +increments input pointer +increments bytes_read value by number of input bytes read (different from num_bytes when data is unsynchronized +decrements input_len by bytes read +decrements output_len by bytes written +*/ +bool ID3v2_4::Frame::ReadData(void *output, const void *&input, size_t &input_len, size_t &frame_len, size_t num_bytes, size_t *bytes_read) const +{ + /* verify that we have enough data in the frame */ + if (num_bytes > frame_len) + return false; + + /* verify that we have enough data in the buffer */ + size_t bytes_to_read = num_bytes; + + if (bytes_to_read > input_len) + return false; + + /* read data */ + + *bytes_read += num_bytes; + memcpy(output, input, num_bytes); + + /* increment input pointer */ + input = (const uint8_t *)input + bytes_to_read; + + /* decrement sizes */ + frame_len -= num_bytes; + input_len -= bytes_to_read; + return true; +} + +/* benski> this function is a bit complex +we have two things to worry about, and can have any combination of the two +1) Is the data 'unsynchronized' +2) Is the data compressed (zlib) + +we keep track of three sizes: +len - number of bytes in input buffer +data_size - number of bytes of output data buffer +frame_size - number of bytes of data in frame AFTER re-synchronization + +frame_size==data_size when compression is OFF +*/ +int ID3v2_4::Frame::Parse(const void *_data, size_t len, size_t *read) +{ + *read = 0; + size_t frame_size = header.FrameSize(); + + // TODO: if frame_size >= 128, verify size. iTunes v2.4 parser bug ... + + + /* Check for group identity. If this exists, we'll store it separate from the raw data */ + /* Note: ID3v2.4 puts group identity BEFORE data length indicator, where as v2.3 has it the other way */ + if (header.Grouped()) + { + // read 1 byte for group identity + if (ReadData(&group_identity, _data, len, frame_size, 1, read) == false) + return 1; + } + + if (header.Compressed() || header.DataLengthIndicated()) + { + // read 4 bytes of decompressed size + uint8_t raw_size[4]; + if (ReadData(raw_size, _data, len, frame_size, 4, read) == false) + return 1; + + bytereader_value_t byte_reader; + bytereader_init(&byte_reader, raw_size, 4); + + data_size = bytereader_read_u32_be(&byte_reader); + } + + if (!(header.Compressed() || header.DataLengthIndicated())) + { + data_size = frame_size; + } + + /* check to make sure that we have enough input data to read the data */ + + if (frame_size > len) + return 1; + + if (!header.Compressed() && header.Unsynchronised()) + { + data_size = ID3v2::Util::UnsynchronisedOutputSize(_data, frame_size); + } + + /* allocate memory (real data_size) */ + data = malloc(data_size); + if (!data) + return NErr_OutOfMemory; + + /* === Read the data === */ + if (header.Compressed()) + { + if (header.Unsynchronised()) // compressed AND unsynchronized.. what a pain!! + { + // TODO: combined re-synchronization + inflation + size_t sync_size = ID3v2::Util::UnsynchronisedOutputSize(_data, frame_size); + void *temp = malloc(sync_size); + if (!temp) + return NErr_OutOfMemory; + + *read += ID3v2::Util::UnsynchroniseTo(temp, _data, sync_size); + + uLongf uncompressedSize = data_size; + int ret = uncompress((Bytef *)data, &uncompressedSize, (const Bytef *)temp, sync_size); + /* TODO: realloc and set data_size to uncompressedSize if uncompressedSize was actually lower */ + free(temp); + if (ret != Z_OK) + return 1; + } + else + { + uLongf uncompressedSize = data_size; + if (uncompress((Bytef *)data, &uncompressedSize, (const Bytef *)_data, frame_size) != Z_OK) + return 1; + /* TODO: realloc and set data_size to uncompressedSize if uncompressedSize was actually lower */ + *read += frame_size; + } + } + else if (header.Unsynchronised()) + { + *read += ID3v2::Util::UnsynchroniseTo(data, _data, data_size); + } + else // normal data + { + memcpy(data, _data, data_size); + *read += data_size; + } + + return 0; +} + +int ID3v2_4::Frame::SerializedSize(uint32_t *length, const ID3v2::Header &tag_header, int flags) const +{ + ID3v2_4::FrameHeader new_header(header, tag_header); + // TODO: for now, we're not going to deal with compression + new_header.ClearCompressed(); + switch(flags & Serialize_UnsynchronizeMask) + { + case Serialize_Unsynchronize: + // TODO: + break; + case Serialize_NoUnsynchronize: + new_header.ClearUnsynchronized(); + break; + } + + // TODO: this doesn't handle compression + if (new_header.Unsynchronised()) + { + size_t unsynchronized_data_size = ID3v2::Util::SynchronisedSize(data, data_size); + new_header.SetSize(unsynchronized_data_size); + } + else + { + new_header.SetSize(data_size); + } + + + size_t current_length = ID3v2_4::FrameHeader::SIZE; + + if (new_header.Unsynchronised()) + { + if (new_header.DataLengthIndicated() || new_header.Compressed()) + { + current_length += 4; + } + + if (new_header.Grouped()) + current_length += ID3v2::Util::SynchronisedSize(&group_identity, 1); + current_length += ID3v2::Util::SynchronisedSize(data, data_size); + } + else + { + current_length += new_header.FrameSize(); + } + + *length = current_length; + return NErr_Success; +} + +int ID3v2_4::Frame::Serialize(void *output, uint32_t *written, const ID3v2::Header &tag_header, int flags) const +{ + size_t current_length = ID3v2_4::FrameHeader::SIZE; + uint8_t *data_ptr = (uint8_t *)output; + ID3v2_4::FrameHeader new_header(header, tag_header); + // TODO: for now, we're not going to deal with compression + new_header.ClearCompressed(); + switch(flags & Serialize_UnsynchronizeMask) + { + case Serialize_Unsynchronize: + // TODO: + break; + case Serialize_NoUnsynchronize: + new_header.ClearUnsynchronized(); + break; + } + + // TODO: this doesn't handle compression + if (new_header.Unsynchronised()) + { + size_t unsynchronized_data_size = ID3v2::Util::SynchronisedSize(data, data_size); + new_header.SetSize(unsynchronized_data_size); + } + else + { + new_header.SetSize(data_size); + } + + // write frame header + uint32_t header_size; + new_header.Serialize(data_ptr, &header_size); + data_ptr += header_size; + + if (new_header.Compressed() || new_header.DataLengthIndicated()) + { + bytewriter_s byte_writer; + bytewriter_init(&byte_writer, data_ptr, 4); + bytewriter_write_u32_be(&byte_writer, ID3v2::Util::Int32To28(data_size)); + data_ptr+=4; + current_length+=4; + } + + if (new_header.Unsynchronised()) + { + + if (Grouped()) + current_length += ID3v2::Util::SynchroniseTo(data_ptr++, &group_identity, 1); + current_length += ID3v2::Util::SynchroniseTo(data_ptr, data, data_size); + } + else + { + + if (new_header.Grouped()) + { + *data_ptr++ = group_identity; + current_length++; + } + memcpy(data_ptr, data, data_size); + current_length += data_size; + } + *written = current_length; + return NErr_Success; +} + +const int8_t *ID3v2_4::Frame::GetIdentifier() const +{ + return header.GetIdentifier(); +} + +bool ID3v2_4::Frame::Encrypted() const +{ + return header.Encrypted(); +} + +bool ID3v2_4::Frame::Compressed() const +{ + return header.Compressed(); +} + +bool ID3v2_4::Frame::Grouped() const +{ + return header.Grouped(); +} + +bool ID3v2_4::Frame::ReadOnly() const +{ + return header.ReadOnly(); +} + +bool ID3v2_4::Frame::FrameUnsynchronised() const +{ + return header.FrameUnsynchronised(); +} + +bool ID3v2_4::Frame::DataLengthIndicated() const +{ + return header.DataLengthIndicated(); +} + + +bool ID3v2_4::Frame::TagAlterPreservation() const +{ + return header.TagAlterPreservation(); +} + +bool ID3v2_4::Frame::FileAlterPreservation() const +{ + return header.FileAlterPreservation(); +} diff --git a/Src/replicant/nsid3v2/frame.h b/Src/replicant/nsid3v2/frame.h new file mode 100644 index 00000000..3cf5fe99 --- /dev/null +++ b/Src/replicant/nsid3v2/frame.h @@ -0,0 +1,117 @@ +#pragma once +#include "frameheader.h" +#include "nu/PtrDeque.h" + +namespace ID3v2 +{ + class Frame : public nu::PtrDequeNode + { + public: + virtual ~Frame(); + int NewData(size_t new_len, void **data, size_t *data_len); + int GetData(const void **data, size_t *data_len) const; + size_t GetDataSize() const; + virtual const int8_t *GetIdentifier() const=0; + virtual unsigned int GetVersion() const=0; + + virtual bool Encrypted() const; + virtual bool Compressed() const; + virtual bool Grouped() const; + virtual bool ReadOnly() const; + virtual bool FrameUnsynchronised() const; + virtual bool DataLengthIndicated() const; + virtual bool TagAlterPreservation() const; + virtual bool FileAlterPreservation() const; + protected: + Frame(); + void *data; + size_t data_size; /* REAL size, might be different from header.headerData.size */ + }; +} + +namespace ID3v2_2 +{ + class Frame : public ID3v2::Frame + { + public: + Frame(const ID3v2::Header &_header, const int8_t *id, int flags); // creates an empty frame with a given ID + Frame(const ID3v2_2::FrameHeader &_header); + int Parse(const void *_data, size_t len, size_t *read); + int SerializedSize(uint32_t *length, const ID3v2::Header &tag_header, int flags) const; + // there is enough room guaranteed to be present because it will be checked with SerializedSize() + int Serialize(void *data, uint32_t *written, const ID3v2::Header &tag_header, int flags) const; + const int8_t *GetIdentifier() const; + unsigned int GetVersion() const { return 2; } + private: + ID3v2_2::FrameHeader header; + }; +} + + +namespace ID3v2_3 +{ + class Frame : public ID3v2::Frame + { + public: + Frame(const ID3v2::Header &_header, const int8_t *id, int flags); // creates an empty frame with a given ID + Frame(const ID3v2_3::FrameHeader &_header); + int Parse(const void *_data, size_t len, size_t *read); + + int SerializedSize(uint32_t *length, const ID3v2::Header &tag_header, int flags) const; + // there is enough room guaranteed to be present because it will be checked with SerializedSize() + int Serialize(void *data, uint32_t *written, const ID3v2::Header &tag_header, int flags) const; + + const int8_t *GetIdentifier() const; + unsigned int GetVersion() const { return 3; } + virtual bool Encrypted() const; + virtual bool Compressed() const; + virtual bool Grouped() const; + virtual bool ReadOnly() const; + virtual bool TagAlterPreservation() const; + virtual bool FileAlterPreservation() const; + + private: + ID3v2_3::FrameHeader header; + uint8_t group_identity; + /* helper function + reads num_bytes from input into output, dealing with re-synchronization and length checking + increments bytes_read value by number of input bytes read (different from num_bytes when data is unsynchronized + decrements input_len by bytes read + decrements output_len by bytes written + */ + bool ReadData(void *output, const void *&input, size_t &input_len, size_t &frame_len, size_t num_bytes, size_t *bytes_read) const; + }; +} + +namespace ID3v2_4 +{ + class Frame : public ID3v2::Frame + { + public: + Frame(const ID3v2::Header &_header, const int8_t *id, int flags); // creates an empty frame with a given ID + Frame(const ID3v2_4::FrameHeader &_header); + int Parse(const void *_data, size_t len, size_t *read); + int SerializedSize(uint32_t *length, const ID3v2::Header &tag_header, int flags) const; + int Serialize(void *data, uint32_t *written, const ID3v2::Header &tag_header, int flags) const; + const int8_t *GetIdentifier() const; + unsigned int GetVersion() const { return 4; } + virtual bool Encrypted() const; + virtual bool Compressed() const; + virtual bool Grouped() const; + virtual bool ReadOnly() const; + virtual bool FrameUnsynchronised() const; + virtual bool DataLengthIndicated() const; + virtual bool TagAlterPreservation() const; + virtual bool FileAlterPreservation() const; + private: + ID3v2_4::FrameHeader header; + uint8_t group_identity; + /* helper function + reads num_bytes from input into output, dealing with re-synchronization and length checking + increments bytes_read value by number of input bytes read (different from num_bytes when data is unsynchronized + decrements input_len by bytes read + decrements output_len by bytes written + */ + bool ReadData(void *output, const void *&input, size_t &input_len, size_t &frame_len, size_t num_bytes, size_t *bytes_read) const; + }; +} diff --git a/Src/replicant/nsid3v2/frame_apic.cpp b/Src/replicant/nsid3v2/frame_apic.cpp new file mode 100644 index 00000000..1276c29d --- /dev/null +++ b/Src/replicant/nsid3v2/frame_apic.cpp @@ -0,0 +1,247 @@ +#include "nsid3v2.h" +#include "nsid3v2/header.h" +#include "nsid3v2/tag.h" +#include "nsid3v2/frame_utils.h" +#include "nu/ByteReader.h" +#include "nu/ByteWriter.h" +#include "nx/nxstring.h" + +struct ParsedPicture +{ + ParsedString mime; + uint8_t picture_type; + ParsedString description; + const void *picture_data; + size_t picture_byte_length; +}; + +static int ParsePicture(const void *data, size_t data_len, ParsedPicture &parsed) +{ + int ret; + if (data_len < 4) + return NErr_NeedMoreData; + + bytereader_value_t byte_reader; + bytereader_init(&byte_reader, data, data_len); + + uint8_t encoding = bytereader_read_u8(&byte_reader); + /* mime type is always latin-1 */ + ret = ParseNullTerminatedString(&byte_reader, 0, parsed.mime); + if (ret != NErr_Success) + return ret; + + if (bytereader_size(&byte_reader) < 2) + return NErr_NeedMoreData; + + parsed.picture_type = bytereader_read_u8(&byte_reader); + + ret = ParseNullTerminatedString(&byte_reader, encoding, parsed.description); + if (ret != NErr_Success) + return ret; + + parsed.picture_data = bytereader_pointer(&byte_reader); + parsed.picture_byte_length = bytereader_size(&byte_reader); + return NErr_Success; +} + +static int ParsePicturev2_2(const void *data, size_t data_len, ParsedPicture &parsed) +{ + int ret; + if (data_len < 6) + return NErr_NeedMoreData; + + bytereader_value_t byte_reader; + bytereader_init(&byte_reader, data, data_len); + + uint8_t encoding = bytereader_read_u8(&byte_reader); + + /* three byte "Image Format" field */ + parsed.mime.encoding = 0; + parsed.mime.data = bytereader_pointer(&byte_reader); + parsed.mime.byte_length = 3; + + bytereader_advance(&byte_reader, 3); + + parsed.picture_type = bytereader_read_u8(&byte_reader); + + ret = ParseNullTerminatedString(&byte_reader, encoding, parsed.description); + if (ret != NErr_Success) + return ret; + + parsed.picture_data = bytereader_pointer(&byte_reader); + parsed.picture_byte_length = bytereader_size(&byte_reader); + return NErr_Success; +} + +int NSID3v2_Frame_Picture_Get(const nsid3v2_frame_t f, nx_string_t *mime, uint8_t *picture_type, nx_string_t *description, const void **picture_data, size_t *length, int text_flags) +{ + const ID3v2::Frame *frame = (const ID3v2::Frame *)f; + if (frame) + { + const void *data; + size_t data_len; + ParsedPicture parsed; + if (frame->GetData(&data, &data_len) == NErr_Success && data_len > 0) + { + int ret; + if (frame->GetVersion() == 2) + ret = ParsePicturev2_2(data, data_len, parsed); + else + ret = ParsePicture(data, data_len, parsed); + + if (ret == NErr_Success) + { + int ret; + if (mime) + { + ret = NXStringCreateFromParsedString(mime, parsed.mime, text_flags); + if (ret != NErr_Success) + return ret; + } + + if (description) + { + ret = NXStringCreateFromParsedString(description, parsed.description, text_flags); + if (ret != NErr_Success) + return ret; + } + + if (picture_type) + *picture_type = parsed.picture_type; + + if (picture_data) + *picture_data = parsed.picture_data; + if (length) + *length = parsed.picture_byte_length; + + return NErr_Success; + } + else + { + return ret; + } + } + + } + return NErr_Empty; +} + +/* ---------------- Setters ---------------- */ +static const char *GetMIME2_2(nx_string_t mime) +{ + if (!mime) + return "\0\0\0"; + + if (NXStringKeywordCompareWithCString(mime, "image/jpeg") == NErr_True || NXStringKeywordCompareWithCString(mime, "image/jpg") == NErr_True) + return "JPG"; + + if (NXStringKeywordCompareWithCString(mime, "image/png") == NErr_True) + return "PNG"; + + if (NXStringKeywordCompareWithCString(mime, "image/gif") == NErr_True) + return "GIF"; + + if (NXStringKeywordCompareWithCString(mime, "image/bmp") == NErr_True) + return "BMP"; + + return "\0\0\0"; +} + +int NSID3v2_Frame_Picture_Set(nsid3v2_frame_t f, nx_string_t mime, uint8_t picture_type, nx_string_t description, const void *picture_data, size_t length, int text_flags) +{ + ID3v2::Frame *frame = (ID3v2::Frame *)f; + if (frame) + { + if (frame->GetVersion() == 2) + { + /* first, we need to get the total encoded size */ + + size_t byte_count_description=0; + if (description) + { + int ret = NXStringGetBytesSize(&byte_count_description, description, nx_charset_latin1, 0); + if (ret != NErr_DirectPointer && ret != NErr_Success) + return ret; + } + + size_t total_size = 1 /* text encoding */ + + 3 /* Image Format is 3 bytes in ID3v2.2*/ + + 1 /* picture type */ + + byte_count_description + 1 /* description + null terminator */ + + length; /* picture length */ + + void *data; + size_t data_size; + int ret = frame->NewData(total_size, &data, &data_size); + if (ret != NErr_Success) + return ret; + + size_t bytes_copied; + bytewriter_s byte_writer; + bytewriter_init(&byte_writer, data, data_size); + bytewriter_write_u8(&byte_writer, 0); /* mark as Latin-1 */ + bytewriter_write_n(&byte_writer, GetMIME2_2(mime), 3); + bytewriter_write_u8(&byte_writer, picture_type); + if (description) + { + NXStringGetBytes(&bytes_copied, description, bytewriter_pointer(&byte_writer), bytewriter_size(&byte_writer), nx_charset_latin1, 0); + bytewriter_advance(&byte_writer, bytes_copied); + } + bytewriter_write_u8(&byte_writer, 0); /* description null terminator */ + bytewriter_write_n(&byte_writer, picture_data, length); + return NErr_Success; + } + else + { + /* first, we need to get the total encoded size */ + size_t byte_count_mime=0; + if (mime) + { + int ret = NXStringGetBytesSize(&byte_count_mime, mime, nx_charset_latin1, 0); + if (ret != NErr_DirectPointer && ret != NErr_Success) + return ret; + } + + size_t byte_count_description=0; + if (description) + { + int ret = NXStringGetBytesSize(&byte_count_description, description, nx_charset_latin1, 0); + if (ret != NErr_DirectPointer && ret != NErr_Success) + return ret; + } + + size_t total_size = 1 /* text encoding */ + + byte_count_mime + 1 /* mime + null terminator */ + + 1 /* picture type */ + + byte_count_description + 1 /* description + null terminator */ + + length; /* picture length */ + + void *data; + size_t data_size; + int ret = frame->NewData(total_size, &data, &data_size); + if (ret != NErr_Success) + return ret; + + size_t bytes_copied; + bytewriter_s byte_writer; + bytewriter_init(&byte_writer, data, data_size); + bytewriter_write_u8(&byte_writer, 0); /* mark as Latin-1 */ + if (mime) + { + NXStringGetBytes(&bytes_copied, mime, bytewriter_pointer(&byte_writer), bytewriter_size(&byte_writer), nx_charset_latin1, 0); + bytewriter_advance(&byte_writer, bytes_copied); + } + bytewriter_write_u8(&byte_writer, 0); /* MIME null terminator */ + bytewriter_write_u8(&byte_writer, picture_type); + if (description) + { + NXStringGetBytes(&bytes_copied, description, bytewriter_pointer(&byte_writer), bytewriter_size(&byte_writer), nx_charset_latin1, 0); + bytewriter_advance(&byte_writer, bytes_copied); + } + bytewriter_write_u8(&byte_writer, 0); /* description null terminator */ + bytewriter_write_n(&byte_writer, picture_data, length); + return NErr_Success; + } + } + return NErr_Empty; +} diff --git a/Src/replicant/nsid3v2/frame_comments.cpp b/Src/replicant/nsid3v2/frame_comments.cpp new file mode 100644 index 00000000..d6e5b331 --- /dev/null +++ b/Src/replicant/nsid3v2/frame_comments.cpp @@ -0,0 +1,185 @@ +#include "nsid3v2.h" +#include "nsid3v2/header.h" +#include "nsid3v2/tag.h" +#include "nsid3v2/frame_utils.h" +#include "nu/AutoWide.h" +#include "nx/nxstring.h" +#include "nu/ByteWriter.h" + +struct ParsedComments +{ + char language[3]; + ParsedString description; + ParsedString value; +}; + +static int ParseComments(const void *data, size_t data_len, ParsedComments &parsed) +{ + int ret; + if (data_len < 5) + return NErr_Insufficient; + + bytereader_value_t byte_reader; + bytereader_init(&byte_reader, data, data_len); + + // Get encoding + uint8_t encoding = bytereader_read_u8(&byte_reader); + // Get language + for (int i = 0; i < 3; i++) + parsed.language[i] = bytereader_read_u8(&byte_reader); + + // Get description + ret = ParseNullTerminatedString(&byte_reader, encoding, parsed.description); + if (ret != NErr_Success) + return ret; + // Get actual text value + ret = ParseFrameTerminatedString(&byte_reader, encoding, parsed.value); + + return ret; +} + +int NSID3v2_Tag_Comments_Find(const nsid3v2_tag_t t, const char *description, nsid3v2_frame_t *out_frame, int text_flags) +{ + const ID3v2::Tag *tag = (const ID3v2::Tag *)t; + if (!tag) + return NErr_Empty; + const ID3v2::Frame *frame = tag->FindFirstFrame(NSID3V2_FRAME_COMMENTS); + while (frame) + { + const void *data; + size_t data_len; + ParsedComments parsed; + if (frame->GetData(&data, &data_len) == NErr_Success && data_len > 0 && ParseComments(data, data_len, parsed) == NErr_Success && (!description || DescriptionMatches(parsed.description, description, text_flags))) + { + *out_frame = (nsid3v2_frame_t)frame; + return NErr_Success; + } + frame = tag->FindNextFrame(frame); + } + + return NErr_Empty; +} + +int NSID3v2_Tag_Comments_Get(const nsid3v2_tag_t t, const char *description, char language[3], nx_string_t *value, int text_flags) +{ + const ID3v2::Tag *tag = (const ID3v2::Tag *)t; + if (!tag) + return NErr_Empty; + const ID3v2::Frame *frame = tag->FindFirstFrame(NSID3V2_FRAME_COMMENTS); + while (frame) + { + const void *data; + size_t data_len; + ParsedComments parsed; + if (frame->GetData(&data, &data_len) == NErr_Success && data_len > 0 && ParseComments(data, data_len, parsed) == NErr_Success && (!description || DescriptionMatches(parsed.description, description, text_flags))) + { + if (language) + memcpy(language, parsed.language, 3); + return NXStringCreateFromParsedString(value, parsed.value, text_flags); + } + + frame = tag->FindNextFrame(frame); + } + + return NErr_Empty; +} + +int NSID3v2_Frame_Comments_Get(const nsid3v2_frame_t f, nx_string_t *description, char language[3], nx_string_t *value, int text_flags) +{ + const ID3v2::Frame *frame = (const ID3v2::Frame *)f; + if (frame) + { + const void *data; + size_t data_len; + ParsedComments parsed; + if (frame->GetData(&data, &data_len) == NErr_Success && data_len > 0 && ParseComments(data, data_len, parsed) == NErr_Success) + { + if (language) + memcpy(language, parsed.language, 3); + + int ret = NXStringCreateFromParsedString(value, parsed.value, text_flags); + if (ret != NErr_Success) + return ret; + + if (description) + return NXStringCreateFromParsedString(description, parsed.description, text_flags); + else + return NErr_Success; + } + + } + return NErr_Error; +} +/* ---------------- Setters ---------------- */ +int NSID3v2_Frame_Comments_Set(nsid3v2_frame_t f, const char *description, const char language[3], nx_string_t value, int text_flags) +{ + ID3v2::Frame *frame = (ID3v2::Frame *)f; + if (frame) + { + /* benski> for now, we're going to store UTF-16LE always. in the future, we'll add functions to NXString to determine a 'best' encoding */ + size_t description_length=description?strlen(description):0; + + size_t byte_count_value=0; + int ret = NXStringGetBytesSize(&byte_count_value, value, nx_charset_utf16le, 0); + if (ret != NErr_DirectPointer && ret != NErr_Success) + return ret; + + /* TODO: overflow check */ + size_t total_size = 1 /* encoding */ + 3 /* language */ + 2 /* BOM for description */ + description_length*2 + 2 /* null separator */ + 2 /* BOM for value */ + byte_count_value; + + void *data; + size_t data_len; + ret = frame->NewData(total_size, &data, &data_len); + if (ret != NErr_Success) + return ret; + + size_t bytes_copied; + + bytewriter_s byte_writer; + bytewriter_init(&byte_writer, data, data_len); + bytewriter_write_u8(&byte_writer, 1); /* mark as UTF-16LE */ + if (language) + bytewriter_write_n(&byte_writer, language, 3); + else + bytewriter_write_zero_n(&byte_writer, 3); + bytewriter_write_u16_le(&byte_writer, 0xFEFF); /* BOM for description */ + for (size_t i=0;i<description_length;i++) + bytewriter_write_u16_le(&byte_writer, description[i]); + bytewriter_write_u16_le(&byte_writer, 0); /* NULL separator*/ + bytewriter_write_u16_le(&byte_writer, 0xFEFF); /* BOM for value */ + NXStringGetBytes(&bytes_copied, value, bytewriter_pointer(&byte_writer), bytewriter_size(&byte_writer), nx_charset_utf16le, 0); + return NErr_Success; + } + return NErr_Error; +} + +int NSID3v2_Tag_Comments_Set(nsid3v2_tag_t t, const char *description, const char language[3], nx_string_t value, int text_flags) +{ + ID3v2::Tag *tag = (ID3v2::Tag *)t; + if (!tag) + return NErr_Empty; + + ID3v2::Frame *frame = tag->FindFirstFrame(NSID3V2_FRAME_COMMENTS); + while (frame) + { + const void *data; + size_t data_len; + ParsedComments parsed; + if (frame->GetData(&data, &data_len) == NErr_Success && data_len > 0 && ParseComments(data, data_len, parsed) == NErr_Success && (!description || DescriptionMatches(parsed.description, description, text_flags))) + { + break; + } + + frame = tag->FindNextFrame(frame); + } + + if (!frame) + { + frame = tag->NewFrame(NSID3V2_FRAME_COMMENTS, 0); + if (!frame) + return NErr_OutOfMemory; + tag->AddFrame(frame); + } + + return NSID3v2_Frame_Comments_Set((nsid3v2_frame_t)frame, description, language, value, text_flags); +} diff --git a/Src/replicant/nsid3v2/frame_id.cpp b/Src/replicant/nsid3v2/frame_id.cpp new file mode 100644 index 00000000..807d8cd6 --- /dev/null +++ b/Src/replicant/nsid3v2/frame_id.cpp @@ -0,0 +1,167 @@ +#include "nsid3v2.h" +#include "nsid3v2/header.h" +#include "nsid3v2/tag.h" +#include "nsid3v2/frame_utils.h" +#include "nu/ByteReader.h" +#include "nu/ByteWriter.h" +#include "nx/nxstring.h" + +struct ParsedID +{ + ParsedString owner; + const void *identifier_data; + size_t identifier_byte_length; +}; + +static int ParseID(const void *data, size_t data_len, ParsedID &parsed) +{ + int ret; + if (data_len < 1) + return NErr_Insufficient; + + bytereader_value_t byte_reader; + bytereader_init(&byte_reader, data, data_len); + + /* owner is always latin-1 */ + ret = ParseNullTerminatedString(&byte_reader, 0, parsed.owner); + if (ret != NErr_Success) + return ret; + parsed.identifier_data = bytereader_pointer(&byte_reader); + parsed.identifier_byte_length = bytereader_size(&byte_reader); + return NErr_Success; +} + +int NSID3v2_Tag_ID_Find(const nsid3v2_tag_t t, const char *owner, nsid3v2_frame_t *out_frame, int text_flags) +{ + const ID3v2::Tag *tag = (const ID3v2::Tag *)t; + if (!tag) + return NErr_Empty; + const ID3v2::Frame *frame = tag->FindFirstFrame(NSID3V2_FRAME_ID); + while (frame) + { + const void *data; + size_t data_len; + ParsedID parsed; + if (frame->GetData(&data, &data_len) == NErr_Success && data_len > 0 && ParseID(data, data_len, parsed) == NErr_Success && (!owner || DescriptionMatches(parsed.owner, owner, text_flags))) + { + *out_frame = (nsid3v2_frame_t)frame; + return NErr_Success; + } + frame = tag->FindNextFrame(frame); + } + + return NErr_Empty; +} + +int NSID3v2_Frame_ID_Get(nsid3v2_frame_t f, nx_string_t *owner, const void **id_data, size_t *length, int text_flags) +{ + const ID3v2::Frame *frame = (const ID3v2::Frame *)f; + if (frame) + { + const void *data; + size_t data_len; + ParsedID parsed; + if (frame->GetData(&data, &data_len) == NErr_Success && data_len > 0 && ParseID(data, data_len, parsed) == NErr_Success) + { + if (owner) + { + int ret = NXStringCreateFromParsedString(owner, parsed.owner, text_flags); + if (ret != NErr_Success) + return ret; + } + + *id_data = parsed.identifier_data; + *length = parsed.identifier_byte_length; + + return NErr_Success; + } + + } + return NErr_Empty; +} + +int NSID3v2_Tag_ID_Get(const nsid3v2_tag_t t, const char *owner, const void **id_data, size_t *length, int text_flags) +{ + const ID3v2::Tag *tag = (const ID3v2::Tag *)t; + if (!tag) + return NErr_Empty; + + ID3v2::Frame *frame = tag->FindFirstFrame(NSID3V2_FRAME_ID); + while (frame) + { + const void *data; + size_t data_len; + ParsedID parsed; + if (frame->GetData(&data, &data_len) == NErr_Success && data_len > 0 && ParseID(data, data_len, parsed) == NErr_Success && (!owner || DescriptionMatches(parsed.owner, owner, text_flags))) + { + *id_data = parsed.identifier_data; + *length = parsed.identifier_byte_length; + + return NErr_Success; + } + + frame = tag->FindNextFrame(frame); + } + return NErr_Empty; +} + + + +/* ---------------- Setters ---------------- */ +int NSID3v2_Frame_ID_Set(nsid3v2_frame_t f, const char *owner, const void *id_data, size_t length, int text_flags) +{ + ID3v2::Frame *frame = (ID3v2::Frame *)f; + if (frame) + { + size_t owner_length=owner?strlen(owner):0; + + /* TODO: overflow check */ + size_t total_size = owner_length + 1 + length; + + void *data; + size_t data_len; + int ret = frame->NewData(total_size, &data, &data_len); + if (ret != NErr_Success) + return ret; + + bytewriter_s byte_writer; + bytewriter_init(&byte_writer, data, data_len); + bytewriter_write_n(&byte_writer, owner, owner_length); + bytewriter_write_u8(&byte_writer, 0); // write null terminator separately, in case owner is NULL + bytewriter_write_n(&byte_writer, id_data, length); + + return NErr_Success; + } + return NErr_Empty; +} + +int NSID3v2_Tag_ID_Set(nsid3v2_tag_t t, const char *owner, const void *id_data, size_t length, int text_flags) +{ + ID3v2::Tag *tag = (ID3v2::Tag *)t; + if (!tag) + return NErr_Empty; + + ID3v2::Frame *frame = tag->FindFirstFrame(NSID3V2_FRAME_ID); + while (frame) + { + const void *data; + size_t data_len; + ParsedID parsed; + if (frame->GetData(&data, &data_len) == NErr_Success && data_len > 0 && ParseID(data, data_len, parsed) == NErr_Success && (!owner || DescriptionMatches(parsed.owner, owner, text_flags))) + { + break; + } + + frame = tag->FindNextFrame(frame); + } + + if (!frame) + { + frame = tag->NewFrame(NSID3V2_FRAME_ID, 0); + if (!frame) + return NErr_OutOfMemory; + tag->AddFrame(frame); + } + + return NSID3v2_Frame_ID_Set((nsid3v2_frame_t)frame, owner, id_data, length, text_flags); +} diff --git a/Src/replicant/nsid3v2/frame_object.cpp b/Src/replicant/nsid3v2/frame_object.cpp new file mode 100644 index 00000000..7a6500f3 --- /dev/null +++ b/Src/replicant/nsid3v2/frame_object.cpp @@ -0,0 +1,86 @@ +#include "nsid3v2.h" +#include "nsid3v2/header.h" +#include "nsid3v2/tag.h" +#include "nsid3v2/frame_utils.h" +#include "nu/ByteReader.h" +#include "nx/nxstring.h" +#if defined(_WIN32) && !defined(strcasecmp) +#define strcasecmp _stricmp +#else +#include <strings.h> +#endif + +struct ParsedObject +{ + ParsedString mime; + ParsedString filename; + ParsedString description; + const void *object_data; + size_t object_byte_length; +}; + +static int ParseObject(const void *data, size_t data_len, ParsedObject &parsed) +{ + int ret; + if (data_len == 0) + return NErr_Insufficient; + + bytereader_value_t byte_reader; + bytereader_init(&byte_reader, data, data_len); + + /* encoding */ + uint8_t encoding = bytereader_read_u8(&byte_reader); + + /* read mime type (Always latin-1) */ + ret = ParseNullTerminatedString(&byte_reader, 0, parsed.mime); + if (ret != NErr_Success) + return ret; + + /* read filename */ + ret = ParseNullTerminatedString(&byte_reader, encoding, parsed.filename); + if (ret != NErr_Success) + return ret; + + /* read content description */ + ret = ParseNullTerminatedString(&byte_reader, encoding, parsed.description); + if (ret != NErr_Success) + return ret; + + + parsed.object_data = bytereader_pointer(&byte_reader); + parsed.object_byte_length = bytereader_size(&byte_reader); + + return NErr_Success; +} + +int NSID3v2_Frame_Object_Get(const nsid3v2_frame_t f, nx_string_t *mime, nx_string_t *filename, nx_string_t *description, const void **out_data, size_t *length, int text_flags) +{ + const ID3v2::Frame *frame = (const ID3v2::Frame *)f; + if (frame) + { + const void *data; + size_t data_len; + ParsedObject parsed; + if (frame->GetData(&data, &data_len) == NErr_Success && data_len > 0 && ParseObject(data, data_len, parsed) == NErr_Success) + { + int ret = NXStringCreateFromParsedString(mime, parsed.mime, text_flags); + if (ret != NErr_Success) + return ret; + + ret = NXStringCreateFromParsedString(filename, parsed.filename, text_flags); + if (ret != NErr_Success) + return ret; + + ret = NXStringCreateFromParsedString(description, parsed.description, text_flags); + if (ret != NErr_Success) + return ret; + + *out_data = parsed.object_data; + *length = parsed.object_byte_length; + + return NErr_Success; + } + + } + return NErr_Empty; +} diff --git a/Src/replicant/nsid3v2/frame_popm.cpp b/Src/replicant/nsid3v2/frame_popm.cpp new file mode 100644 index 00000000..e2a4f6a5 --- /dev/null +++ b/Src/replicant/nsid3v2/frame_popm.cpp @@ -0,0 +1,95 @@ +#include "nsid3v2.h" +#include "nsid3v2/header.h" +#include "nsid3v2/tag.h" +#include "nsid3v2/frame_utils.h" +#include "nu/ByteReader.h" +#include "nx/nxstring.h" +#if defined(_WIN32) && !defined(strcasecmp) +#define strcasecmp _stricmp +#else +#include <strings.h> +#endif + +struct ParsedPopularimeter +{ + ParsedString email; + uint8_t rating; + uint64_t playcount; +}; + +static int ParsePopularimeter(const void *data, size_t data_len, ParsedPopularimeter &parsed) +{ + int ret; + if (data_len < 6) + return NErr_Insufficient; + + bytereader_value_t byte_reader; + bytereader_init(&byte_reader, data, data_len); + + /* read email (Always latin-1) */ + ret = ParseNullTerminatedString(&byte_reader, 0, parsed.email); + if (ret != NErr_Success) + return ret; + + if (bytereader_size(&byte_reader) == 0) + return NErr_Insufficient; + + parsed.rating = bytereader_read_u8(&byte_reader); + + parsed.playcount=0; + while (bytereader_size(&byte_reader)) + { + + parsed.playcount <<= 8; + parsed.playcount |= bytereader_read_u8(&byte_reader); + } + return NErr_Success; +} + +int NSID3v2_Tag_Popularimeter_GetRatingPlaycount(const nsid3v2_tag_t t, const char *email, uint8_t *rating, uint64_t *playcount) +{ + const ID3v2::Tag *tag = (const ID3v2::Tag *)t; + if (!tag) + return NErr_Empty; + const ID3v2::Frame *frame = tag->FindFirstFrame(NSID3V2_FRAME_POPULARIMETER); + while (frame) + { + const void *data; + size_t data_len; + ParsedPopularimeter parsed; + if (frame->GetData(&data, &data_len) == NErr_Success && data_len > 0 && ParsePopularimeter(data, data_len, parsed) == NErr_Success) + { + if (!strcasecmp(email, (const char *)parsed.email.data)) + { + *rating = parsed.rating; + *playcount = parsed.playcount; + return NErr_Success; + } + + } + frame = tag->FindNextFrame(frame); + } + + return NErr_Empty; +} + +int NSID3v2_Frame_Popularity_Get(nsid3v2_frame_t f, nx_string_t *email, uint8_t *rating, uint64_t *playcount, int text_flags) +{ + const ID3v2::Frame *frame = (const ID3v2::Frame *)f; + if (frame) + { + const void *data; + size_t data_len; + ParsedPopularimeter parsed; + if (frame->GetData(&data, &data_len) == NErr_Success && data_len > 0 && ParsePopularimeter(data, data_len, parsed) == NErr_Success) + { + int ret = NXStringCreateFromParsedString(email, parsed.email, text_flags); + if (ret != NErr_Success) + return ret; + *rating = parsed.rating; + *playcount = parsed.playcount; + return NErr_Success; + } + } + return NErr_Empty; +} diff --git a/Src/replicant/nsid3v2/frame_private.cpp b/Src/replicant/nsid3v2/frame_private.cpp new file mode 100644 index 00000000..f70b8fd3 --- /dev/null +++ b/Src/replicant/nsid3v2/frame_private.cpp @@ -0,0 +1,61 @@ +#include "nsid3v2.h" +#include "nsid3v2/header.h" +#include "nsid3v2/tag.h" +#include "nsid3v2/frame_utils.h" +#include "nu/ByteReader.h" +#include "nx/nxstring.h" +#if defined(_WIN32) && !defined(strcasecmp) +#define strcasecmp _stricmp +#else +#include <strings.h> +#endif + + +struct ParsedPrivate +{ + ParsedString owner; + const void *private_data; + size_t private_byte_length; +}; + +static int ParsePrivate(const void *data, size_t data_len, ParsedPrivate &parsed) +{ + if (data_len == 0) + return NErr_Insufficient; + + bytereader_value_t byte_reader; + bytereader_init(&byte_reader, data, data_len); + + int ret = ParseNullTerminatedString(&byte_reader, 0, parsed.owner); + if (ret != NErr_Success) + return ret; + + parsed.private_data = bytereader_pointer(&byte_reader); + parsed.private_byte_length = bytereader_size(&byte_reader); + + return NErr_Success; +} + +int NSID3v2_Frame_Private_Get(const nsid3v2_frame_t f, nx_string_t *description, const void **out_data, size_t *length) +{ + const ID3v2::Frame *frame = (const ID3v2::Frame *)f; + if (frame) + { + const void *data; + size_t data_len; + ParsedPrivate parsed; + if (frame->GetData(&data, &data_len) == NErr_Success && data_len > 0 && ParsePrivate(data, data_len, parsed) == NErr_Success) + { + int ret = NXStringCreateFromParsedString(description, parsed.owner, 0); + if (ret != NErr_Success) + return ret; + + *out_data = parsed.private_data; + *length = parsed.private_byte_length; + + return NErr_Success; + } + + } + return NErr_Empty; +} diff --git a/Src/replicant/nsid3v2/frame_text.cpp b/Src/replicant/nsid3v2/frame_text.cpp new file mode 100644 index 00000000..4e0260be --- /dev/null +++ b/Src/replicant/nsid3v2/frame_text.cpp @@ -0,0 +1,95 @@ +#include "nsid3v2.h" +#include "nsid3v2/header.h" +#include "nsid3v2/tag.h" +#include "nu/ByteReader.h" +#include "nx/nxstring.h" +#include "nu/ByteWriter.h" +#include "nsid3v2/frame_utils.h" + +static int ParseText(const void *data, size_t data_len, ParsedString &parsed) +{ + if (data_len == 0) + return NErr_Insufficient; + + bytereader_value_t byte_reader; + bytereader_init(&byte_reader, data, data_len); + + return ParseFrameTerminatedString(&byte_reader, bytereader_read_u8(&byte_reader), parsed); +} + +int NSID3v2_Frame_Text_Get(const nsid3v2_frame_t f, nx_string_t *value, int text_flags) +{ + const ID3v2::Frame *frame = (const ID3v2::Frame *)f; + if (frame) + { + const void *data; + size_t data_len; + ParsedString parsed; + if (frame->GetData(&data, &data_len) == NErr_Success && data_len > 0 && ParseText(data, data_len, parsed) == NErr_Success) + { + return NXStringCreateFromParsedString(value, parsed, text_flags); + } + } + + return NErr_Empty; +} + +int NSID3v2_Tag_Text_Get(const nsid3v2_tag_t t, int frame_enum, nx_string_t *value, int text_flags) +{ + const ID3v2::Tag *tag = (const ID3v2::Tag *)t; + if (!tag) + return NErr_Empty; + + const ID3v2::Frame *frame = tag->FindFirstFrame(frame_enum); + return NSID3v2_Frame_Text_Get((const nsid3v2_frame_t)frame, value, text_flags); +} + + +/* ---------------- Setters ---------------- */ +int NSID3v2_Frame_Text_Set(nsid3v2_frame_t f, nx_string_t value, int text_flags) +{ + ID3v2::Frame *frame = (ID3v2::Frame *)f; + if (frame) + { + /* benski> for now, we're going to store UTF-16LE always. in the future, we'll add functions to NXString to determine a 'best' encoding */ + size_t byte_count=0; + int ret = NXStringGetBytesSize(&byte_count, value, nx_charset_utf16le, 0); + if (ret != NErr_DirectPointer && ret != NErr_Success) + return ret; + + void *data; + size_t data_len; + byte_count+=3; // need one byte for encoding type, two bytes for BOM + ret = frame->NewData(byte_count, &data, &data_len); + if (ret != NErr_Success) + return ret; + + bytewriter_s byte_writer; + bytewriter_init(&byte_writer, data, data_len); + bytewriter_write_u8(&byte_writer, 1); /* mark as UTF-16LE */ + bytewriter_write_u16_le(&byte_writer, 0xFEFF); /* BOM */ + + size_t bytes_copied; + return NXStringGetBytes(&bytes_copied, value, bytewriter_pointer(&byte_writer), bytewriter_size(&byte_writer), nx_charset_utf16le, 0); + } + + return NErr_Empty; +} + +int NSID3v2_Tag_Text_Set(nsid3v2_tag_t t, int frame_enum, nx_string_t value, int text_flags) +{ + ID3v2::Tag *tag = (ID3v2::Tag *)t; + if (!tag) + return NErr_Empty; + + ID3v2::Frame *frame = tag->FindFirstFrame(frame_enum); + if (!frame) + { + frame = tag->NewFrame(frame_enum, 0); + if (!frame) + return NErr_OutOfMemory; + tag->AddFrame(frame); + } + + return NSID3v2_Frame_Text_Set((nsid3v2_frame_t)frame, value, text_flags); +} diff --git a/Src/replicant/nsid3v2/frame_url.cpp b/Src/replicant/nsid3v2/frame_url.cpp new file mode 100644 index 00000000..307f6e44 --- /dev/null +++ b/Src/replicant/nsid3v2/frame_url.cpp @@ -0,0 +1,42 @@ +#include "nsid3v2.h" +#include "nsid3v2/header.h" +#include "nsid3v2/tag.h" +#include "nu/ByteReader.h" +#include "nx/nxstring.h" +#include "nsid3v2/frame_utils.h" + +static int ParseText(const void *data, size_t data_len, ParsedString &parsed) +{ + bytereader_value_t byte_reader; + bytereader_init(&byte_reader, data, data_len); + + return ParseFrameTerminatedString(&byte_reader, 0, parsed); +} + +int NSID3v2_Frame_URL_Get(const nsid3v2_frame_t f, nx_string_t *value, int text_flags) +{ + const ID3v2::Frame *frame = (const ID3v2::Frame *)f; + if (frame) + { + const void *data; + size_t data_len; + ParsedString parsed; + if (frame->GetData(&data, &data_len) == NErr_Success && ParseText(data, data_len, parsed) == NErr_Success) + { + return NXStringCreateFromParsedString(value, parsed, text_flags); + } + } + + return NErr_Empty; +} + +int NSID3v2_Tag_URL_Get(const nsid3v2_tag_t t, int frame_enum, nx_string_t *value, int text_flags) +{ + const ID3v2::Tag *tag = (const ID3v2::Tag *)t; + if (!tag) + return NErr_Empty; + const ID3v2::Frame *frame = tag->FindFirstFrame(frame_enum); + return NSID3v2_Frame_URL_Get((const nsid3v2_frame_t)frame, value, text_flags); +} + + diff --git a/Src/replicant/nsid3v2/frame_usertext.cpp b/Src/replicant/nsid3v2/frame_usertext.cpp new file mode 100644 index 00000000..07d06039 --- /dev/null +++ b/Src/replicant/nsid3v2/frame_usertext.cpp @@ -0,0 +1,166 @@ +#include "nsid3v2.h" +#include "nsid3v2/header.h" +#include "nsid3v2/tag.h" +#include "nsid3v2/frame_utils.h" +#include "nu/ByteReader.h" +#include "nu/ByteWriter.h" +#include "nx/nxstring.h" + +struct ParsedUserText +{ + ParsedString description; + ParsedString value; +}; + +static int ParseUserText(const void *data, size_t data_len, ParsedUserText &parsed) +{ + int ret; + if (data_len == 0) + return NErr_Insufficient; + + bytereader_value_t byte_reader; + bytereader_init(&byte_reader, data, data_len); + + uint8_t encoding = bytereader_read_u8(&byte_reader); + + ret = ParseNullTerminatedString(&byte_reader, encoding, parsed.description); + if (ret != NErr_Success) + return ret; + + return ParseFrameTerminatedString(&byte_reader, encoding, parsed.value); +} + +int NSID3v2_Tag_TXXX_Find(const nsid3v2_tag_t t, const char *description, nsid3v2_frame_t *out_frame, int text_flags) +{ + const ID3v2::Tag *tag = (const ID3v2::Tag *)t; + if (!tag) + return NErr_Empty; + const ID3v2::Frame *frame = tag->FindFirstFrame(NSID3V2_FRAME_USER_TEXT); + while (frame) + { + const void *data; + size_t data_len; + ParsedUserText parsed; + if (frame->GetData(&data, &data_len) == NErr_Success && data_len > 0 && ParseUserText(data, data_len, parsed) == NErr_Success && DescriptionMatches(parsed.description, description, text_flags)) + { + *out_frame = (nsid3v2_frame_t)frame; + return NErr_Success; + } + frame = tag->FindNextFrame(frame); + } + + return NErr_Empty; +} + +int NSID3v2_Tag_TXXX_Get(const nsid3v2_tag_t t, const char *description, nx_string_t *value, int text_flags) +{ + const ID3v2::Tag *tag = (const ID3v2::Tag *)t; + if (!tag) + return NErr_Empty; + const ID3v2::Frame *frame = tag->FindFirstFrame(NSID3V2_FRAME_USER_TEXT); + while (frame) + { + const void *data; + size_t data_len; + ParsedUserText parsed; + if (frame->GetData(&data, &data_len) == NErr_Success && data_len > 0 && ParseUserText(data, data_len, parsed) == NErr_Success && DescriptionMatches(parsed.description, description, text_flags)) + { + return NXStringCreateFromParsedString(value, parsed.value, text_flags); + } + frame = tag->FindNextFrame(frame); + } + + return NErr_Empty; +} + +int NSID3v2_Frame_UserText_Get(const nsid3v2_frame_t f, nx_string_t *description, nx_string_t *value, int text_flags) +{ + const ID3v2::Frame *frame = (const ID3v2::Frame *)f; + if (frame) + { + const void *data; + size_t data_len; + ParsedUserText parsed; + if (frame->GetData(&data, &data_len) == NErr_Success && data_len > 0 && ParseUserText(data, data_len, parsed) == NErr_Success) + { + int ret = NXStringCreateFromParsedString(value, parsed.value, text_flags); + if (ret != NErr_Success) + return ret; + + if (description) + return NXStringCreateFromParsedString(description, parsed.description, text_flags); + else + return NErr_Success; + } + + } + return NErr_Error; +} +/* ---------------- Setters ---------------- */ +int NSID3v2_Frame_UserText_Set(nsid3v2_frame_t f, const char *description, nx_string_t value, int text_flags) +{ + ID3v2::Frame *frame = (ID3v2::Frame *)f; + if (frame) + { + /* benski> for now, we're going to store UTF-16LE always. in the future, we'll add functions to NXString to determine a 'best' encoding */ + size_t description_length=strlen(description); + + size_t byte_count_value=0; + int ret = NXStringGetBytesSize(&byte_count_value, value, nx_charset_utf16le, 0); + if (ret != NErr_DirectPointer && ret != NErr_Success) + return ret; + + /* TODO: overflow check */ + size_t total_size = 1 /* encoding */ + 2 /* BOM for description */ + description_length*2 + 2 /* null separator */ + 2 /* BOM for value */ + byte_count_value; + + void *data; + size_t data_len; + ret = frame->NewData(total_size, &data, &data_len); + if (ret != NErr_Success) + return ret; + + size_t bytes_copied; + + bytewriter_s byte_writer; + bytewriter_init(&byte_writer, data, data_len); + bytewriter_write_u8(&byte_writer, 1); /* mark as UTF-16LE */ + bytewriter_write_u16_le(&byte_writer, 0xFEFF); /* BOM for description */ + for (size_t i=0;i<description_length;i++) + bytewriter_write_u16_le(&byte_writer, description[i]); + bytewriter_write_u16_le(&byte_writer, 0); /* NULL separator*/ + bytewriter_write_u16_le(&byte_writer, 0xFEFF); /* BOM for value */ + NXStringGetBytes(&bytes_copied, value, bytewriter_pointer(&byte_writer), bytewriter_size(&byte_writer), nx_charset_utf16le, 0); + return NErr_Success; + } + return NErr_Error; +} + +int NSID3v2_Tag_TXXX_Set(nsid3v2_tag_t t, const char *description, nx_string_t value, int text_flags) +{ + ID3v2::Tag *tag = (ID3v2::Tag *)t; + if (!tag) + return NErr_Empty; + + ID3v2::Frame *frame = tag->FindFirstFrame(NSID3V2_FRAME_USER_TEXT); + while (frame) + { + const void *data; + size_t data_len; + ParsedUserText parsed; + if (frame->GetData(&data, &data_len) == NErr_Success && data_len > 0 && ParseUserText(data, data_len, parsed) == NErr_Success && DescriptionMatches(parsed.description, description, text_flags)) + { + break; + } + frame = tag->FindNextFrame(frame); + } + + if (!frame) + { + frame = tag->NewFrame(NSID3V2_FRAME_USER_TEXT, 0); + if (!frame) + return NErr_OutOfMemory; + tag->AddFrame(frame); + } + + return NSID3v2_Frame_UserText_Set((nsid3v2_frame_t)frame, description, value, text_flags); +} diff --git a/Src/replicant/nsid3v2/frame_userurl.cpp b/Src/replicant/nsid3v2/frame_userurl.cpp new file mode 100644 index 00000000..ca053012 --- /dev/null +++ b/Src/replicant/nsid3v2/frame_userurl.cpp @@ -0,0 +1,78 @@ +#include "nsid3v2.h" +#include "nsid3v2/header.h" +#include "nsid3v2/tag.h" +#include "nsid3v2/frame_utils.h" +#include "nu/ByteReader.h" +#include "nx/nxstring.h" +#if defined(_WIN32) && !defined(strcasecmp) +#define strcasecmp _stricmp +#else +#include <string.h> +#endif + +struct ParsedUserURL +{ + ParsedString description; + ParsedString value; +}; + +static int ParseUserURL(const void *data, size_t data_len, ParsedUserURL &parsed) +{ + int ret; + if (data_len < 2) + return NErr_Insufficient; + + bytereader_value_t byte_reader; + bytereader_init(&byte_reader, data, data_len); + + uint8_t encoding = bytereader_read_u8(&byte_reader); + + ret = ParseNullTerminatedString(&byte_reader, encoding, parsed.description); + if (ret != NErr_Success) + return ret; + + return ParseFrameTerminatedString(&byte_reader, 0, parsed.value); +} + +int NSID3v2_Tag_WXXX_Get(const nsid3v2_tag_t t, const char *description, nx_string_t *value, int text_flags) +{ + const ID3v2::Tag *tag = (const ID3v2::Tag *)t; + if (!tag) + return NErr_Empty; + const ID3v2::Frame *frame = tag->FindFirstFrame(NSID3V2_FRAME_USER_TEXT); + while (frame) + { + const void *data; + size_t data_len; + ParsedUserURL parsed; + if (frame->GetData(&data, &data_len) == NErr_Success && data_len > 0 && ParseUserURL(data, data_len, parsed) == NErr_Success && DescriptionMatches(parsed.description, description, text_flags)) + { + return NXStringCreateFromParsedString(value, parsed.value, text_flags); + } + + frame = tag->FindNextFrame(frame); + } + + return NErr_Empty; +} + +int NSID3v2_Frame_UserURL_Get(const nsid3v2_frame_t f, nx_string_t *description, nx_string_t *value, int text_flags) +{ + const ID3v2::Frame *frame = (const ID3v2::Frame *)f; + if (frame) + { + const void *data; + size_t data_len; + ParsedUserURL parsed; + if (frame->GetData(&data, &data_len) == NErr_Success && data_len > 0 && ParseUserURL(data, data_len, parsed) == NErr_Success) + { + int ret = NXStringCreateFromParsedString(value, parsed.value, text_flags); + if (ret != NErr_Success) + return ret; + + return NXStringCreateFromParsedString(description, parsed.description, text_flags); + } + + } + return NErr_Error; +} diff --git a/Src/replicant/nsid3v2/frame_utils.cpp b/Src/replicant/nsid3v2/frame_utils.cpp new file mode 100644 index 00000000..6fe88686 --- /dev/null +++ b/Src/replicant/nsid3v2/frame_utils.cpp @@ -0,0 +1,265 @@ +#include "frame_utils.h" +#include "foundation/error.h" +#include "nsid3v2/nsid3v2.h" +#if defined(_WIN32) && !defined(strcasecmp) +#define strcasecmp _stricmp +#else +#include <string.h> +#endif + +int ParseDescription(const char *&str, size_t &data_len, size_t &str_cch) +{ + str_cch=0; + while (data_len && str[str_cch]) + { + data_len--; + str_cch++; + } + if (!data_len) + return NErr_Error; + + data_len--; + return NErr_Success; +} + +int ParseDescription(const wchar_t *&str, size_t &data_len, size_t &str_cch, uint8_t &str_encoding) +{ + str_cch=0; + if (data_len > 2 && str[0] == 0xFFFE) + { + str_encoding=2; + str++; + str-=3; + } + else if (data_len > 2 && str[0] == 0xFEFF) + { + str_encoding=1; + str++; + data_len-=3; + } + else + { + data_len--; + } + + while (data_len > 1 && str[str_cch]) + { + data_len-=2; + str_cch++; + } + + if (!data_len) + return NErr_Error; + + data_len-=2; + return NErr_Success; +} + + +static void ParseBOM(bytereader_t reader, uint8_t *encoding, const uint8_t default_encoding) +{ + if (bytereader_size(reader) >= 2) + { + uint16_t bom = bytereader_show_u16_le(reader); + if (bom == 0xFFFE) + { + bytereader_advance(reader, 2); + *encoding=2; + } + else if (bom == 0xFEFF) + { + bytereader_advance(reader, 2); + *encoding=1; + } + else + { + *encoding=default_encoding; + } + } + else + { + *encoding=default_encoding; + } +} + +int ParseNullTerminatedString(bytereader_t reader, uint8_t encoding, ParsedString &parsed) +{ + switch(encoding) + { + case 0: // ISO-8859-1 + if (bytereader_size(reader) == 0) + return NErr_Insufficient; + + parsed.encoding = 0; + parsed.data = bytereader_pointer(reader); + parsed.byte_length = 0; + while (bytereader_size(reader) && bytereader_read_u8(reader)) + parsed.byte_length++; + + return NErr_Success; + case 1: // UTF-16 + if (bytereader_size(reader) < 2) + return NErr_Insufficient; + + parsed.byte_length = 0; + ParseBOM(reader, &parsed.encoding, 1); + parsed.data = bytereader_pointer(reader); + while (bytereader_size(reader) && bytereader_read_u16_le(reader)) + parsed.byte_length+=2; + + return NErr_Success; + case 2: // UTF-16BE + if (bytereader_size(reader) < 2) + return NErr_Insufficient; + + parsed.byte_length = 0; + ParseBOM(reader, &parsed.encoding, 2); + parsed.data = bytereader_pointer(reader); + while (bytereader_size(reader) && bytereader_read_u16_le(reader)) + parsed.byte_length+=2; + + return NErr_Success; + case 3: // UTF-8 + if (bytereader_size(reader) == 0) + return NErr_Insufficient; + + parsed.encoding = 3; + parsed.data = bytereader_pointer(reader); + parsed.byte_length = 0; + + size_t start = bytereader_size(reader); +#if 0 // TODO + /* check for UTF-8 BOM and skip it */ + if (bytereader_size(reader) > 3 && bytereader_read_u8(reader) == 0xEF && bytereader_read_u8(reader) == 0xBB && bytereader_read_u8(reader) == 0xBF) + { + parsed.data = bytereader_pointer(reader); + parsed.byte_length = bytereader_size(reader); + } + else + { + /* no BOM but skip however far we read into the string */ + size_t offset = start - bytereader_size(reader); + parsed.data = (const uint8_t *)parsed.data + offset; + parsed.byte_length -= offset; + } +#endif + /* finish it up */ + while (bytereader_size(reader) && bytereader_read_u8(reader)) + parsed.byte_length++; + + return NErr_Success; + } + return NErr_Unknown; +} + +int ParseFrameTerminatedString(bytereader_t reader, uint8_t encoding, ParsedString &parsed) +{ + switch(encoding) + { + case 0: // ISO-8859-1 + parsed.encoding = 0; + parsed.data = bytereader_pointer(reader); + parsed.byte_length = bytereader_size(reader); + return NErr_Success; + case 1: // UTF-16 + if ((bytereader_size(reader) & 1) == 1) + return NErr_Error; + ParseBOM(reader, &parsed.encoding, 1); + parsed.data = bytereader_pointer(reader); + parsed.byte_length = bytereader_size(reader); + return NErr_Success; + case 2: // UTF-16BE + if ((bytereader_size(reader) & 1) == 1) + return NErr_Error; + ParseBOM(reader, &parsed.encoding, 2); + parsed.data = bytereader_pointer(reader); + parsed.byte_length = bytereader_size(reader); + return NErr_Success; + case 3: // UTF-8 + parsed.encoding = 3; + parsed.data = bytereader_pointer(reader); + parsed.byte_length = bytereader_size(reader); + if (bytereader_size(reader) > 3 && bytereader_read_u8(reader) == 0xEF && bytereader_read_u8(reader) == 0xBB && bytereader_read_u8(reader) == 0xBF) + { + parsed.data = bytereader_pointer(reader); + parsed.byte_length = bytereader_size(reader); + } + return NErr_Success; + } + return NErr_Error; +} + +int NXStringCreateFromParsedString(nx_string_t *value, ParsedString &parsed, int text_flags) +{ + switch(parsed.encoding) + { + case 0: // ISO-8859-1 + if (parsed.byte_length == 0) + return NXStringCreateEmpty(value); + if (text_flags & NSID3V2_TEXT_SYSTEM) + return NXStringCreateWithBytes(value, parsed.data, parsed.byte_length, nx_charset_system); + else + return NXStringCreateWithBytes(value, parsed.data, parsed.byte_length, nx_charset_latin1); + case 1: // UTF-16 + if (parsed.byte_length < 2) + return NXStringCreateEmpty(value); + return NXStringCreateWithBytes(value, parsed.data, parsed.byte_length, nx_charset_utf16le); + case 2: // UTF-16BE + if (parsed.byte_length < 2) + return NXStringCreateEmpty(value); + return NXStringCreateWithBytes(value, parsed.data, parsed.byte_length, nx_charset_utf16be); + case 3: // UTF-8 + if (parsed.byte_length == 0) + return NXStringCreateEmpty(value); + return NXStringCreateWithBytes(value, parsed.data, parsed.byte_length, nx_charset_utf8); + default: + return NErr_Unknown; + } + +} + +bool DescriptionMatches(const ParsedString &parsed, const char *description, int text_flags) +{ + // see if our description matches + switch(parsed.encoding) + { + case 0: // ISO-8859-1 + return !strcasecmp(description, (const char *)parsed.data); + case 1: + { + bytereader_value_t utf16; + bytereader_init(&utf16, parsed.data, parsed.byte_length); + + while (*description && bytereader_size(&utf16)) + { + if ((*description++ & ~0x20) != (bytereader_read_u16_le(&utf16) & ~0x20)) + return false; + } + + if (*description == 0 && bytereader_size(&utf16) == 0) + return true; + else + return false; + } + + case 2: + { + bytereader_value_t utf16; + bytereader_init(&utf16, parsed.data, parsed.byte_length); + + while (*description && bytereader_size(&utf16)) + { + if ((*description++ & ~0x20) != (bytereader_read_u16_be(&utf16) & ~0x20)) + return false; + } + if (*description == 0 && bytereader_size(&utf16) == 0) + return true; + else + return false; + } + case 3: + return !strcasecmp(description, (const char *)parsed.data); + } + + return false; +} diff --git a/Src/replicant/nsid3v2/frame_utils.h b/Src/replicant/nsid3v2/frame_utils.h new file mode 100644 index 00000000..1a24d5b7 --- /dev/null +++ b/Src/replicant/nsid3v2/frame_utils.h @@ -0,0 +1,21 @@ +#pragma once +#include "foundation/types.h" +#include "nu/ByteReader.h" +#include "nx/nxstring.h" + + +/* updates str, data_len and str_cch */ +int ParseDescription(const char *&str, size_t &data_len, size_t &str_cch); +int ParseDescription(const wchar_t *&str, size_t &data_len, size_t &str_cch, uint8_t &str_encoding); + +struct ParsedString +{ + uint8_t encoding; // 0 - iso-8859-1, 1 - UTF16LE, 2 - UTF16BE, 3 - UTF8 + const void *data; + size_t byte_length; +}; + +int ParseNullTerminatedString(bytereader_t reader, uint8_t encoding, ParsedString &parsed); +int ParseFrameTerminatedString(bytereader_t reader, uint8_t encoding, ParsedString &parsed); +int NXStringCreateFromParsedString(nx_string_t *value, ParsedString &parsed, int text_flags); +bool DescriptionMatches(const ParsedString &parsed, const char *description, int text_flags); diff --git a/Src/replicant/nsid3v2/frameheader.cpp b/Src/replicant/nsid3v2/frameheader.cpp new file mode 100644 index 00000000..3ffa63f9 --- /dev/null +++ b/Src/replicant/nsid3v2/frameheader.cpp @@ -0,0 +1,403 @@ +#include "frameheader.h" +#include "util.h" +#include "values.h" +#include "nu/ByteReader.h" +#include "nu/ByteWriter.h" +#include <string.h> +#include "foundation/error.h" +/* === ID3v2 common === */ +ID3v2::FrameHeader::FrameHeader(const ID3v2::Header &_header) : tagHeader(_header) +{ +} + +static bool CharOK(int8_t c) +{ + if (c >= '0' && c <= '9') + return true; + + if (c >= 'A' && c <= 'Z') + return true; + + return false; +} + +/* === ID3v2.2 === */ +ID3v2_2::FrameHeader::FrameHeader(const ID3v2_2::FrameHeader &frame_header, const ID3v2::Header &_header) : ID3v2::FrameHeader(_header) +{ + frameHeaderData = frame_header.frameHeaderData; +} + +ID3v2_2::FrameHeader::FrameHeader(const ID3v2::Header &_header, const int8_t *id, int flags) : ID3v2::FrameHeader(_header) +{ + memcpy(&frameHeaderData.id, id, 3); + frameHeaderData.id[3]=0; + memset(&frameHeaderData.size, 0, 3); +} + +ID3v2_2::FrameHeader::FrameHeader(const ID3v2::Header &_header, const void *data) : ID3v2::FrameHeader(_header) +{ + char temp_data[FrameHeader::SIZE]; + if (tagHeader.Unsynchronised()) + { + ID3v2::Util::UnsynchroniseTo(temp_data, data, sizeof(temp_data)); + data = temp_data; + } + + bytereader_value_t byte_reader; + bytereader_init(&byte_reader, data, FrameHeader::SIZE); + + bytereader_read_n(&byte_reader, &frameHeaderData.id, 3); + frameHeaderData.id[3]=0; + bytereader_read_n(&byte_reader, &frameHeaderData.size, 3); +} + +bool ID3v2_2::FrameHeader::IsValid() const +{ + if (CharOK(frameHeaderData.id[0]) + && CharOK(frameHeaderData.id[1]) + && CharOK(frameHeaderData.id[2])) + return true; + + return false; +} + +const int8_t *ID3v2_2::FrameHeader::GetIdentifier() const +{ + return frameHeaderData.id; +} + +bool ID3v2_2::FrameHeader::Unsynchronised() const +{ + return tagHeader.Unsynchronised(); +} + +uint32_t ID3v2_2::FrameHeader::FrameSize() const +{ + return (frameHeaderData.size[0] << 16) | (frameHeaderData.size[1] << 8) | (frameHeaderData.size[2]); +} + +void ID3v2_2::FrameHeader::SetSize(uint32_t data_size) +{ + frameHeaderData.size[0] = data_size >> 16; + frameHeaderData.size[1] = data_size >> 8; + frameHeaderData.size[2] = data_size; +} + +int ID3v2_2::FrameHeader::SerializedSize(uint32_t *written) const +{ + if (tagHeader.Unsynchronised()) + { + uint8_t data[SIZE]; + bytewriter_s byte_writer; + bytewriter_init(&byte_writer, data, SIZE); + bytewriter_write_n(&byte_writer, frameHeaderData.id, 3); + bytewriter_write_n(&byte_writer, frameHeaderData.size, 3); + *written = ID3v2::Util::SynchronisedSize(data, SIZE); + } + else + { + *written = SIZE; + } + return NErr_Success; +} + +int ID3v2_2::FrameHeader::Serialize(void *data) const +{ + if (tagHeader.Unsynchronised()) + { + uint8_t temp[SIZE]; + bytewriter_s byte_writer; + bytewriter_init(&byte_writer, temp, SIZE); + bytewriter_write_n(&byte_writer, frameHeaderData.id, 3); + bytewriter_write_n(&byte_writer, frameHeaderData.size, 3); + ID3v2::Util::SynchroniseTo(data, temp, SIZE); + } + else + { + bytewriter_s byte_writer; + bytewriter_init(&byte_writer, data, SIZE); + bytewriter_write_n(&byte_writer, frameHeaderData.id, 3); + bytewriter_write_n(&byte_writer, frameHeaderData.size, 3); + } + return NErr_Success; +} + +/* === ID3v2.3+ common === */ +ID3v2_3::FrameHeaderBase::FrameHeaderBase(const ID3v2_3::FrameHeaderBase &frame_header_base, const ID3v2::Header &_header) : ID3v2::FrameHeader(_header) +{ + memcpy(id, frame_header_base.id, 4); + size=frame_header_base.size; + flags[0] = frame_header_base.flags[0]; + flags[1] = frame_header_base.flags[1]; +} + +ID3v2_3::FrameHeaderBase::FrameHeaderBase(const ID3v2::Header &_header) : ID3v2::FrameHeader(_header) +{ +} + +ID3v2_3::FrameHeaderBase::FrameHeaderBase(const ID3v2::Header &_header, const int8_t *_id, int _flags) : ID3v2::FrameHeader(_header) +{ + memcpy(id, _id, 4); + size=0; + // TODO: flags + flags[0]=0; + flags[1]=0; +} + +const int8_t *ID3v2_3::FrameHeaderBase::GetIdentifier() const +{ + return id; +} + + +bool ID3v2_3::FrameHeaderBase::IsValid() const +{ + if (CharOK(id[0]) + && CharOK(id[1]) + && CharOK(id[2]) + && CharOK(id[3])) + return true; + + return false; +} + + + +/* === ID3v2.3 === */ +ID3v2_3::FrameHeader::FrameHeader(const ID3v2_3::FrameHeader &frame_header, const ID3v2::Header &tag_header) : ID3v2_3::FrameHeaderBase(frame_header, tag_header) +{ +} + +ID3v2_3::FrameHeader::FrameHeader(const ID3v2::Header &_header, const int8_t *id, int flags) : ID3v2_3::FrameHeaderBase(_header, id, flags) +{ +} + +ID3v2_3::FrameHeader::FrameHeader(const ID3v2::Header &_header, const void *data) : ID3v2_3::FrameHeaderBase(_header) +{ + char temp_data[FrameHeaderBase::SIZE]; + if (tagHeader.Unsynchronised()) + { + ID3v2::Util::UnsynchroniseTo(temp_data, data, sizeof(temp_data)); + data = temp_data; + } + + bytereader_value_t byte_reader; + bytereader_init(&byte_reader, data, FrameHeaderBase::SIZE); + + bytereader_read_n(&byte_reader, &id, 4); + size = bytereader_read_u32_be(&byte_reader); + bytereader_read_n(&byte_reader, &flags, 2); +} + +int ID3v2_3::FrameHeaderBase::SerializedSize(uint32_t *written) const +{ + if (tagHeader.Unsynchronised()) + { + uint8_t data[SIZE]; + bytewriter_s byte_writer; + bytewriter_init(&byte_writer, data, SIZE); + bytewriter_write_n(&byte_writer, id, 4); + bytewriter_write_u32_be(&byte_writer, size); + bytewriter_write_u8(&byte_writer, flags[0]); + bytewriter_write_u8(&byte_writer, flags[1]); + *written = ID3v2::Util::SynchronisedSize(data, SIZE); + } + else + { + *written = SIZE; + } + return NErr_Success; +} + +int ID3v2_3::FrameHeaderBase::Serialize(void *data, uint32_t *written) const +{ + if (tagHeader.Unsynchronised()) + { + uint8_t temp[SIZE]; + bytewriter_s byte_writer; + bytewriter_init(&byte_writer, temp, SIZE); + bytewriter_write_n(&byte_writer, id, 4); + bytewriter_write_u32_be(&byte_writer, size); + bytewriter_write_u8(&byte_writer, flags[0]); + bytewriter_write_u8(&byte_writer, flags[1]); + *written = ID3v2::Util::SynchroniseTo(data, temp, SIZE); + } + else + { + bytewriter_s byte_writer; + bytewriter_init(&byte_writer, data, SIZE); + bytewriter_write_n(&byte_writer, id, 4); + bytewriter_write_u32_be(&byte_writer, size); + bytewriter_write_u8(&byte_writer, flags[0]); + bytewriter_write_u8(&byte_writer, flags[1]); + *written = SIZE; + } + return NErr_Success; +} + +uint32_t ID3v2_3::FrameHeader::FrameSize() const +{ + return size; +} + +bool ID3v2_3::FrameHeader::ReadOnly() const +{ + return !!(flags[0] & (1<<5)); +} + +bool ID3v2_3::FrameHeader::Encrypted() const +{ + return !!(flags[1] & (1<<6)); +} + +bool ID3v2_3::FrameHeader::Unsynchronised() const +{ + return tagHeader.Unsynchronised(); +} + +bool ID3v2_3::FrameHeader::Grouped() const +{ + return !!(flags[1] & (1 << 5)); +} + +bool ID3v2_3::FrameHeader::Compressed() const +{ + return !!(flags[1] & (1 << 7)); +} + +bool ID3v2_3::FrameHeader::TagAlterPreservation() const +{ + return !!(flags[0] & (1<<7)); +} + +bool ID3v2_3::FrameHeader::FileAlterPreservation() const +{ + return !!(flags[0] & (1<<6)); +} + +void ID3v2_3::FrameHeader::ClearCompressed() +{ + flags[1] &= ~(1 << 7); +} + +void ID3v2_3::FrameHeader::SetSize(uint32_t data_size) +{ + if (Compressed()) + data_size+=4; + if (Grouped()) + data_size++; + size = data_size; +} + +/* === ID3v2.4 === */ +ID3v2_4::FrameHeader::FrameHeader(const ID3v2_4::FrameHeader &frame_header, const ID3v2::Header &tag_header) : ID3v2_3::FrameHeaderBase(frame_header, tag_header) +{ +} + +ID3v2_4::FrameHeader::FrameHeader(const ID3v2::Header &_header, const int8_t *id, int flags) : ID3v2_3::FrameHeaderBase(_header, id, flags) +{ +} + +ID3v2_4::FrameHeader::FrameHeader(const ID3v2::Header &_header, const void *data) : ID3v2_3::FrameHeaderBase(_header) +{ + bytereader_value_t byte_reader; + bytereader_init(&byte_reader, data, FrameHeaderBase::SIZE); + + bytereader_read_n(&byte_reader, &id, 4); + size = bytereader_read_u32_be(&byte_reader); + bytereader_read_n(&byte_reader, &flags, 2); +} + +uint32_t ID3v2_4::FrameHeader::FrameSize() const +{ + // many programs write non-syncsafe sizes (iTunes is the biggest culprit) + // so we'll try to detect it. unfortunately this isn't foolproof + // ID3v2_4::Frame will have some additional checks + int mask = size & 0x80808080; + if (mask) + return size; + else + return ID3v2::Util::Int28To32(size); +} + +bool ID3v2_4::FrameHeader::ReadOnly() const +{ + return !!(flags[0] & (1<<4)); +} + +bool ID3v2_4::FrameHeader::Encrypted() const +{ + return !!(flags[1] & (1<<3)); +} + +bool ID3v2_4::FrameHeader::Unsynchronised() const +{ + return tagHeader.Unsynchronised() || !!(flags[1] & (1 << 1)); +} + +bool ID3v2_4::FrameHeader::FrameUnsynchronised() const +{ + return !!(flags[1] & (1 << 1)); +} + +bool ID3v2_4::FrameHeader::DataLengthIndicated() const +{ + return !!(flags[1] & (1 << 0)); +} + +bool ID3v2_4::FrameHeader::Compressed() const +{ + return !!(flags[1] & (1 << 3)); +} + +bool ID3v2_4::FrameHeader::Grouped() const +{ + return !!(flags[1] & (1 << 6)); +} + +bool ID3v2_4::FrameHeader::TagAlterPreservation() const +{ + return !!(flags[0] & (1<<6)); +} + +bool ID3v2_4::FrameHeader::FileAlterPreservation() const +{ + return !!(flags[0] & (1<<5)); +} + +void ID3v2_4::FrameHeader::ClearUnsynchronized() +{ + flags[1] &= ~(1 << 1); +} + +void ID3v2_4::FrameHeader::ClearCompressed() +{ + flags[1] &= ~(1 << 3); +} + +void ID3v2_4::FrameHeader::SetSize(uint32_t data_size) +{ + if (Compressed() || DataLengthIndicated()) + data_size+=4; + if (Grouped()) + data_size++; + size = ID3v2::Util::Int32To28(data_size); +} + +int ID3v2_4::FrameHeader::SerializedSize(uint32_t *written) const +{ + *written = SIZE; + return NErr_Success; +} + +int ID3v2_4::FrameHeader::Serialize(void *data, uint32_t *written) const +{ + bytewriter_s byte_writer; + bytewriter_init(&byte_writer, data, SIZE); + bytewriter_write_n(&byte_writer, id, 4); + bytewriter_write_u32_be(&byte_writer, size); + bytewriter_write_u8(&byte_writer, flags[0]); + bytewriter_write_u8(&byte_writer, flags[1]); + *written = SIZE; + return NErr_Success; +} diff --git a/Src/replicant/nsid3v2/frameheader.h b/Src/replicant/nsid3v2/frameheader.h new file mode 100644 index 00000000..bc518ff0 --- /dev/null +++ b/Src/replicant/nsid3v2/frameheader.h @@ -0,0 +1,124 @@ +#pragma once +#include "foundation/types.h" +#include "header.h" + + +namespace ID3v2 +{ + class FrameHeader + { + protected: + FrameHeader(const ID3v2::Header &_header); + const ID3v2::Header &tagHeader; + }; +} + +namespace ID3v2_2 +{ + + struct FrameHeaderData + { + int8_t id[4]; // ID3v2.2 uses 3 bytes but we add a NULL for the last to make it easier + uint8_t size[3]; // 24 bit size field + }; + + class FrameHeader : public ID3v2::FrameHeader + { + public: + FrameHeader(const ID3v2_2::FrameHeader &frame_header, const ID3v2::Header &_header); + FrameHeader(const ID3v2::Header &_header, const int8_t *id, int flags); + FrameHeader(const ID3v2::Header &_header, const void *data); + bool IsValid() const; + bool Unsynchronised() const; + uint32_t FrameSize() const; + const int8_t *GetIdentifier() const; + void SetSize(uint32_t data_size); + int SerializedSize(uint32_t *written) const; + int Serialize(void *data) const; + enum + { + SIZE=6, + }; + private: + FrameHeaderData frameHeaderData; + }; +} + +namespace ID3v2_3 +{ + + class FrameHeaderBase : public ID3v2::FrameHeader + { + public: + int SerializedSize(uint32_t *written) const; + int Serialize(void *data, uint32_t *written) const; + bool IsValid() const; + const int8_t *GetIdentifier() const; + enum + { + SIZE=10, + }; + + protected: + FrameHeaderBase(const ID3v2_3::FrameHeaderBase &frame_header_base, const ID3v2::Header &_header); + FrameHeaderBase(const ID3v2::Header &_header); + FrameHeaderBase(const ID3v2::Header &_header, const int8_t *id, int flags); + + int8_t id[4]; + uint32_t size; + uint8_t flags[2]; + }; + + class FrameHeader : public ID3v2_3::FrameHeaderBase + { + public: + FrameHeader(const ID3v2_3::FrameHeader &frame_header, const ID3v2::Header &_header); + FrameHeader(const ID3v2::Header &_header, const int8_t *id, int flags); + FrameHeader(const ID3v2::Header &_header, const void *data); + + uint32_t FrameSize() const; + bool Encrypted() const; + bool Compressed() const; + bool Grouped() const; + bool ReadOnly() const; + bool Unsynchronised() const; + bool TagAlterPreservation() const; + bool FileAlterPreservation() const; + + void ClearCompressed(); + /* sets a new size, given a data size. this function might add additional size (grouped, compressed, etc) */ + void SetSize(uint32_t data_size); + + private: + + }; +} + +namespace ID3v2_4 +{ + class FrameHeader : public ID3v2_3::FrameHeaderBase + { + public: + FrameHeader(const ID3v2_4::FrameHeader &frame_header, const ID3v2::Header &_header); + FrameHeader(const ID3v2::Header &_header, const int8_t *id, int flags); + FrameHeader(const ID3v2::Header &_header, const void *data); + /* size to read from disk */ + uint32_t FrameSize() const; + bool Encrypted() const; + bool Compressed() const; + bool Grouped() const; + bool ReadOnly() const; + bool Unsynchronised() const; + bool FrameUnsynchronised() const; + bool DataLengthIndicated() const; + bool TagAlterPreservation() const; + bool FileAlterPreservation() const; + + void ClearUnsynchronized(); + void ClearCompressed(); + /* sets a new size, given a data size. this function might add additional size (grouped, compressed, etc) */ + void SetSize(uint32_t data_size); + int SerializedSize(uint32_t *written) const; + int Serialize(void *data, uint32_t *written) const; + }; +} diff --git a/Src/replicant/nsid3v2/frames.c b/Src/replicant/nsid3v2/frames.c new file mode 100644 index 00000000..f64052c7 --- /dev/null +++ b/Src/replicant/nsid3v2/frames.c @@ -0,0 +1,61 @@ +#include "frames.h" + +/* this is a .c file to shut up GCC which doesn't like to convert from int8_t to char */ + +/* order needs to match the enum in nsid3v2.h */ + +const FrameID frame_ids[] = +{ + {FRAMEID("PIC"), FRAMEID("APIC"), FRAMEID("APIC")}, + {FRAMEID("COM"), FRAMEID("COMM"), FRAMEID("COMM")}, + {FRAMEID("POP"), FRAMEID("POPM"), FRAMEID("POPM")}, + {FRAMEID("TAL"), FRAMEID("TALB"), FRAMEID("TALB")}, + {FRAMEID("TBP"), FRAMEID("TBPM"), FRAMEID("TBPM")}, + {FRAMEID("TCM"), FRAMEID("TCOM"), FRAMEID("TCOM")}, + {FRAMEID("TCO"), FRAMEID("TCON"), FRAMEID("TCON")}, + {FRAMEID("TCR"), FRAMEID("TCOP"), FRAMEID("TCOP")}, + {FRAMEID("TDA"), FRAMEID("TDAT"), FRAMEID("TDAT")}, + {FRAMEID("TDY"), FRAMEID("TDLY"), FRAMEID("TDLY")}, + {FRAMEID(0), FRAMEID(0), FRAMEID("TDRC")}, + {FRAMEID("TEN"), FRAMEID("TENC"), FRAMEID("TENC")}, + {FRAMEID(0), FRAMEID("TEXT"), FRAMEID("TEXT")}, + {FRAMEID("TFT"), FRAMEID("TFLT"), FRAMEID("TFLT")}, + {FRAMEID("TIM"), FRAMEID("TIME"), FRAMEID("TIME")}, + {FRAMEID("TT1"), FRAMEID("TIT1"), FRAMEID("TIT1")}, + {FRAMEID("TT2"), FRAMEID("TIT2"), FRAMEID("TIT2")}, + {FRAMEID("TT3"), FRAMEID("TIT3"), FRAMEID("TIT3")}, + {FRAMEID("TKE"), FRAMEID("TKEY"), FRAMEID("TKEY")}, + {FRAMEID("TLA"), FRAMEID("TLAN"), FRAMEID("TLAN")}, + {FRAMEID("TLE"), FRAMEID("TLEN"), FRAMEID("TLEN")}, + {FRAMEID("TMT"), FRAMEID("TMED"), FRAMEID("TMED")}, + {FRAMEID(0), FRAMEID(0), FRAMEID("TMOO")}, + {FRAMEID(0), FRAMEID("TOAL"), FRAMEID("TOAL")}, + + {FRAMEID("TOA"), FRAMEID("TOPE"), FRAMEID("TOPE")}, + + {FRAMEID("TP1"), FRAMEID("TPE1"), FRAMEID("TPE1")}, + {FRAMEID("TP2"), FRAMEID("TPE2"), FRAMEID("TPE2")}, + {FRAMEID("TP3"), FRAMEID("TPE3"), FRAMEID("TPE3")}, + {FRAMEID("TP4"), FRAMEID("TPE4"), FRAMEID("TPE4")}, + {FRAMEID("TPA"), FRAMEID("TPOS"), FRAMEID("TPOS")}, + {FRAMEID("TPB"), FRAMEID("TPUB"), FRAMEID("TPUB")}, + {FRAMEID("TRK"), FRAMEID("TRCK"), FRAMEID("TRCK")}, + {FRAMEID("TRD"), FRAMEID("TRDA"), FRAMEID("TRDA")}, + + {FRAMEID("TRC"), FRAMEID("TSRC"), FRAMEID("TSRC")}, + {FRAMEID("TSS"), FRAMEID("TSSE"), FRAMEID("TSSE")}, + {FRAMEID("TYE"), FRAMEID("TYER"), FRAMEID("TYER")}, + + {FRAMEID("TXX"), FRAMEID("TXXX"), FRAMEID("TXXX")}, + {FRAMEID("UFI"), FRAMEID("UFID"), FRAMEID("UFID")}, + +}; + +int ValidFrameID(int id) +{ + if (id < 0) + return 0; + if (id >= (sizeof(frame_ids) / sizeof(*frame_ids))) + return 0; + return 1; +} diff --git a/Src/replicant/nsid3v2/frames.h b/Src/replicant/nsid3v2/frames.h new file mode 100644 index 00000000..d30ae26c --- /dev/null +++ b/Src/replicant/nsid3v2/frames.h @@ -0,0 +1,21 @@ +#pragma once +#include "foundation/types.h" +#ifdef __cplusplus +extern "C" { +#endif + +#define FRAMEID(__frame_id) ((const int8_t*)__frame_id) + +typedef struct frameid_struct_t +{ + const int8_t *v2; + const int8_t *v3; + const int8_t *v4; +} FrameID; + +extern const FrameID frame_ids[]; + +int ValidFrameID(int frame_id); +#ifdef __cplusplus +} +#endif diff --git a/Src/replicant/nsid3v2/header.cpp b/Src/replicant/nsid3v2/header.cpp new file mode 100644 index 00000000..f006b238 --- /dev/null +++ b/Src/replicant/nsid3v2/header.cpp @@ -0,0 +1,186 @@ +#include "header.h" +#include "values.h" +#include "util.h" +#include <assert.h> +#include "nu/ByteReader.h" +#include "nu/ByteWriter.h" +#include <string.h> +#include "foundation/error.h" + +ID3v2::Header::Header() +{ + marker[0]=0; + marker[1]=0; + marker[2]=0; + version=0; + revision=0; + flags=0; + size=0; +} + +ID3v2::Header::Header(uint8_t version, uint8_t revision) +{ + marker[0]='I'; + marker[1]='D'; + marker[2]='3'; + this->version=version; + this->revision=revision; + this->flags=0; + this->size=0; +} + +ID3v2::Header::Header(const void *data) +{ + Parse(data); +} + + +ID3v2::Header::Header(const ID3v2::Header *copy, uint32_t new_size) +{ + marker[0]=copy->marker[0]; + marker[1]=copy->marker[1]; + marker[2]=copy->marker[2]; + version=copy->version; + revision=copy->revision; + flags=copy->flags; + size = Util::Int32To28(new_size); +} + +void ID3v2::Header::Parse(const void *data) +{ + bytereader_value_t byte_reader; + bytereader_init(&byte_reader, data, Header::SIZE); + bytereader_read_n(&byte_reader, &marker, 3); + version = bytereader_read_u8(&byte_reader); + revision = bytereader_read_u8(&byte_reader); + flags = bytereader_read_u8(&byte_reader); + size = bytereader_read_u32_be(&byte_reader); +} + +int ID3v2::Header::Serialize(void *data) +{ + bytewriter_s byte_writer; + bytewriter_init(&byte_writer, data, 10); + bytewriter_write_n(&byte_writer, marker, 3); + bytewriter_write_u8(&byte_writer, version); + bytewriter_write_u8(&byte_writer, revision); + bytewriter_write_u8(&byte_writer, flags); + bytewriter_write_u32_be(&byte_writer, size); + return NErr_Success; +} + +int ID3v2::Header::SerializeAsHeader(void *data) +{ + bytewriter_s byte_writer; + bytewriter_init(&byte_writer, data, 10); + bytewriter_write_n(&byte_writer, "ID3", 3); + bytewriter_write_u8(&byte_writer, version); + bytewriter_write_u8(&byte_writer, revision); + bytewriter_write_u8(&byte_writer, flags); + bytewriter_write_u32_be(&byte_writer, size); + return NErr_Success; +} + +int ID3v2::Header::SerializeAsFooter(void *data) +{ + bytewriter_s byte_writer; + bytewriter_init(&byte_writer, data, 10); + bytewriter_write_n(&byte_writer, "3DI", 3); + bytewriter_write_u8(&byte_writer, version); + bytewriter_write_u8(&byte_writer, revision); + bytewriter_write_u8(&byte_writer, flags); + bytewriter_write_u32_be(&byte_writer, size); + return NErr_Success; +} + +bool ID3v2::Header::Valid() const +{ + if (marker[0] != 'I' + || marker[1] != 'D' + || marker[2] != '3') + return false; + + if (!Values::KnownVersion(version, revision)) + return false; + + if (flags & ~Values::ValidHeaderMask(version, revision)) + return false; + + if (size & 0x80808080) + return false; + + return true; +} + +bool ID3v2::Header::FooterValid() const +{ + if (marker[0] != '3' + || marker[1] != 'D' + || marker[2] != 'I') + return false; + + if (!Values::KnownVersion(version, revision)) + return false; + + if (flags & ~Values::ValidHeaderMask(version, revision)) + return false; + + if (size & 0x80808080) + return false; + + return true; +} + +uint32_t ID3v2::Header::TagSize() const +{ + uint32_t size = Util::Int28To32(this->size); + return size; +} + +bool ID3v2::Header::HasExtendedHeader() const +{ + return !!(flags & ID3v2::Values::ExtendedHeaderFlag(version, revision)); +} + +uint8_t ID3v2::Header::GetVersion() const +{ + return version; +} + +uint8_t ID3v2::Header::GetRevision() const +{ + return revision; +} + +bool ID3v2::Header::Unsynchronised() const +{ + return !!(flags & (1 << 7)); +} + +bool ID3v2::Header::HasFooter() const +{ + if (version < 4) + return false; + return !!(flags & 0x10); +} + +void ID3v2::Header::ClearExtendedHeader() +{ + flags &= ~ID3v2::Values::ExtendedHeaderFlag(version, revision); +} + +void ID3v2::Header::ClearUnsynchronized() +{ + flags &= ~(1 << 7); +} + +void ID3v2::Header::SetFooter(bool footer) +{ + if (version >= 4) + { + if (footer) + flags |= 0x10; + else + flags &= 0x10; + } +} diff --git a/Src/replicant/nsid3v2/header.h b/Src/replicant/nsid3v2/header.h new file mode 100644 index 00000000..bb009958 --- /dev/null +++ b/Src/replicant/nsid3v2/header.h @@ -0,0 +1,44 @@ +#pragma once + +#include "foundation/types.h" + +namespace ID3v2 +{ + class Header + { + public: + Header(); + Header(uint8_t version, uint8_t revision); + Header(const void *data); + Header(const Header *copy, uint32_t new_size); + void Parse(const void *data); + int Serialize(void *data); + int SerializeAsHeader(void *data); + int SerializeAsFooter(void *data); + /* Does this seem like a valid ID3v2 header? */ + bool Valid() const; + bool FooterValid() const; + /* how much space the tag occupies on disk */ + uint32_t TagSize() const; + bool HasExtendedHeader() const; + uint8_t GetVersion() const; + uint8_t GetRevision() const; + bool Unsynchronised() const; + bool HasFooter() const; + void SetFooter(bool footer); + enum + { + SIZE=10, + }; + + void ClearExtendedHeader(); + void ClearUnsynchronized(); + private: + + uint8_t marker[3]; + uint8_t version; + uint8_t revision; + uint8_t flags; + uint32_t size; + }; +} diff --git a/Src/replicant/nsid3v2/nsid3v2.h b/Src/replicant/nsid3v2/nsid3v2.h new file mode 100644 index 00000000..b656c07d --- /dev/null +++ b/Src/replicant/nsid3v2/nsid3v2.h @@ -0,0 +1,190 @@ +#pragma once + +/* include any platform-specific stuff, first */ +#ifdef __ANDROID__ +#include "android/nsid3v2.h" +#elif defined(_WIN32) +#include "windows/nsid3v2.h" +#elif defined(__linux__) +#include "linux/nsid3v2.h" +#elif defined(__APPLE__) +#include "osx/nsid3v2.h" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + // must be exactly 10 bytes + NSID3V2_EXPORT int NSID3v2_Header_Valid(const void *header_data); + NSID3V2_EXPORT int NSID3v2_Header_FooterValid(const void *footer_data); + NSID3V2_EXPORT int NSID3v2_Header_Create(nsid3v2_header_t *header, const void *header_data, size_t header_len); + NSID3V2_EXPORT int NSID3v2_Header_New(nsid3v2_header_t *h, uint8_t version, uint8_t revision); + + /* Creates from footer data instead of header data */ + NSID3V2_EXPORT int NSID3v2_Header_FooterCreate(nsid3v2_header_t *footer, const void *footer_data, size_t footer_len); + NSID3V2_EXPORT int NSID3v2_Header_TagSize(const nsid3v2_header_t header, uint32_t *tag_size); + NSID3V2_EXPORT int NSID3v2_Header_HasFooter(const nsid3v2_header_t header); + NSID3V2_EXPORT int NSID3v2_Header_Destroy(nsid3v2_header_t header); + + // currently, this function makes a copy of any necessary data. in the future, it would be better + // to make another version of this function that "borrows" your data + // if you can guarantee that the memory will outlive the nsid3v2_tag_t object + NSID3V2_EXPORT int NSID3v2_Tag_Create(nsid3v2_tag_t *tag, const nsid3v2_header_t header, const void *bytes, size_t bytes_len); + NSID3V2_EXPORT int NSID3v2_Tag_Destroy(nsid3v2_tag_t tag); + + NSID3V2_EXPORT int NSID3v2_Tag_GetFrame(const nsid3v2_tag_t tag, int frame_enum, nsid3v2_frame_t *frame); + NSID3V2_EXPORT int NSID3v2_Tag_GetNextFrame(const nsid3v2_tag_t tag, const nsid3v2_frame_t start_frame, nsid3v2_frame_t *frame); + NSID3V2_EXPORT int NSID3v2_Tag_RemoveFrame(nsid3v2_tag_t tag, nsid3v2_frame_t frame); + NSID3V2_EXPORT int NSID3v2_Tag_CreateFrame(nsid3v2_tag_t tag, int frame_enum, int flags, nsid3v2_frame_t *frame); + NSID3V2_EXPORT int NSID3v2_Tag_AddFrame(nsid3v2_tag_t tag, nsid3v2_frame_t frame); + NSID3V2_EXPORT int NSID3v2_Tag_EnumerateFrame(const nsid3v2_tag_t tag, nsid3v2_frame_t position, nsid3v2_frame_t *frame); + + NSID3V2_EXPORT int NSID3v2_Tag_GetInformation(nsid3v2_tag_t tag, uint8_t *version, uint8_t *revision, int *flags); + + /* + get specific information out of a tag. returns the first one found that matches the requirements. + */ + enum + { + NSID3V2_TEXT_SYSTEM=1, // use system code page instead of ISO-8859-1 + }; + NSID3V2_EXPORT int NSID3v2_Tag_TXXX_Find(const nsid3v2_tag_t tag, const char *description, nsid3v2_frame_t *out_frame, int text_flags); + NSID3V2_EXPORT int NSID3v2_Tag_ID_Find(const nsid3v2_tag_t tag, const char *owner, nsid3v2_frame_t *out_frame, int text_flags); + NSID3V2_EXPORT int NSID3v2_Tag_Comments_Find(const nsid3v2_tag_t tag, const char *description, nsid3v2_frame_t *out_frame, int text_flags); + + NSID3V2_EXPORT int NSID3v2_Tag_Text_Get(const nsid3v2_tag_t tag, int frame_enum, nx_string_t *value, int text_flags); + NSID3V2_EXPORT int NSID3v2_Tag_TXXX_Get(const nsid3v2_tag_t tag, const char *description, nx_string_t *value, int text_flags); + NSID3V2_EXPORT int NSID3v2_Tag_Popularimeter_GetRatingPlaycount(const nsid3v2_tag_t tag, const char *email, uint8_t *rating, uint64_t *playcount); + NSID3V2_EXPORT int NSID3v2_Tag_Comments_Get(const nsid3v2_tag_t tag, const char *description, char language[3], nx_string_t *value, int text_flags); + NSID3V2_EXPORT int NSID3v2_Tag_WXXX_Get(const nsid3v2_tag_t tag, const char *description, nx_string_t *value, int text_flags); + NSID3V2_EXPORT int NSID3v2_Tag_ID_Get(const nsid3v2_tag_t tag, const char *owner, const void **id_data, size_t *length, int text_flags); + + NSID3V2_EXPORT int NSID3v2_Frame_GetInformation(nsid3v2_frame_t frame, int *type, int *flags); + + NSID3V2_EXPORT int NSID3v2_Frame_Text_Get(const nsid3v2_frame_t frame, nx_string_t *value, int text_flags); + NSID3V2_EXPORT int NSID3v2_Frame_UserText_Get(const nsid3v2_frame_t frame, nx_string_t *description, nx_string_t *value, int text_flags); + NSID3V2_EXPORT int NSID3v2_Frame_Private_Get(const nsid3v2_frame_t frame, nx_string_t *description, const void **data, size_t *length); + NSID3V2_EXPORT int NSID3v2_Frame_Object_Get(const nsid3v2_frame_t frame, nx_string_t *mime, nx_string_t *filename, nx_string_t *description, const void **out_data, size_t *length, int text_flags); + NSID3V2_EXPORT int NSID3v2_Frame_Popularity_Get(nsid3v2_frame_t frame, nx_string_t *email, uint8_t *rating, uint64_t *playcount, int text_flags); + NSID3V2_EXPORT int NSID3v2_Frame_Picture_Get(const nsid3v2_frame_t frame, nx_string_t *mime, uint8_t *picture_type, nx_string_t *description, const void **picture_data, size_t *length, int text_flags); + NSID3V2_EXPORT int NSID3v2_Frame_ID_Get(nsid3v2_frame_t frame, nx_string_t *owner, const void **id_data, size_t *length, int text_flags); + NSID3V2_EXPORT int NSID3v2_Frame_Comments_Get(const nsid3v2_frame_t frame, nx_string_t *description, char language[3], nx_string_t *value, int text_flags); + NSID3V2_EXPORT int NSID3v2_Frame_URL_Get(const nsid3v2_frame_t frame, nx_string_t *value, int text_flags); + NSID3V2_EXPORT int NSID3v2_Frame_UserURL_Get(const nsid3v2_frame_t frame, nx_string_t *description, nx_string_t *value, int text_flags); + NSID3V2_EXPORT int NSID3v2_Frame_Binary_Get(nsid3v2_frame_t frame, const void **binary, size_t *length); + + NSID3V2_EXPORT int NSID3v2_Frame_GetIdentifier(nsid3v2_frame_t frame, const char **identifier); + + enum + { + SerializedSize_Padding = (0x1 << 0), // write 'padding_size' extra 0's to the file + SerializedSize_AbsoluteSize = (0x2 << 0), // 'padding_size' represents the final total tag size + SerializedSize_BlockSize = (0x3 << 0), // 'padding_size' represents a block size. the total tag size will be a multiple of 'padding_size' + SerializedSize_PaddingMask = (0x3 << 0), + + // note that setting NEITHER of this will preserve whatever setting was originally used in the tag (or individual frames for ID3v2.4) + Serialize_Unsynchronize = (0x1 << 2), // force the tag to be unsynchronized, even if it wasn't originally + Serialize_NoUnsynchronize = (0x2 << 2), // disable all unsynchronization, even if the tag was originally unsynchronized + Serialize_UnsynchronizeMask = (0x3 << 2), + + Serialize_NoCompression = (0x1 << 4), // disables all compression + Serialize_CompressionMask = (0x1 << 4), + }; + + NSID3V2_EXPORT int NSID3v2_Tag_SerializedSize(nsid3v2_tag_t tag, uint32_t *length, uint32_t padding_size, int flags); + NSID3V2_EXPORT int NSID3v2_Tag_Serialize(nsid3v2_tag_t tag, void *data, uint32_t len, int flags); + + NSID3V2_EXPORT int NSID3v2_Tag_Text_Set(nsid3v2_tag_t tag, int frame_enum, nx_string_t value, int text_flags); + NSID3V2_EXPORT int NSID3v2_Tag_TXXX_Set(nsid3v2_tag_t tag, const char *description, nx_string_t value, int text_flags); + NSID3V2_EXPORT int NSID3v2_Tag_Comments_Set(nsid3v2_tag_t tag, const char *description, const char language[3], nx_string_t value, int text_flags); + NSID3V2_EXPORT int NSID3v2_Tag_ID_Set(nsid3v2_tag_t tag, const char *owner, const void *id_data, size_t length, int text_flags); + + NSID3V2_EXPORT int NSID3v2_Frame_Text_Set(nsid3v2_frame_t frame, nx_string_t value, int text_flags); + NSID3V2_EXPORT int NSID3v2_Frame_UserText_Set(nsid3v2_frame_t frame, const char *description, nx_string_t value, int text_flags); + NSID3V2_EXPORT int NSID3v2_Frame_Comments_Set(nsid3v2_frame_t frame, const char *description, const char language[3], nx_string_t value, int text_flags); + NSID3V2_EXPORT int NSID3v2_Frame_ID_Set(nsid3v2_frame_t frame, const char *owner, const void *id_data, size_t length, int text_flags); + NSID3V2_EXPORT int NSID3v2_Frame_Picture_Set(nsid3v2_frame_t frame, nx_string_t mime, uint8_t picture_type, nx_string_t description, const void *picture_data, size_t length, int text_flags); + enum + { + NSID3V2_FRAME_PICTURE, // APIC + NSID3V2_FRAME_COMMENTS, // COMM + NSID3V2_FRAME_POPULARIMETER, // POPM + NSID3V2_FRAME_ALBUM, // TALB + NSID3V2_FRAME_BPM, // TBPM + NSID3V2_FRAME_COMPOSER, // TCOM + NSID3V2_FRAME_CONTENTTYPE, // TCON + NSID3V2_FRAME_COPYRIGHT, // TCOP + NSID3V2_FRAME_DATE, // TDAT + NSID3V2_FRAME_PLAYLISTDELAY, // TDLY + NSID3V2_FRAME_RECORDINGTIME, // TDRC + NSID3V2_FRAME_ENCODEDBY, // TENC + NSID3V2_FRAME_LYRICIST, // TEXT + NSID3V2_FRAME_FILETYPE, // TFLT + NSID3V2_FRAME_TIME, // TIME + NSID3V2_FRAME_CONTENTGROUP, // TIT1 + NSID3V2_FRAME_TITLE, // TIT2 + NSID3V2_FRAME_SUBTITLE, // TIT3 + NSID3V2_FRAME_KEY, // TKEY + NSID3V2_FRAME_LANGUAGE, // TLAN + NSID3V2_FRAME_LENGTH, // TLEN + NSID3V2_FRAME_MEDIATYPE, // TMED + NSID3V2_FRAME_MOOD, // TMOO + NSID3V2_FRAME_ORIGINALALBUM, // TOAL + + NSID3V2_FRAME_ORIGINALARTIST, // TOPE + + NSID3V2_FRAME_LEADARTIST, // TPE1 + NSID3V2_FRAME_BAND, // TPE2 + NSID3V2_FRAME_CONDUCTOR, // TPE3 + NSID3V2_FRAME_REMIXER, // TPE4 + NSID3V2_FRAME_PARTOFSET, // TPOS + NSID3V2_FRAME_PUBLISHER, // TPUB + NSID3V2_FRAME_TRACK, // TRCK + NSID3V2_FRAME_RECORDINGDATES, // TRDA + + NSID3V2_FRAME_ISRC, // TSRC + NSID3V2_FRAME_ENCODERSETTINGS, // TSSE + NSID3V2_FRAME_YEAR, // TYER + + NSID3V2_FRAME_USER_TEXT, // TXXX + + NSID3V2_FRAME_ID, // UFID + + }; + + /* frame types */ + enum + { + NSID3V2_FRAMETYPE_UNKNOWN, + NSID3V2_FRAMETYPE_TEXT, + NSID3V2_FRAMETYPE_USERTEXT, + NSID3V2_FRAMETYPE_COMMENTS, + NSID3V2_FRAMETYPE_URL, + NSID3V2_FRAMETYPE_USERURL, + NSID3V2_FRAMETYPE_PRIVATE, + NSID3V2_FRAMETYPE_OBJECT, + NSID3V2_FRAMETYPE_POPULARITY, + NSID3V2_FRAMETYPE_PICTURE, + NSID3V2_FRAMETYPE_ID, + }; + + /* these DO NOT map to the actual flag bitmasks, they are only for API usage! */ + enum + { + NSID3V2_TAGFLAG_EXTENDED_HEADER = 1<<1, + NSID3V2_TAGFLAG_UNSYNCHRONIZED = 1<<2, + NSID3V2_TAGFLAG_HASFOOTER = 1<<3, + + NSID3V2_FRAMEFLAG_TAG_ALTER_PRESERVATION = 1<<1, + NSID3V2_FRAMEFLAG_FILE_ALTER_PRESERVATION = 1<<2, + NSID3V2_FRAMEFLAG_ENCRYPTED = 1<<3, + NSID3V2_FRAMEFLAG_COMPRESSED = 1<<4, + NSID3V2_FRAMEFLAG_GROUPED = 1<<5, + NSID3V2_FRAMEFLAG_READONLY =1<<6, + NSID3V2_FRAMEFLAG_UNSYNCHRONIZED =1<<7, + NSID3V2_FRAMEFLAG_DATALENGTHINDICATED =1<<8, + }; + +#ifdef __cplusplus +} +#endif diff --git a/Src/replicant/nsid3v2/nsid3v2.sln b/Src/replicant/nsid3v2/nsid3v2.sln new file mode 100644 index 00000000..9e9f4b16 --- /dev/null +++ b/Src/replicant/nsid3v2/nsid3v2.sln @@ -0,0 +1,44 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29509.3 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nsid3v2", "nsid3v2.vcxproj", "{32025DF9-ACB0-435E-8D51-743719818799}" + ProjectSection(ProjectDependencies) = postProject + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915} = {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nu", "..\nu\nu.vcxproj", "{F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {32025DF9-ACB0-435E-8D51-743719818799}.Debug|Win32.ActiveCfg = Debug|Win32 + {32025DF9-ACB0-435E-8D51-743719818799}.Debug|Win32.Build.0 = Debug|Win32 + {32025DF9-ACB0-435E-8D51-743719818799}.Debug|x64.ActiveCfg = Debug|x64 + {32025DF9-ACB0-435E-8D51-743719818799}.Debug|x64.Build.0 = Debug|x64 + {32025DF9-ACB0-435E-8D51-743719818799}.Release|Win32.ActiveCfg = Release|Win32 + {32025DF9-ACB0-435E-8D51-743719818799}.Release|Win32.Build.0 = Release|Win32 + {32025DF9-ACB0-435E-8D51-743719818799}.Release|x64.ActiveCfg = Release|x64 + {32025DF9-ACB0-435E-8D51-743719818799}.Release|x64.Build.0 = Release|x64 + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|Win32.ActiveCfg = Debug|Win32 + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|Win32.Build.0 = Debug|Win32 + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|x64.ActiveCfg = Debug|x64 + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|x64.Build.0 = Debug|x64 + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|Win32.ActiveCfg = Release|Win32 + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|Win32.Build.0 = Release|Win32 + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|x64.ActiveCfg = Release|x64 + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {EE7C04E4-0AAC-4AB2-8176-253E6622DB09} + EndGlobalSection +EndGlobal diff --git a/Src/replicant/nsid3v2/nsid3v2.vcxproj b/Src/replicant/nsid3v2/nsid3v2.vcxproj new file mode 100644 index 00000000..506667e4 --- /dev/null +++ b/Src/replicant/nsid3v2/nsid3v2.vcxproj @@ -0,0 +1,194 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup Label="ProjectConfigurations"> + <ProjectConfiguration Include="Debug|Win32"> + <Configuration>Debug</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Debug|x64"> + <Configuration>Debug</Configuration> + <Platform>x64</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|Win32"> + <Configuration>Release</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|x64"> + <Configuration>Release</Configuration> + <Platform>x64</Platform> + </ProjectConfiguration> + </ItemGroup> + <PropertyGroup Label="Globals"> + <ProjectGuid>{32025DF9-ACB0-435E-8D51-743719818799}</ProjectGuid> + <RootNamespace>nsid3v2</RootNamespace> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration"> + <ConfigurationType>StaticLibrary</ConfigurationType> + <PlatformToolset>v142</PlatformToolset> + <CharacterSet>Unicode</CharacterSet> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration"> + <ConfigurationType>StaticLibrary</ConfigurationType> + <PlatformToolset>v142</PlatformToolset> + <CharacterSet>Unicode</CharacterSet> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration"> + <ConfigurationType>StaticLibrary</ConfigurationType> + <PlatformToolset>v142</PlatformToolset> + <CharacterSet>Unicode</CharacterSet> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration"> + <ConfigurationType>StaticLibrary</ConfigurationType> + <PlatformToolset>v142</PlatformToolset> + <CharacterSet>Unicode</CharacterSet> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> + <ImportGroup Label="ExtensionSettings"> + </ImportGroup> + <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <PropertyGroup Label="UserMacros" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <LinkIncremental>true</LinkIncremental> + <OutDir>x86_Debug\</OutDir> + <IntDir>x86_Debug\</IntDir> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <LinkIncremental>true</LinkIncremental> + <OutDir>x64_Debug\</OutDir> + <IntDir>x64_Debug\</IntDir> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <LinkIncremental>false</LinkIncremental> + <OutDir>x86_Release\</OutDir> + <IntDir>x86_Release\</IntDir> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <LinkIncremental>false</LinkIncremental> + <OutDir>x64_Release\</OutDir> + <IntDir>x64_Release\</IntDir> + </PropertyGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <ClCompile> + <Optimization>Disabled</Optimization> + <AdditionalIncludeDirectories>..;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <MinimalRebuild>false</MinimalRebuild> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> + <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>EditAndContinue</DebugInformationFormat> + <DisableSpecificWarnings>4267;%(DisableSpecificWarnings)</DisableSpecificWarnings> + </ClCompile> + <Lib> + <OutputFile>$(ProjectDir)x86_Debug\$(ProjectName).lib</OutputFile> + </Lib> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <ClCompile> + <Optimization>Disabled</Optimization> + <AdditionalIncludeDirectories>..;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>WIN64;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <MinimalRebuild>false</MinimalRebuild> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> + <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>EditAndContinue</DebugInformationFormat> + <DisableSpecificWarnings>4267;%(DisableSpecificWarnings)</DisableSpecificWarnings> + </ClCompile> + <Lib> + <OutputFile>$(ProjectDir)x64_Debug\$(ProjectName).lib</OutputFile> + </Lib> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <ClCompile> + <Optimization>MaxSpeed</Optimization> + <IntrinsicFunctions>true</IntrinsicFunctions> + <AdditionalIncludeDirectories>..;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <RuntimeLibrary>MultiThreaded</RuntimeLibrary> + <FunctionLevelLinking>true</FunctionLevelLinking> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> + <DisableSpecificWarnings>4267;%(DisableSpecificWarnings)</DisableSpecificWarnings> + </ClCompile> + <Lib> + <OutputFile>$(ProjectDir)x86_Release\$(ProjectName).lib</OutputFile> + </Lib> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <ClCompile> + <Optimization>MaxSpeed</Optimization> + <IntrinsicFunctions>true</IntrinsicFunctions> + <AdditionalIncludeDirectories>..;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>WIN64;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <RuntimeLibrary>MultiThreaded</RuntimeLibrary> + <FunctionLevelLinking>true</FunctionLevelLinking> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> + <DisableSpecificWarnings>4267;%(DisableSpecificWarnings)</DisableSpecificWarnings> + </ClCompile> + <Lib> + <OutputFile>$(ProjectDir)x64_Release\$(ProjectName).lib</OutputFile> + </Lib> + </ItemDefinitionGroup> + <ItemGroup> + <ProjectReference Include="..\nu\nu.vcxproj"> + <Project>{efc75a79-269f-44fc-bac5-d7d4fd4ec92c}</Project> + <CopyLocalSatelliteAssemblies>true</CopyLocalSatelliteAssemblies> + <ReferenceOutputAssembly>true</ReferenceOutputAssembly> + </ProjectReference> + </ItemGroup> + <ItemGroup> + <ClCompile Include="extendedheader.cpp" /> + <ClCompile Include="frame.cpp" /> + <ClCompile Include="frameheader.cpp" /> + <ClCompile Include="frames.c" /> + <ClCompile Include="frame_apic.cpp" /> + <ClCompile Include="frame_comments.cpp" /> + <ClCompile Include="frame_id.cpp" /> + <ClCompile Include="frame_object.cpp" /> + <ClCompile Include="frame_popm.cpp" /> + <ClCompile Include="frame_private.cpp" /> + <ClCompile Include="frame_text.cpp" /> + <ClCompile Include="frame_url.cpp" /> + <ClCompile Include="frame_usertext.cpp" /> + <ClCompile Include="frame_userurl.cpp" /> + <ClCompile Include="frame_utils.cpp" /> + <ClCompile Include="header.cpp" /> + <ClCompile Include="tag.cpp" /> + <ClCompile Include="util.cpp" /> + <ClCompile Include="values.cpp" /> + </ItemGroup> + <ItemGroup> + <ClInclude Include="extendedheader.h" /> + <ClInclude Include="frame.h" /> + <ClInclude Include="frameheader.h" /> + <ClInclude Include="frames.h" /> + <ClInclude Include="frame_utils.h" /> + <ClInclude Include="header.h" /> + <ClInclude Include="nsid3v2.h" /> + <ClInclude Include="precomp.h" /> + <ClInclude Include="tag.h" /> + <ClInclude Include="util.h" /> + <ClInclude Include="values.h" /> + </ItemGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> + <ImportGroup Label="ExtensionTargets"> + </ImportGroup> +</Project>
\ No newline at end of file diff --git a/Src/replicant/nsid3v2/nsid3v2_common.cpp b/Src/replicant/nsid3v2/nsid3v2_common.cpp new file mode 100644 index 00000000..e1e53e5d --- /dev/null +++ b/Src/replicant/nsid3v2/nsid3v2_common.cpp @@ -0,0 +1,366 @@ +#include "nsid3v2/nsid3v2.h" +#include "nsid3v2/header.h" +#include "nsid3v2/tag.h" +#include <string.h> // for memcmp +#include <new> + +int NSID3v2_Header_Valid(const void *header_data) +{ + ID3v2::Header header(header_data); + if (header.Valid()) + return NErr_Success; + else + return NErr_False; +} + +int NSID3v2_Header_FooterValid(const void *footer_data) +{ + ID3v2::Header footer(footer_data); + if (footer.FooterValid()) + return NErr_Success; + else + return NErr_False; +} + +int NSID3v2_Header_Create(nsid3v2_header_t *h, const void *header_data, size_t header_len) +{ + if (header_len < 10) + return NErr_NeedMoreData; + + ID3v2::Header header(header_data); + if (!header.Valid()) + return NErr_Error; + + nsid3v2_header_t new_header = (nsid3v2_header_t)new (std::nothrow) ID3v2::Header(header); + if (!new_header) + return NErr_OutOfMemory; + *h = new_header; + return NErr_Success; +} + +int NSID3v2_Header_New(nsid3v2_header_t *h, uint8_t version, uint8_t revision) +{ + nsid3v2_header_t new_header = (nsid3v2_header_t)new (std::nothrow) ID3v2::Header(version, revision); + if (!new_header) + return NErr_OutOfMemory; + *h = new_header; + return NErr_Success; +} + +int NSID3v2_Header_FooterCreate(nsid3v2_header_t *f, const void *footer_data, size_t footer_len) +{ + if (footer_len < 10) + return NErr_NeedMoreData; + + ID3v2::Header footer(footer_data); + if (!footer.FooterValid()) + return NErr_Error; + + nsid3v2_header_t new_header = (nsid3v2_header_t)new (std::nothrow) ID3v2::Header(footer); + if (!new_header) + return NErr_OutOfMemory; + *f = new_header; + return NErr_Success; +} + +int NSID3v2_Header_TagSize(const nsid3v2_header_t h, uint32_t *tag_size) +{ + const ID3v2::Header *header = (const ID3v2::Header *)h; + if (!header) + return NErr_NullPointer; + + *tag_size = header->TagSize(); + return NErr_Success; +} + +int NSID3v2_Header_HasFooter(const nsid3v2_header_t h) +{ + const ID3v2::Header *header = (const ID3v2::Header *)h; + if (!header) + return NErr_NullPointer; + + if (header->HasFooter()) + return NErr_Success; + else + return NErr_False; +} + +int NSID3v2_Header_Destroy(nsid3v2_header_t h) +{ + const ID3v2::Header *header = (const ID3v2::Header *)h; + if (!header) + return NErr_NullPointer; + + delete header; + return NErr_Success; +} + +/* +================== Tag ================== += = +========================================= +*/ + +int NSID3v2_Tag_Create(nsid3v2_tag_t *t, const nsid3v2_header_t h, const void *bytes, size_t bytes_len) +{ + const ID3v2::Header *header = (const ID3v2::Header *)h; + if (!header) + return NErr_NullPointer; + + switch(header->GetVersion()) + { + case 2: + { + ID3v2_2::Tag *tag = new (std::nothrow) ID3v2_2::Tag(*header); + if (!tag) + return NErr_OutOfMemory; + tag->Parse(bytes, bytes_len); + *t = (nsid3v2_tag_t)tag; + return NErr_Success; + } + case 3: + { + ID3v2_3::Tag *tag = new (std::nothrow) ID3v2_3::Tag(*header); + if (!tag) + return NErr_OutOfMemory; + tag->Parse(bytes, bytes_len); + *t = (nsid3v2_tag_t)tag; + return NErr_Success; + } + case 4: + { + ID3v2_4::Tag *tag = new (std::nothrow) ID3v2_4::Tag(*header); + if (!tag) + return NErr_OutOfMemory; + tag->Parse(bytes, bytes_len); + *t = (nsid3v2_tag_t)tag; + return NErr_Success; + } + default: + return NErr_NotImplemented; + } +} + +int NSID3v2_Tag_Destroy(nsid3v2_tag_t t) +{ + ID3v2::Tag *tag = (ID3v2::Tag *)t; + delete tag; + return NErr_Success; +} + +int NSID3v2_Tag_RemoveFrame(nsid3v2_tag_t t, nsid3v2_frame_t f) +{ + ID3v2::Tag *tag = (ID3v2::Tag *)t; + ID3v2::Frame *frame = (ID3v2::Frame *)f; + tag->RemoveFrame(frame); + return NErr_Success; +} + +int NSID3v2_Tag_CreateFrame(nsid3v2_tag_t t, int frame_enum, int flags, nsid3v2_frame_t *f) +{ + ID3v2::Tag *tag = (ID3v2::Tag *)t; + *f = (nsid3v2_frame_t)tag->NewFrame(frame_enum, flags); + return NErr_Success; +} + +int NSID3v2_Tag_AddFrame(nsid3v2_tag_t t, nsid3v2_frame_t f) +{ + ID3v2::Tag *tag = (ID3v2::Tag *)t; + ID3v2::Frame *frame = (ID3v2::Frame *)f; + tag->AddFrame(frame); + return NErr_Success; +} + +int NSID3v2_Tag_GetFrame(const nsid3v2_tag_t t, int frame_enum, nsid3v2_frame_t *frame) +{ + ID3v2::Tag *tag = (ID3v2::Tag *)t; + ID3v2::Frame *found_frame = tag->FindFirstFrame(frame_enum); + if (found_frame) + { + *frame = (nsid3v2_frame_t)found_frame; + return NErr_Success; + } + else + return NErr_Empty; +} + +int NSID3v2_Tag_GetNextFrame(const nsid3v2_tag_t t, const nsid3v2_frame_t f, nsid3v2_frame_t *frame) +{ + ID3v2::Tag *tag = (ID3v2::Tag *)t; + ID3v2::Frame *start_frame = (ID3v2::Frame *)f; + + ID3v2::Frame *found_frame = tag->FindNextFrame(start_frame); + if (found_frame) + { + *frame = (nsid3v2_frame_t)found_frame; + return NErr_Success; + } + else + return NErr_EndOfEnumeration; +} + +int NSID3v2_Frame_Binary_Get(nsid3v2_frame_t f, const void **binary, size_t *length) +{ + ID3v2::Frame *frame = (ID3v2::Frame *)f; + if (!frame) + return NErr_BadParameter; + + return frame->GetData(binary, length); +} + +int NSID3v2_Tag_EnumerateFrame(const nsid3v2_tag_t t, nsid3v2_frame_t p, nsid3v2_frame_t *f) +{ + const ID3v2::Tag *tag = (const ID3v2::Tag *)t; + const ID3v2::Frame *frame = tag->EnumerateFrame((const ID3v2::Frame *)p); + *f = (nsid3v2_frame_t)frame; + if (frame) + { + return NErr_Success; + } + else + { + return NErr_Error; + } +} + +int NSID3v2_Tag_GetInformation(nsid3v2_tag_t t, uint8_t *version, uint8_t *revision, int *flags) +{ + const ID3v2::Tag *tag = (const ID3v2::Tag *)t; + if (!tag) + return NErr_BadParameter; + + *version = tag->GetVersion(); + *revision = tag->GetRevision(); + + int local_flags=0; + if (tag->HasExtendedHeader()) + local_flags |= NSID3V2_TAGFLAG_EXTENDED_HEADER; + + if (tag->Unsynchronised()) + local_flags |= NSID3V2_TAGFLAG_UNSYNCHRONIZED; + if (tag->HasFooter()) + local_flags |= NSID3V2_TAGFLAG_HASFOOTER; + + *flags = local_flags; + + return NErr_Success; +} + +int NSID3v2_Tag_SerializedSize(nsid3v2_tag_t t, uint32_t *length, uint32_t padding_size, int flags) +{ + const ID3v2::Tag *tag = (const ID3v2::Tag *)t; + if (!tag) + return NErr_BadParameter; + + return tag->SerializedSize(length, padding_size, flags); +} + +int NSID3v2_Tag_Serialize(nsid3v2_tag_t t, void *data, uint32_t len, int flags) +{ + const ID3v2::Tag *tag = (const ID3v2::Tag *)t; + if (!tag) + return NErr_BadParameter; + + return tag->Serialize(data, len, flags); +} +/* +================= Frame ================= += = +========================================= +*/ + +int NSID3v2_Frame_GetIdentifier(nsid3v2_frame_t f, const char **identifier) +{ + ID3v2::Frame *frame = (ID3v2::Frame *)f; + if (!frame) + return NErr_BadParameter; + + *identifier = (const char *)frame->GetIdentifier(); + return NErr_Success; +} + + +int NSID3v2_Frame_GetInformation(nsid3v2_frame_t f, int *type, int *flags) +{ + ID3v2::Frame *frame = (ID3v2::Frame *)f; + if (!frame) + return NErr_BadParameter; + + const char *identifier=0; + int ret = NSID3v2_Frame_GetIdentifier(f, &identifier); + if (ret != NErr_Success) + return ret; + + /* TODO: make a method to get the version from the frame */ + + /* ok this is a bit of hack job */ + if (!memcmp(identifier, "TXX", 4) || !memcmp(identifier, "TXXX", 4)) + { + *type = NSID3V2_FRAMETYPE_USERTEXT; + } + else if (!memcmp(identifier, "COM", 4) || !memcmp(identifier, "COMM", 4)) + { + *type = NSID3V2_FRAMETYPE_COMMENTS; + } + else if (identifier[0] == 'T') /* check for text */ + { + *type = NSID3V2_FRAMETYPE_TEXT; + } + else if (!memcmp(identifier, "WXX", 4) || !memcmp(identifier, "WXXX", 4)) + { + *type = NSID3V2_FRAMETYPE_USERURL; + } + else if (identifier[0] == 'W') /* check for URL */ + { + *type = NSID3V2_FRAMETYPE_URL; + } + else if (!memcmp(identifier, "PRIV", 4)) + { + *type = NSID3V2_FRAMETYPE_PRIVATE; + } + else if (!memcmp(identifier, "GEO", 4) || !memcmp(identifier, "GEOB", 4)) + { + *type = NSID3V2_FRAMETYPE_OBJECT; + } + else if (!memcmp(identifier, "POP", 4) || !memcmp(identifier, "POPM", 4)) + { + *type = NSID3V2_FRAMETYPE_POPULARITY; + } + else if (!memcmp(identifier, "PIC", 4) || !memcmp(identifier, "APIC", 4)) + { + *type = NSID3V2_FRAMETYPE_PICTURE; + } + else if (!memcmp(identifier, "UFI", 4) || !memcmp(identifier, "UFID", 4)) + { + *type = NSID3V2_FRAMETYPE_ID; + } + else + { + *type = NSID3V2_FRAMETYPE_UNKNOWN; + } + + if (flags) + { + int local_flags=0; + if (frame->TagAlterPreservation()) + local_flags |= NSID3V2_FRAMEFLAG_TAG_ALTER_PRESERVATION; + if (frame->FileAlterPreservation()) + local_flags |= NSID3V2_FRAMEFLAG_FILE_ALTER_PRESERVATION; + if (frame->Encrypted()) + local_flags |= NSID3V2_FRAMEFLAG_ENCRYPTED; + if (frame->Compressed()) + local_flags |= NSID3V2_FRAMEFLAG_COMPRESSED; + if (frame->Grouped()) + local_flags |= NSID3V2_FRAMEFLAG_GROUPED; + if (frame->ReadOnly()) + local_flags |= NSID3V2_FRAMEFLAG_READONLY; + if (frame->FrameUnsynchronised()) + local_flags |= NSID3V2_FRAMEFLAG_UNSYNCHRONIZED; + if (frame->DataLengthIndicated()) + local_flags |= NSID3V2_FRAMEFLAG_DATALENGTHINDICATED; + *flags = local_flags; + } + + return NErr_Success; +} + diff --git a/Src/replicant/nsid3v2/precomp.h b/Src/replicant/nsid3v2/precomp.h new file mode 100644 index 00000000..48c66866 --- /dev/null +++ b/Src/replicant/nsid3v2/precomp.h @@ -0,0 +1,32 @@ +// +// precomp.h +// nsid3v2 +// + +#include <assert.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "foundation/error.h" +#include "foundation/types.h" + + +#include "nu/ByteReader.h" +#include "nu/ByteWriter.h" + +#include "nu/utf.h" +#include "nx/nxstring.h" + + +#ifdef __cplusplus + +#include <new> +#include "nu/AutoWide.h" +#include "nu/PtrDeque.h" + +#include "nsid3v2/header.h" +#include "nsid3v2/tag.h" + +#endif diff --git a/Src/replicant/nsid3v2/tag.cpp b/Src/replicant/nsid3v2/tag.cpp new file mode 100644 index 00000000..effb99fd --- /dev/null +++ b/Src/replicant/nsid3v2/tag.cpp @@ -0,0 +1,644 @@ +#include "tag.h" +#include "frameheader.h" +#include "frames.h" +#include "foundation/error.h" +#include <string.h> +#include <new> +#include "nsid3v2.h" // for serialize flags + +/* === ID3v2 common === */ +static bool IdentifierMatch(const int8_t *id1, const int8_t *id2) +{ + return !memcmp(id1, id2, 4); +} + + +ID3v2::Tag::Tag(const ID3v2::Header &_header) : Header(_header) +{ +} + +void ID3v2::Tag::RemoveFrame(ID3v2::Frame *frame) +{ + frames.erase(frame); + delete frame; +} + +ID3v2::Frame *ID3v2::Tag::FindFirstFrame(const int8_t *id) const +{ + if (!id) + return 0; + + for (FrameList::const_iterator itr=frames.begin();itr != frames.end();itr++) + { + if (IdentifierMatch(itr->GetIdentifier(), id)) + return *itr; + } + return 0; +} + +ID3v2::Frame *ID3v2::Tag::FindNextFrame(const Frame *frame) const +{ + FrameList::const_iterator itr=frame; + for (itr++;itr != frames.end();itr++) + { + if (IdentifierMatch(itr->GetIdentifier(), frame->GetIdentifier())) + return *itr; + } + return 0; +} + +void ID3v2::Tag::RemoveFrames(const int8_t *id) +{ + // TODO: not exactly the fastest way + Frame *frame; + while (frame = FindFirstFrame(id)) + frames.erase(frame); +} +void ID3v2::Tag::AddFrame(ID3v2::Frame *frame) +{ + frames.push_back(frame); +} + +ID3v2::Frame *ID3v2::Tag::EnumerateFrame(const ID3v2::Frame *position) const +{ + if (!position) + return frames.front(); + else + return (ID3v2::Frame *)position->next; + +} +/* === ID3v2.2 === */ + +ID3v2_2::Tag::Tag(const ID3v2::Header &_header) : ID3v2::Tag(_header), extendedHeader(_header) +{ +} + +ID3v2_2::Tag::~Tag() +{ + frames.deleteAll(); +} + +static inline void Advance(const void *&data, size_t &len, size_t amount) +{ + data = (const uint8_t *)data + amount; + len -= amount; +} + +int ID3v2_2::Tag::Parse(const void *data, size_t len) +{ + /* Is there an extended header? */ + if (Header::HasExtendedHeader()) + { + size_t read=0; + if (extendedHeader.Parse(data, len, &read) != 0) + { + return 1; + } + Advance(data, len, read); + } + + /* Read each frame */ + while (len >= FrameHeader::SIZE) + { + /* if next byte is zero, we've hit the padding area, GTFO */ + if (*(uint8_t *)data == 0x0) + break; + + /* Read frame header first */ + FrameHeader frame_header(*this, data); + Advance(data, len, FrameHeader::SIZE); + + if (!frame_header.IsValid()) + return 1; + + /* read frame data */ + Frame *new_frame = new (std::nothrow) Frame(frame_header); + if (!new_frame) + return NErr_OutOfMemory; + + size_t read=0; + if (new_frame->Parse(data, len, &read) == 0) + { + Advance(data, len, read); + frames.push_back(new_frame); + } + else + { + delete new_frame; + return 1; + } + } + return 0; +} + +int ID3v2_2::Tag::SerializedSize(uint32_t *length, uint32_t padding_size, int flags) const +{ + size_t current_length=0; + + Header new_header(*this); + /* TODO: going to clear this, for now */ + new_header.ClearExtendedHeader(); + switch(flags & Serialize_UnsynchronizeMask) + { + case Serialize_Unsynchronize: + // TODO: + break; + case Serialize_NoUnsynchronize: + new_header.ClearUnsynchronized(); + break; + } + + current_length += Header::SIZE; + + if (new_header.HasExtendedHeader()) + { + // TODO: deal with extended header + } + + for (FrameList::const_iterator itr=frames.begin();itr != frames.end();itr++) + { + uint32_t written; + const ID3v2_2::Frame *frame = (const ID3v2_2::Frame *)*itr; + int ret = frame->SerializedSize(&written, new_header, flags); + if (ret != NErr_Success) + return ret; + current_length += written; + } + + switch(flags & SerializedSize_PaddingMask) + { + case SerializedSize_Padding: + current_length += padding_size; + break; + case SerializedSize_AbsoluteSize: + if (current_length < padding_size) + current_length = padding_size; + break; + case SerializedSize_BlockSize: + { + uint32_t additional = current_length % padding_size; + current_length += additional; + } + break; + } + + *length = current_length; + return NErr_Success; +} + +int ID3v2_2::Tag::Serialize(void *data, uint32_t len, int flags) const +{ + uint8_t *data_itr = (uint8_t *)data; + uint32_t current_length=0; + + // write header. note the passed-in length is guaranteed to be correct, as it was generated by SerializedSize + Header new_header(this, len-10); + /* TODO: going to clear this, for now */ + new_header.ClearExtendedHeader(); + switch(flags & Serialize_UnsynchronizeMask) + { + case Serialize_Unsynchronize: + // TODO: + break; + case Serialize_NoUnsynchronize: + new_header.ClearUnsynchronized(); + break; + } + + new_header.Serialize(data); + + current_length += Header::SIZE; + data_itr += Header::SIZE; + + if (new_header.HasExtendedHeader()) + { + // TODO: write extended header + } + + for (FrameList::const_iterator itr=frames.begin();itr != frames.end();itr++) + { + uint32_t written; + const ID3v2_2::Frame *frame = (const ID3v2_2::Frame *)*itr; + int ret = frame->Serialize((void *)data_itr, &written, new_header, flags); + if (ret != NErr_Success) + return ret; + current_length += written; + data_itr += written; + } + + // write padding + memset(data_itr, 0, len-current_length); + + return NErr_Success; +} + +static bool IdentifierMatch3(const int8_t *id1, const int8_t *id2) +{ + return !memcmp(id1, id2, 3); +} + +ID3v2_2::Frame *ID3v2_2::Tag::FindFirstFrame(int frame_id) const +{ + if (!ValidFrameID(frame_id)) + return 0; + return (ID3v2_2::Frame *)ID3v2::Tag::FindFirstFrame(frame_ids[frame_id].v2); +}; + + +void ID3v2_2::Tag::RemoveFrames(int frame_id) +{ + // TODO: not exactly the fastest way + Frame *frame; + while (frame = FindFirstFrame(frame_id)) + frames.erase(frame); +} + +ID3v2_2::Frame *ID3v2_2::Tag::NewFrame(int frame_id, int flags) const +{ + if (!ValidFrameID(frame_id) || !frame_ids[frame_id].v2) + return 0; + return new (std::nothrow) ID3v2_2::Frame(*this, frame_ids[frame_id].v2, flags); +} + +/* === ID3v2.3 === */ + +ID3v2_3::Tag::Tag(const ID3v2::Header &_header) : ID3v2::Tag(_header), extendedHeader(_header) +{ +} + +ID3v2_3::Tag::~Tag() +{ + frames.deleteAll(); +} + +int ID3v2_3::Tag::Parse(const void *data, size_t len) +{ + /* Is there an extended header? */ + if (Header::HasExtendedHeader()) + { + size_t read=0; + if (extendedHeader.Parse(data, len, &read) != 0) + { + return 1; + } + Advance(data, len, read); + } + + /* Read each frame */ + while (len >= FrameHeader::SIZE) + { + /* if next byte is zero, we've hit the padding area, GTFO */ + if (*(uint8_t *)data == 0x0) + break; + + /* Read frame header first */ + FrameHeader frame_header(*this, data); + Advance(data, len, FrameHeader::SIZE); + + if (!frame_header.IsValid()) + return 1; + + /* read frame data */ + Frame *new_frame = new (std::nothrow) Frame(frame_header); + if (!new_frame) + return NErr_OutOfMemory; + + size_t read=0; + if (new_frame->Parse(data, len, &read) == 0) + { + Advance(data, len, read); + frames.push_back(new_frame); + } + else + { + delete new_frame; + return 1; + } + } + return 0; +} + +int ID3v2_3::Tag::SerializedSize(uint32_t *length, uint32_t padding_size, int flags) const +{ + size_t current_length=0; + + Header new_header(*this); + /* TODO: going to clear this, for now */ + new_header.ClearExtendedHeader(); + switch(flags & Serialize_UnsynchronizeMask) + { + case Serialize_Unsynchronize: + // TODO: + break; + case Serialize_NoUnsynchronize: + new_header.ClearUnsynchronized(); + break; + } + + current_length += Header::SIZE; + + if (new_header.HasExtendedHeader()) + { + // TODO: deal with extended header + } + + for (FrameList::const_iterator itr=frames.begin();itr != frames.end();itr++) + { + uint32_t written; + const ID3v2_3::Frame *frame = (const ID3v2_3::Frame *)*itr; + int ret = frame->SerializedSize(&written, new_header, flags); + if (ret != NErr_Success) + return ret; + current_length += written; + } + + switch(flags & SerializedSize_PaddingMask) + { + case SerializedSize_Padding: + current_length += padding_size; + break; + case SerializedSize_AbsoluteSize: + if (current_length < padding_size) + current_length = padding_size; + break; + case SerializedSize_BlockSize: + { + uint32_t additional = padding_size - (current_length % padding_size); + if (additional == padding_size) + additional=0; + current_length += additional; + } + break; + } + + *length = current_length; + return NErr_Success; +} + +int ID3v2_3::Tag::Serialize(void *data, uint32_t len, int flags) const +{ + uint8_t *data_itr = (uint8_t *)data; + uint32_t current_length=0; + + // write header. note the passed-in length is guaranteed to be correct, as it was generated by SerializedSize + Header new_header(this, len-10); + /* TODO: going to clear this, for now */ + new_header.ClearExtendedHeader(); + switch(flags & Serialize_UnsynchronizeMask) + { + case Serialize_Unsynchronize: + // TODO: + break; + case Serialize_NoUnsynchronize: + new_header.ClearUnsynchronized(); + break; + } + + new_header.Serialize(data); + + current_length += Header::SIZE; + data_itr += Header::SIZE; + + if (new_header.HasExtendedHeader()) + { + // TODO: write extended header + } + + for (FrameList::const_iterator itr=frames.begin();itr != frames.end();itr++) + { + uint32_t written; + const ID3v2_3::Frame *frame = (const ID3v2_3::Frame *)*itr; + int ret = frame->Serialize((void *)data_itr, &written, new_header, flags); + if (ret != NErr_Success) + return ret; + current_length += written; + data_itr += written; + } + + // write padding + memset(data_itr, 0, len-current_length); + + return NErr_Success; +} + +ID3v2_3::Frame *ID3v2_3::Tag::FindFirstFrame(int frame_id) const +{ + if (!ValidFrameID(frame_id)) + return 0; + return (ID3v2_3::Frame *)ID3v2::Tag::FindFirstFrame(frame_ids[frame_id].v3); +}; + +void ID3v2_3::Tag::RemoveFrames(int frame_id) +{ + // TODO: not exactly the fastest way + Frame *frame; + while (frame = FindFirstFrame(frame_id)) + frames.erase(frame); +} + +ID3v2_3::Frame *ID3v2_3::Tag::NewFrame(int frame_id, int flags) const +{ + if (!ValidFrameID(frame_id) || !frame_ids[frame_id].v3) + return 0; + return new (std::nothrow) ID3v2_3::Frame(*this, frame_ids[frame_id].v3, flags); +} + +/* === ID3v2.4 === */ +ID3v2_4::Tag::Tag(const ID3v2::Header &_header) : ID3v2::Tag(_header), extendedHeader(_header) +{ +} + +ID3v2_4::Tag::~Tag() +{ + frames.deleteAll(); +} + +int ID3v2_4::Tag::Parse(const void *data, size_t len) +{ + /* Is there an extended header? */ + if (Header::HasExtendedHeader()) + { + size_t read=0; + if (extendedHeader.Parse(data, len, &read) != 0) + { + return 1; + } + Advance(data, len, read); + } + + /* Read each frame */ + while (len >= FrameHeader::SIZE) + { + /* if next byte is zero, we've hit the padding area, GTFO */ + if (*(uint8_t *)data == 0x0) + break; + + /* Read frame header first */ + FrameHeader frame_header(*this, data); + Advance(data, len, FrameHeader::SIZE); + + if (!frame_header.IsValid()) + return 1; + + /* read frame data */ + Frame *new_frame = new (std::nothrow) Frame(frame_header); + if (!new_frame) + return NErr_OutOfMemory; + + size_t read=0; + if (new_frame->Parse(data, len, &read) == 0) + { + Advance(data, len, read); + frames.push_back(new_frame); + } + else + { + delete new_frame; + return 1; + } + } + return 0; +} + +int ID3v2_4::Tag::SerializedSize(uint32_t *length, uint32_t padding_size, int flags) const +{ + size_t current_length=0; + + Header new_header(*this); + /* TODO: going to clear this, for now */ + new_header.ClearExtendedHeader(); + switch(flags & Serialize_UnsynchronizeMask) + { + case Serialize_Unsynchronize: + // TODO: + break; + case Serialize_NoUnsynchronize: + new_header.ClearUnsynchronized(); + break; + } + + current_length += Header::SIZE; + + if (new_header.HasExtendedHeader()) + { + // TODO: deal with extended header + } + + for (FrameList::const_iterator itr=frames.begin();itr != frames.end();itr++) + { + uint32_t written; + const ID3v2_4::Frame *frame = (const ID3v2_4::Frame *)*itr; + int ret = frame->SerializedSize(&written, new_header, flags); + if (ret != NErr_Success) + return ret; + current_length += written; + } + + switch(flags & SerializedSize_PaddingMask) + { + case 0: + /* we can only write a footer if there is no padding */ + if (new_header.FooterValid() || new_header.HasFooter()) + { + current_length += Header::SIZE; + } + break; + case SerializedSize_Padding: + current_length += padding_size; + break; + case SerializedSize_AbsoluteSize: + if (current_length < padding_size) + current_length = padding_size; + break; + case SerializedSize_BlockSize: + { + uint32_t additional = current_length % padding_size; + current_length += additional; + } + break; + } + + *length = current_length; + return NErr_Success; +} + +int ID3v2_4::Tag::Serialize(void *data, uint32_t len, int flags) const +{ + uint8_t *data_itr = (uint8_t *)data; + uint32_t current_length=0; + + // write header. note the passed-in length is guaranteed to be correct, as it was generated by SerializedSize + bool write_footer=false; + if ((flags & SerializedSize_PaddingMask) == 0 && (FooterValid() || HasFooter())) + write_footer=true; + + Header new_header(this, write_footer?(len-20):(len-10)); + new_header.SetFooter(write_footer); + + /* TODO: going to clear this, for now */ + new_header.ClearExtendedHeader(); + switch(flags & Serialize_UnsynchronizeMask) + { + case Serialize_Unsynchronize: + // TODO: + break; + case Serialize_NoUnsynchronize: + new_header.ClearUnsynchronized(); + break; + } + + new_header.SerializeAsHeader(data); + + current_length += Header::SIZE; + data_itr += Header::SIZE; + + if (new_header.HasExtendedHeader()) + { + // TODO: write extended header + } + + for (FrameList::const_iterator itr=frames.begin();itr != frames.end();itr++) + { + uint32_t written; + const ID3v2_4::Frame *frame = (const ID3v2_4::Frame *)*itr; + int ret = frame->Serialize((void *)data_itr, &written, new_header, flags); + if (ret != NErr_Success) + return ret; + current_length += written; + data_itr += written; + } + + if (write_footer) + { + new_header.SerializeAsFooter(data_itr); + current_length += Header::SIZE; + data_itr += Header::SIZE; + } + + // write padding + memset(data_itr, 0, len-current_length); + + return NErr_Success; +} + +ID3v2_4::Frame *ID3v2_4::Tag::FindFirstFrame(int frame_id) const +{ + if (!ValidFrameID(frame_id)) + return 0; + return (ID3v2_4::Frame *)ID3v2::Tag::FindFirstFrame(frame_ids[frame_id].v4); +}; + +void ID3v2_4::Tag::RemoveFrames(int frame_id) +{ + // TODO: not exactly the fastest way + Frame *frame; + while (frame = FindFirstFrame(frame_id)) + frames.erase(frame); +} + +ID3v2_4::Frame *ID3v2_4::Tag::NewFrame(int frame_id, int flags) const +{ + if (!ValidFrameID(frame_id) || !frame_ids[frame_id].v4) + return 0; + return new (std::nothrow) ID3v2_4::Frame(*this, frame_ids[frame_id].v4, flags); +} diff --git a/Src/replicant/nsid3v2/tag.h b/Src/replicant/nsid3v2/tag.h new file mode 100644 index 00000000..0f661a4a --- /dev/null +++ b/Src/replicant/nsid3v2/tag.h @@ -0,0 +1,106 @@ +#pragma once + +#include "header.h" +#include "nu/PtrDeque.h" +#include "frame.h" +#include "extendedheader.h" + +/* benski> random thoughts + +Frames are stored as raw bytes. Users of this library will have to parse/encode +on their own. But we'll supply a helper library for that. + +new frames must be created from the tag object (not standalone "new ID3v2::Frame") +so that revision & version information (and any relevant tag-wide flags) can be inherited +*/ +namespace ID3v2 +{ + class Tag : public ID3v2::Header + { + public: + Tag(const ID3v2::Header &header); + virtual ~Tag() {} + /* finds the first frame with the desired identifier. */ + ID3v2::Frame *FindFirstFrame(const int8_t *id) const; + /* finds the next frame with the same identifier as the passed in frame + "frame" parameter MUST be a frame returned from the same Tag object! */ + ID3v2::Frame *FindNextFrame(const ID3v2::Frame *frame) const; + void RemoveFrame(ID3v2::Frame *frame); + void RemoveFrames(const int8_t *id); + void AddFrame(ID3v2::Frame *frame); + virtual int Parse(const void *data, size_t len)=0; + virtual int SerializedSize(uint32_t *length, uint32_t padding_size, int flags) const=0; + // tag will be padded up to length. use SerializedSize() to retrieve the length to use! + virtual int Serialize(void *data, uint32_t len, int flags) const=0; + virtual ID3v2::Frame *FindFirstFrame(int frame_id) const = 0; + virtual void RemoveFrames(int frame_id)=0; + virtual ID3v2::Frame *NewFrame(int frame_id, int flags) const=0; + ID3v2::Frame *EnumerateFrame(const ID3v2::Frame *position) const; + protected: + typedef nu::PtrDeque<ID3v2::Frame> FrameList; + nu::PtrDeque<ID3v2::Frame> frames; + }; +} +namespace ID3v2_2 +{ + class Tag : public ID3v2::Tag + { + public: + Tag(const ID3v2::Header &header); + ~Tag(); + int Parse(const void *data, size_t len); + int SerializedSize(uint32_t *length, uint32_t padding_size, int flags) const; + int Serialize(void *data, uint32_t len, int flags) const; + /* finds the first frame with the desired identifier. */ + ID3v2_2::Frame *FindFirstFrame(int frame_id) const; + /* finds the next frame with the same identifier as the passed in frame + "frame" parameter MUST be a frame returned from the same Tag object! */ + void RemoveFrames(int frame_id); + ID3v2_2::Frame *NewFrame(int frame_id, int flags) const; + private: + ID3v2_3::ExtendedHeader extendedHeader; + }; +} + +namespace ID3v2_3 +{ + class Tag : public ID3v2::Tag + { + public: + Tag(const ID3v2::Header &header); + ~Tag(); + int Parse(const void *data, size_t len); + int SerializedSize(uint32_t *length, uint32_t padding_size, int flags) const; + int Serialize(void *data, uint32_t len, int flags) const; + /* finds the first frame with the desired identifier. */ + ID3v2_3::Frame *FindFirstFrame(int frame_id) const; + + void RemoveFrames(int frame_id); + ID3v2_3::Frame *NewFrame(int frame_id, int flags) const; + private: + ID3v2_3::ExtendedHeader extendedHeader; + }; +} + +namespace ID3v2_4 +{ + class Tag : public ID3v2::Tag + { + public: + Tag(const ID3v2::Header &header); + ~Tag(); + int Parse(const void *data, size_t len); + int SerializedSize(uint32_t *length, uint32_t padding_size, int flags) const; + int Serialize(void *data, uint32_t len, int flags) const; + /* finds the first frame with the desired identifier. */ + ID3v2_4::Frame *FindFirstFrame(int frame_id) const; + /* finds the next frame with the same identifier as the passed in frame + "frame" parameter MUST be a frame returned from the same Tag object! */ + ID3v2::Frame *FindNextFrame(const ID3v2::Frame *frame) const { return FindNextFrame((const Frame *)frame); } + + void RemoveFrames(int frame_id); + ID3v2_4::Frame *NewFrame(int frame_id, int flags) const; + private: + ID3v2_4::ExtendedHeader extendedHeader; + }; +} diff --git a/Src/replicant/nsid3v2/util.cpp b/Src/replicant/nsid3v2/util.cpp new file mode 100644 index 00000000..21e09d37 --- /dev/null +++ b/Src/replicant/nsid3v2/util.cpp @@ -0,0 +1,161 @@ +#include "util.h" + +uint32_t ID3v2::Util::Int28To32(uint32_t val) +{ + // TODO: big endian safe? + uint32_t ret; + + ret = (val & 0x7FU); + val >>= 1; + ret |= (val & 0x3F80U); + val >>= 1; + ret |= (val & 0x1FC000U); + val >>= 1; + ret |= (val & 0xFE00000U); + + return ret; + /* + uint8_t *bytes = (uint8_t *)&ret; + const uint8_t *value = (const uint8_t *)&val; + + ret = (value[0] << 21) + (value[1] << 14) + (value[2] << 7) + (value[3]); + + // for (size_t i=0;i<sizeof(uint32_t);i++ ) + // value[sizeof(uint32_t)-1-i]=(uint8_t)(val>>(i*8)) & 0xFF; + + return ret; + */ +} + +uint32_t ID3v2::Util::Int32To28(uint32_t val) +{ + // TODO: big endian safe? + uint32_t ret; + ret = (val & 0x7FU); + ret |= (val & 0x3F80U) << 1; + ret |= (val & 0x1FC000U) << 2; + ret |= (val & 0xFE00000U) << 3; + + return ret; +} + +size_t ID3v2::Util::UnsynchroniseTo(void *_output, const void *_input, size_t bytes) +{ + uint8_t *output = (uint8_t *)_output; + const uint8_t *input = (const uint8_t *)_input; + size_t bytes_read = 0; + while (bytes) + { + if (input[0] == 0xFF && input[1] == 0) + { + *output++ = 0xFF; + input+=2; + bytes_read+=2; + bytes--; + } + else + { + *output++=*input++; + bytes_read++; + bytes--; + } + } + return bytes_read; +} + +size_t ID3v2::Util::UnsynchronisedInputSize(const void *data, size_t output_bytes) +{ + const uint8_t *input = (const uint8_t *)data; + size_t bytes_read = 0; + while (output_bytes) + { + if (input[0] == 0xFF && input[1] == 0) + { + input+=2; + bytes_read+=2; + output_bytes--; + } + else + { + input++; + bytes_read++; + output_bytes--; + } + } + return bytes_read; +} + +size_t ID3v2::Util::UnsynchronisedOutputSize(const void *data, size_t input_bytes) +{ + const uint8_t *input = (const uint8_t *)data; + size_t bytes_written = 0; + while (input_bytes) + { + if (input[0] == 0xFF && input_bytes > 1 && input[1] == 0) + { + input+=2; + bytes_written++; + input_bytes-=2; + } + else + { + input++; + bytes_written++; + input_bytes--; + } + } + return bytes_written; +} + +// returns output bytes used +size_t ID3v2::Util::SynchroniseTo(void *_output, const void *data, size_t bytes) +{ + uint8_t *output = (uint8_t *)_output; + const uint8_t *input = (const uint8_t *)data; + size_t bytes_needed = 0; + while (bytes) + { + *output++=*input; + bytes_needed++; + if (*input++ == 0xFF) + { + if (bytes == 1) + { + // if this is the last byte, we need to make room for an extra 0 + *output = 0; + return bytes_needed + 1; + } + else if ((*input & 0xE0) == 0xE0 || *input == 0) + { + *output++ = 0; + bytes_needed++; + } + } + bytes--; + } + return bytes_needed; +} + +size_t ID3v2::Util::SynchronisedSize(const void *data, size_t bytes) +{ + const uint8_t *input = (const uint8_t *)data; + size_t bytes_needed = 0; + while (bytes) + { + bytes_needed++; + if (*input++ == 0xFF) + { + if (bytes == 1) + { + // if this is the last byte, we need to make room for an extra 0 + return bytes_needed + 1; + } + else if ((*input & 0xE0) == 0xE0 || *input == 0) + { + bytes_needed++; + } + } + bytes--; + } + return bytes_needed; +} diff --git a/Src/replicant/nsid3v2/util.h b/Src/replicant/nsid3v2/util.h new file mode 100644 index 00000000..11ca1180 --- /dev/null +++ b/Src/replicant/nsid3v2/util.h @@ -0,0 +1,28 @@ +#pragma once +#include "foundation/types.h" + +namespace ID3v2 +{ + namespace Util + { + /* pass a value you read as if it was a 32bit integer */ + uint32_t Int28To32(uint32_t val); + + uint32_t Int32To28(uint32_t val); + + // returns input bytes used + size_t UnsynchroniseTo(void *output, const void *input, size_t output_bytes); + + // returns number of real bytes required to read 'bytes' data + size_t UnsynchronisedInputSize(const void *input, size_t output_bytes); + + + size_t UnsynchronisedOutputSize(const void *input, size_t input_bytes); + + // returns output bytes used + size_t SynchroniseTo(void *output, const void *input, size_t bytes); + + // returns number of bytes required to store synchronized version of 'data' (bytes long) + size_t SynchronisedSize(const void *data, size_t bytes); + } +} diff --git a/Src/replicant/nsid3v2/values.cpp b/Src/replicant/nsid3v2/values.cpp new file mode 100644 index 00000000..843cec3f --- /dev/null +++ b/Src/replicant/nsid3v2/values.cpp @@ -0,0 +1,49 @@ +#include "values.h" + +uint8_t ID3v2::Values::ValidHeaderMask(uint8_t version, uint8_t revision) +{ + switch(version) + { + case 2: + if (revision == 1) + return 0xE0; + else + return 0xC0; + case 4: + return 0xF0; /* 11110000 */ + case 3: + return 0xE0; /* 11100000 */ + default: + return 0; + } +} + +bool ID3v2::Values::KnownVersion(uint8_t version, uint8_t revision) +{ + if (version > Values::MAX_VERSION) + return false; + + if (version < Values::MIN_VERSION) + return false; + + return true; +} + +uint8_t ID3v2::Values::ExtendedHeaderFlag(uint8_t version, uint8_t revision) +{ + switch(version) + { + case 2: + if (revision == 1) + return (1 << 6); + else + return 0; + + case 3: + case 4: + return (1 << 6); + + default: + return 0; + } +} diff --git a/Src/replicant/nsid3v2/values.h b/Src/replicant/nsid3v2/values.h new file mode 100644 index 00000000..ee6f2e50 --- /dev/null +++ b/Src/replicant/nsid3v2/values.h @@ -0,0 +1,22 @@ +#pragma once +#include "foundation/types.h" + +/* benski> +This is where we encapsulate all data. +Everything is implemented by a function that accepts a version and revision. +*/ +namespace ID3v2 +{ + namespace Values + { + enum + { + MIN_VERSION = 2, + MAX_VERSION = 4, + }; + + bool KnownVersion(uint8_t version, uint8_t revision); + uint8_t ValidHeaderMask(uint8_t version, uint8_t revision); + uint8_t ExtendedHeaderFlag(uint8_t version, uint8_t revision); + } +}; diff --git a/Src/replicant/nsid3v2/windows/frame_apic.cpp b/Src/replicant/nsid3v2/windows/frame_apic.cpp new file mode 100644 index 00000000..0e937dee --- /dev/null +++ b/Src/replicant/nsid3v2/windows/frame_apic.cpp @@ -0,0 +1,170 @@ +#include "nsid3v2.h" +#include "nsid3v2/header.h" +#include "nsid3v2/tag.h" +#include "nsid3v2/frame_utils.h" +#include <api/memmgr/api_memmgr.h> +#include <strsafe.h> + +struct ParsedPicture +{ + uint8_t encoding; // 0 - iso-8859-1, 1 - UTF16LE, 2 - UTF16BE, 3 - UTF8 + const char *mime_type; + size_t mime_cch; + uint8_t picture_type; + union + { + const char *as8; + const wchar_t *as16; + } description_data; + size_t description_cch; + const void *picture_data; + size_t picture_bytes; +}; + +static int ParsePicture(const void *data, size_t data_len, ParsedPicture &parsed) +{ + const uint8_t *data8 = (const uint8_t *)data; + parsed.encoding = data8[0]; + parsed.mime_type = (const char *)&data8[1]; + data_len--; + ParseDescription(parsed.mime_type, data_len, parsed.mime_cch); + parsed.picture_type = data8[2+parsed.mime_cch]; + data_len--; + + switch(parsed.encoding) + { + case 0: // ISO-8859-1 + ParseDescription(parsed.description_data.as8, parsed.description_cch, data_len); + parsed.picture_data = parsed.description_data.as8 + parsed.description_cch + 1; + parsed.picture_bytes = data_len; + return NErr_Success; + case 1: // UTF-16 + ParseDescription(parsed.description_data.as16, parsed.description_cch, data_len, parsed.encoding); + parsed.picture_data = parsed.description_data.as8 + parsed.description_cch + 1; + parsed.picture_bytes = data_len; + return NErr_Success; + + case 2: // UTF-16 BE + ParseDescription(parsed.description_data.as16, parsed.description_cch, data_len, parsed.encoding); + parsed.picture_data = parsed.description_data.as8 + parsed.description_cch + 1; + parsed.picture_bytes = data_len; + return NErr_Success; + case 3: // UTF-8 + ParseDescription(parsed.description_data.as8, parsed.description_cch, data_len); + parsed.picture_data = parsed.description_data.as8 + parsed.description_cch + 1; + parsed.picture_bytes = data_len; + return NErr_Success; + } + return NErr_NotImplemented; +} + +int NSID3v2_Tag_APIC_GetPicture(const nsid3v2_tag_t t, uint8_t picture_type, void *_memmgr, wchar_t **mime_type, void **picture_data, size_t *picture_bytes) +{ + api_memmgr *memmgr = (api_memmgr *)_memmgr; + const ID3v2::Tag *tag = (const ID3v2::Tag *)t; + const ID3v2::Frame *frame = tag->FindFirstFrame(NSID3V2_FRAME_PICTURE); + while (frame) + { + const void *data; + size_t data_len; + ParsedPicture parsed; + if (frame->GetData(&data, &data_len) == NErr_Success && data_len > 0 && ParsePicture(data, data_len, parsed) == NErr_Success && parsed.picture_type == picture_type) + { + const char *type = strchr(parsed.mime_type, '/'); + + if (type && *type) + { + type++; + int typelen = MultiByteToWideChar(CP_ACP, 0, type, -1, 0, 0); + *mime_type = (wchar_t *)memmgr->sysMalloc(typelen * sizeof(wchar_t)); + MultiByteToWideChar(CP_ACP, 0, type, -1, *mime_type, typelen); + } + else + *mime_type = 0; // unknown! + + *picture_bytes = parsed.picture_bytes; + *picture_data = memmgr->sysMalloc(parsed.picture_bytes); + memcpy(*picture_data, parsed.picture_data, parsed.picture_bytes); + return NErr_Success; + } + frame = tag->FindNextFrame(frame); + } + + return NErr_Error; +} + + +int NSID3v2_Tag_APIC_GetFirstPicture(const nsid3v2_tag_t t, void *_memmgr, wchar_t **mime_type, void **picture_data, size_t *picture_bytes) +{ + api_memmgr *memmgr = (api_memmgr *)_memmgr; + const ID3v2::Tag *tag = (const ID3v2::Tag *)t; + const ID3v2::Frame *frame = tag->FindFirstFrame(NSID3V2_FRAME_PICTURE); + while (frame) + { + const void *data; + size_t data_len; + ParsedPicture parsed; + if (frame->GetData(&data, &data_len) == NErr_Success && data_len > 0 && ParsePicture(data, data_len, parsed) == NErr_Success) + { + const char *type = strchr(parsed.mime_type, '/'); + + if (type && *type) + { + type++; + int typelen = MultiByteToWideChar(CP_ACP, 0, type, -1, 0, 0); + *mime_type = (wchar_t *)memmgr->sysMalloc(typelen * sizeof(wchar_t)); + MultiByteToWideChar(CP_ACP, 0, type, -1, *mime_type, typelen); + } + else + *mime_type = 0; // unknown! + + *picture_bytes = parsed.picture_bytes; + *picture_data = memmgr->sysMalloc(parsed.picture_bytes); + memcpy(*picture_data, parsed.picture_data, parsed.picture_bytes); + return NErr_Success; + } + frame = tag->FindNextFrame(frame); + } + + return NErr_Error; +} + +int NSID3v2_Tag_APIC_GetFrame(const nsid3v2_tag_t t, uint8_t picture_type, nsid3v2_frame_t *f) +{ + const ID3v2::Tag *tag = (const ID3v2::Tag *)t; + const ID3v2::Frame *frame = tag->FindFirstFrame(NSID3V2_FRAME_PICTURE); + while (frame) + { + const void *data; + size_t data_len; + ParsedPicture parsed; + if (frame->GetData(&data, &data_len) == NErr_Success && data_len > 0 && ParsePicture(data, data_len, parsed) == NErr_Success && parsed.picture_type == picture_type) + { + *f = (nsid3v2_frame_t)frame; + return NErr_Success; + } + frame = tag->FindNextFrame(frame); + } + + return NErr_Error; +} + +int NSID3v2_Tag_APIC_GetFirstFrame(const nsid3v2_tag_t t, nsid3v2_frame_t *f) +{ + const ID3v2::Tag *tag = (const ID3v2::Tag *)t; + const ID3v2::Frame *frame = tag->FindFirstFrame(NSID3V2_FRAME_PICTURE); + while (frame) + { + const void *data; + size_t data_len; + ParsedPicture parsed; + if (frame->GetData(&data, &data_len) == NErr_Success && data_len > 0 && ParsePicture(data, data_len, parsed) == NErr_Success) + { + *f = (nsid3v2_frame_t)frame; + return NErr_Success; + } + frame = tag->FindNextFrame(frame); + } + + return NErr_Error; +}
\ No newline at end of file diff --git a/Src/replicant/nsid3v2/windows/frame_comments.cpp b/Src/replicant/nsid3v2/windows/frame_comments.cpp new file mode 100644 index 00000000..4e1beb5f --- /dev/null +++ b/Src/replicant/nsid3v2/windows/frame_comments.cpp @@ -0,0 +1,228 @@ +#include "nsid3v2/nsid3v2.h" +#include "nsid3v2/header.h" +#include "nsid3v2/tag.h" +#include "nsid3v2/frame_utils.h" +#include "nu/AutoWide.h" +#include <strsafe.h> + +struct ParsedComments +{ + const char *language; + uint8_t description_encoding; // 0 - iso-8859-1, 1 - UTF16LE, 2 - UTF16BE, 3 - UTF8 + union + { + const char *as8; + const wchar_t *as16; + } description_data; + size_t description_cch; + uint8_t value_encoding; // 0 - iso-8859-1, 1 - UTF16LE, 2 - UTF16BE, 3 - UTF8 + union + { + const char *as8; + const wchar_t *as16; + } value_data; + size_t value_cch; +}; + +static int ParseComments(const void *data, size_t data_len, ParsedComments &parsed) +{ + int ret; + if (data_len < 5) + return NErr_Error; + + const uint8_t *data8 = (const uint8_t *)data; + switch(data8[0]) + { + case 0: // ISO-8859-1 + parsed.description_encoding = 0; + parsed.language = (const char *)&data8[1]; + + parsed.value_encoding = 0; + parsed.description_data.as8 = (const char *)&data8[4]; + data_len-=4; + + ret = ParseDescription(parsed.description_data.as8, data_len, parsed.description_cch); + if (ret != NErr_Success) + return ret; + + parsed.value_data.as8 = parsed.description_data.as8 + 2 + parsed.description_cch; + parsed.value_cch = data_len; + + return NErr_Success; + case 1: // UTF-16 + parsed.language = (const char *)&data8[1]; + parsed.description_encoding=1; + parsed.description_data.as16 = (const wchar_t *)&data8[4]; + data_len-=4; + + ret = ParseDescription(parsed.description_data.as16, data_len, parsed.description_cch, parsed.description_encoding); + if (ret != NErr_Success) + return ret; + + parsed.value_data.as16 = parsed.description_data.as16 + 2 + parsed.description_cch; + parsed.value_cch = data_len/2; + + if (parsed.value_cch && parsed.value_data.as16[0] == 0xFFFE) + { + parsed.value_encoding=2; + parsed.value_data.as16++; + parsed.value_cch--; + } + else if (parsed.value_cch && parsed.value_data.as16[0] == 0xFEFF) + { + parsed.value_encoding=1; + parsed.value_data.as16++; + parsed.value_cch--; + } + else + { + parsed.value_encoding=1; + } + + return NErr_Success; + + case 2: // UTF-16 BE + parsed.language = (const char *)&data8[1]; + parsed.description_encoding=2; + parsed.description_data.as16 = (const wchar_t *)&data8[4]; + data_len-=3; + + ret = ParseDescription(parsed.description_data.as16, data_len, parsed.description_cch, parsed.description_encoding); + if (ret != NErr_Success) + return ret; + + parsed.value_data.as16 = parsed.description_data.as16 + 2 + parsed.description_cch; + parsed.value_cch = data_len/2; + parsed.value_encoding=2; + + return NErr_Success; + case 3: // UTF-8 + parsed.description_encoding = 3; + parsed.language = (const char *)&data8[1]; + parsed.value_encoding = 3; + parsed.description_data.as8 = (const char *)&data8[4]; + data_len-=4; + + ret = ParseDescription(parsed.description_data.as8, data_len, parsed.description_cch); + if (ret != NErr_Success) + return ret; + + // check for UTF-8 BOM + if (parsed.description_cch >= 3 && parsed.description_data.as8[0] == 0xEF && parsed.description_data.as8[1] == 0xBB && parsed.description_data.as8[2] == 0xBF) + { + parsed.description_data.as8+=3; + parsed.description_cch-=3; + } + + if (!data_len) + return NErr_Error; + + parsed.value_data.as8 = parsed.description_data.as8 + 2 + parsed.description_cch; + parsed.value_cch = data_len; + + // check for UTF-8 BOM + if (parsed.value_cch >= 3 && parsed.value_data.as8[0] == 0xEF && parsed.value_data.as8[1] == 0xBB && parsed.value_data.as8[2] == 0xBF) + { + parsed.value_data.as8+=3; + parsed.value_cch-=3; + } + + return NErr_Success; + } + return NErr_NotImplemented; +} + + +int NSID3v2_Tag_Comments_GetUTF16(const nsid3v2_tag_t t, const wchar_t *description, wchar_t *buf, size_t buf_cch, int text_flags) +{ + const ID3v2::Tag *tag = (const ID3v2::Tag *)t; + const ID3v2::Frame *frame = tag->FindFirstFrame(NSID3V2_FRAME_COMMENTS); + while (frame) + { + const void *data; + size_t data_len; + ParsedComments parsed; + if (frame->GetData(&data, &data_len) == NErr_Success && data_len > 0 && ParseComments(data, data_len, parsed) == NErr_Success) + { + // see if our description matches + switch(parsed.description_encoding) + { + case 0: // ISO-8859-1 + { + UINT codepage = (text_flags & NSID3V2_TEXT_SYSTEM)?28591:CP_ACP; + if (CompareStringW(MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT), NORM_IGNORECASE, AutoWide(parsed.description_data.as8, codepage), -1, description, -1) != CSTR_EQUAL) + goto next_frame; + } + break; + case 1: + if (CompareStringW(MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT), NORM_IGNORECASE, parsed.description_data.as16, -1, description, -1) != CSTR_EQUAL) + goto next_frame; + break; + case 2: + // TODO! + goto next_frame; + break; + case 3: + if (CompareStringW(MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT), NORM_IGNORECASE, AutoWide(parsed.description_data.as8, CP_UTF8), -1, description, -1) != CSTR_EQUAL) + goto next_frame; + break; + } + + switch(parsed.value_encoding) + { + case 0: // ISO-8859-1 + { + UINT codepage = (text_flags & NSID3V2_TEXT_SYSTEM)?28591:CP_ACP; + int utf16_len = MultiByteToWideChar(codepage, 0, parsed.value_data.as8, parsed.value_cch, 0, 0); + + if (utf16_len) + { + utf16_len = MultiByteToWideChar(codepage, 0, parsed.value_data.as8, parsed.value_cch, buf, utf16_len-1); + buf[utf16_len]=0; + } + else + { + buf[0]=0; + } + } + return NErr_Success; + case 1: // UTF-16 + StringCchCopyNW(buf, buf_cch, parsed.value_data.as16, parsed.value_cch); + return NErr_Success; + case 2: // UTF-16BE + { + size_t toCopy = buf_cch-1; + if (parsed.value_cch < toCopy) + toCopy = parsed.value_cch; + for (size_t i=0;i<toCopy;i++) + { + buf[i] = ((parsed.value_data.as16[i] >> 8) & 0xFF) | (((parsed.value_data.as16[i]) & 0xFF) << 8); + } + buf[toCopy]=0; + } + return NErr_Success; + case 3: // UTF-8 + { + int utf16_len = MultiByteToWideChar(CP_UTF8, 0, parsed.value_data.as8, parsed.value_cch, 0, 0); + + if (utf16_len) + { + utf16_len = MultiByteToWideChar(CP_UTF8, 0, parsed.value_data.as8, parsed.value_cch, buf, utf16_len-1); + buf[utf16_len]=0; + } + else + { + buf[0]=0; + } + } + return NErr_Success; + } + +next_frame: + frame = tag->FindNextFrame(frame); + } + } + + return NErr_Error; +} + diff --git a/Src/replicant/nsid3v2/windows/nsid3v2.cpp b/Src/replicant/nsid3v2/windows/nsid3v2.cpp new file mode 100644 index 00000000..f132be8e --- /dev/null +++ b/Src/replicant/nsid3v2/windows/nsid3v2.cpp @@ -0,0 +1,73 @@ +#include "nsid3v2/nsid3v2.h" +#include "nsid3v2/header.h" +#include "nsid3v2/tag.h" +#include <new> +#include <strsafe.h> + + +/* +================== Tag ================== += = +========================================= +*/ +#if 0 // save for reference +int NSID3v2_Frame_Text_SetUTF16(nsid3v2_frame_t f, const wchar_t *value) +{ + ID3v2::Frame *frame = (ID3v2::Frame *)f; + size_t len = wcslen(value); + size_t bytes = len * 2 + 1; // leave 1 byte for encoding + if (bytes < len) // woops, integer overflow + return NErr_Error; + + size_t datalen; + void *data; + int ret = frame->NewData(bytes, &data, &datalen); + if (ret == NErr_Success) + { + uint8_t *data8 = (uint8_t *)data; + data8[0]=1; // set encoding to UTF-16 + memcpy(data8+1, value, len*2); + } + return ret; +} + +int NSID3v2_Frame_UserText_SetUTF16(nsid3v2_frame_t f, const wchar_t *description, const wchar_t *value) +{ + ID3v2::Frame *frame = (ID3v2::Frame *)f; + size_t value_len = wcslen(value); + size_t description_len = wcslen(description); + size_t bytes = (value_len + description_len + 1) * 2 + 1; // leave 1 byte for encoding + + size_t datalen; + void *data; + int ret = frame->NewData(bytes, &data, &datalen); + if (ret == NErr_Success) + { + uint8_t *data8 = (uint8_t *)data; + data8[0]=1; // set encoding to UTF-16 + wcscpy((wchar_t *)(data8+1), description); // guaranteed to be room + memcpy(data8+1+1+description_len*2, value, value_len*2); + } + return ret; +} + +int NSID3v2_Frame_UserText_SetLatin(nsid3v2_frame_t f, const char *description, const char *value) +{ + ID3v2::Frame *frame = (ID3v2::Frame *)f; + size_t value_len = strlen(value); + size_t description_len = strlen(description); + size_t bytes = (value_len + description_len + 1) + 1; // leave 1 byte for encoding + + size_t datalen; + void *data; + int ret = frame->NewData(bytes, &data, &datalen); + if (ret == NErr_Success) + { + uint8_t *data8 = (uint8_t *)data; + data8[0]=0; // set encoding to ISO-8859-1 + strcpy((char *)(data8+1), description); // guaranteed to be room + memcpy(data8+1+1+description_len, value, value_len); + } + return ret; +} +#endif diff --git a/Src/replicant/nsid3v2/windows/nsid3v2.h b/Src/replicant/nsid3v2/windows/nsid3v2.h new file mode 100644 index 00000000..345f74ca --- /dev/null +++ b/Src/replicant/nsid3v2/windows/nsid3v2.h @@ -0,0 +1,20 @@ +#pragma once + +#include "foundation/types.h" +#include "foundation/export.h" +#include "foundation/error.h" +#include "nx/nxstring.h" + + +#ifdef __cplusplus +extern "C" { +#endif + +#define NSID3V2_EXPORT + typedef struct nsid3v2_header_struct_t { } *nsid3v2_header_t; + typedef struct nsid3v2_tag_struct_t { } *nsid3v2_tag_t; + typedef struct nsid3v2_frame_struct_t { } *nsid3v2_frame_t; + +#ifdef __cplusplus +} +#endif
\ No newline at end of file |