Initial community commit

This commit is contained in:
Jef 2024-09-24 14:54:57 +02:00
parent 537bcbc862
commit fc06254474
16440 changed files with 4239995 additions and 2 deletions

View file

@ -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;
}

View file

@ -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;
};
}

View file

@ -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(&current_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(&current_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();
}

View file

@ -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;
};
}

View file

@ -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;
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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);

View file

@ -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;
}

View file

@ -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;
};
}

View file

@ -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;
}

View file

@ -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

View file

@ -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;
}
}

View file

@ -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;
};
}

View file

@ -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

View file

@ -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

View file

@ -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>

View file

@ -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;
}

View file

@ -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

View file

@ -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);
}

106
Src/replicant/nsid3v2/tag.h Normal file
View file

@ -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;
};
}

View file

@ -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;
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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);
}
};

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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

View file

@ -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