Initial community commit
This commit is contained in:
parent
537bcbc862
commit
fc06254474
16440 changed files with 4239995 additions and 2 deletions
108
Src/replicant/nsid3v2/extendedheader.cpp
Normal file
108
Src/replicant/nsid3v2/extendedheader.cpp
Normal 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;
|
||||
}
|
52
Src/replicant/nsid3v2/extendedheader.h
Normal file
52
Src/replicant/nsid3v2/extendedheader.h
Normal 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;
|
||||
|
||||
};
|
||||
}
|
786
Src/replicant/nsid3v2/frame.cpp
Normal file
786
Src/replicant/nsid3v2/frame.cpp
Normal 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(¤t_length);
|
||||
|
||||
if (new_header.Unsynchronised())
|
||||
{
|
||||
current_length += ID3v2::Util::SynchronisedSize(data, data_size);
|
||||
}
|
||||
else
|
||||
{
|
||||
current_length += new_header.FrameSize();
|
||||
}
|
||||
|
||||
*length = current_length;
|
||||
return NErr_Success;
|
||||
}
|
||||
|
||||
int ID3v2_2::Frame::Serialize(void *output, uint32_t *written, const ID3v2::Header &tag_header, int flags) const
|
||||
{
|
||||
size_t current_length = FrameHeader::SIZE;
|
||||
uint8_t *data_ptr = (uint8_t *)output;
|
||||
ID3v2_2::FrameHeader new_header(header, tag_header);
|
||||
new_header.SetSize(data_size);
|
||||
|
||||
// write frame header
|
||||
new_header.Serialize(data_ptr);
|
||||
data_ptr += FrameHeader::SIZE;
|
||||
if (new_header.Unsynchronised())
|
||||
{
|
||||
current_length += ID3v2::Util::SynchroniseTo(data_ptr, data, data_size);
|
||||
}
|
||||
else
|
||||
{
|
||||
memcpy(data_ptr, data, data_size);
|
||||
current_length += data_size;
|
||||
}
|
||||
*written = current_length;
|
||||
return NErr_Success;
|
||||
}
|
||||
|
||||
|
||||
const int8_t *ID3v2_2::Frame::GetIdentifier() const
|
||||
{
|
||||
return header.GetIdentifier();
|
||||
}
|
||||
|
||||
|
||||
/* === ID3v2.3 === */
|
||||
ID3v2_3::Frame::Frame(const ID3v2::Header &_header, const int8_t *id, int flags) : header(_header, id, flags)
|
||||
{
|
||||
}
|
||||
|
||||
ID3v2_3::Frame::Frame(const FrameHeader &_header) : header(_header)
|
||||
{
|
||||
}
|
||||
|
||||
/* helper function
|
||||
reads num_bytes from input into output, dealing with re-synchronization and length checking
|
||||
increments input pointer
|
||||
increments bytes_read value by number of input bytes read (different from num_bytes when data is unsynchronized
|
||||
decrements input_len by bytes read
|
||||
decrements output_len by bytes written
|
||||
*/
|
||||
bool ID3v2_3::Frame::ReadData(void *output, const void *&input, size_t &input_len, size_t &frame_len, size_t num_bytes, size_t *bytes_read) const
|
||||
{
|
||||
/* verify that we have enough data in the frame */
|
||||
if (num_bytes > frame_len)
|
||||
return false;
|
||||
|
||||
/* verify that we have enough data in the buffer */
|
||||
size_t bytes_to_read;
|
||||
if (header.Unsynchronised())
|
||||
bytes_to_read = ID3v2::Util::UnsynchronisedInputSize(input, num_bytes);
|
||||
else
|
||||
bytes_to_read = num_bytes;
|
||||
|
||||
if (bytes_to_read > input_len)
|
||||
return false;
|
||||
|
||||
/* read data */
|
||||
if (header.Unsynchronised())
|
||||
{
|
||||
*bytes_read += ID3v2::Util::SynchroniseTo(&output, input, num_bytes);
|
||||
}
|
||||
else
|
||||
{
|
||||
*bytes_read += num_bytes;
|
||||
memcpy(output, input, num_bytes);
|
||||
}
|
||||
|
||||
/* increment input pointer */
|
||||
input = (const uint8_t *)input + bytes_to_read;
|
||||
|
||||
/* decrement sizes */
|
||||
frame_len -= num_bytes;
|
||||
input_len -= bytes_to_read;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* benski> this function is a bit complex
|
||||
we have two things to worry about, and can have any combination of the two
|
||||
1) Is the data 'unsynchronized'
|
||||
2) Is the data compressed (zlib)
|
||||
|
||||
we keep track of three sizes:
|
||||
len - number of bytes in input buffer
|
||||
data_size - number of bytes of output data buffer
|
||||
frame_size - number of bytes of data in frame AFTER re-synchronization
|
||||
|
||||
frame_size==data_size when compression is OFF
|
||||
*/
|
||||
int ID3v2_3::Frame::Parse(const void *_data, size_t len, size_t *read)
|
||||
{
|
||||
*read = 0;
|
||||
size_t frame_size = header.FrameSize(); // size of frame AFTER re-synchronization
|
||||
|
||||
if (header.Compressed())
|
||||
{
|
||||
// read 4 bytes of decompressed size
|
||||
uint8_t raw_size[4];
|
||||
if (ReadData(raw_size, _data, len, frame_size, 4, read) == false)
|
||||
return 1;
|
||||
|
||||
bytereader_value_t byte_reader;
|
||||
bytereader_init(&byte_reader, raw_size, 4);
|
||||
|
||||
data_size = bytereader_read_u32_be(&byte_reader);
|
||||
}
|
||||
|
||||
/* Check for group identity. If this exists, we'll store it separate from the raw data */
|
||||
if (header.Grouped())
|
||||
{
|
||||
// read 1 byte for group identity
|
||||
if (ReadData(&group_identity, _data, len, frame_size, 1, read) == false)
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!header.Compressed())
|
||||
{
|
||||
data_size = frame_size;
|
||||
}
|
||||
|
||||
/* check to make sure that we have enough input data to read the data */
|
||||
if (!header.Compressed() && header.Unsynchronised())
|
||||
{
|
||||
/* this is tricky, because the stored size reflects after re-synchronization,
|
||||
but the incoming data is unsynchronized */
|
||||
if (ID3v2::Util::UnsynchronisedInputSize(_data, data_size) > len)
|
||||
return 1;
|
||||
}
|
||||
else if (frame_size > len)
|
||||
return 1;
|
||||
|
||||
/* allocate memory (real data_size) */
|
||||
data = malloc(data_size);
|
||||
if (!data)
|
||||
return NErr_OutOfMemory;
|
||||
|
||||
/* === Read the data === */
|
||||
if (header.Compressed())
|
||||
{
|
||||
if (header.Unsynchronised()) // compressed AND unsynchronized.. what a pain!!
|
||||
{
|
||||
// TODO: combined re-synchronization + inflation
|
||||
void *temp = malloc(frame_size);
|
||||
if (!temp)
|
||||
return NErr_OutOfMemory;
|
||||
|
||||
*read += ID3v2::Util::UnsynchroniseTo(temp, _data, frame_size);
|
||||
|
||||
uLongf uncompressedSize = data_size;
|
||||
int ret = uncompress((Bytef *)data, &uncompressedSize, (const Bytef *)temp, frame_size);
|
||||
free(temp);
|
||||
if (ret != Z_OK)
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
uLongf uncompressedSize = data_size;
|
||||
if (uncompress((Bytef *)data, &uncompressedSize, (const Bytef *)_data, frame_size) != Z_OK)
|
||||
return 1;
|
||||
*read += frame_size;
|
||||
}
|
||||
}
|
||||
else if (header.Unsynchronised())
|
||||
{
|
||||
*read += ID3v2::Util::UnsynchroniseTo(data, _data, data_size);
|
||||
}
|
||||
else // normal data
|
||||
{
|
||||
memcpy(data, _data, data_size);
|
||||
*read += data_size;
|
||||
}
|
||||
|
||||
return NErr_Success;
|
||||
}
|
||||
|
||||
int ID3v2_3::Frame::SerializedSize(uint32_t *length, const ID3v2::Header &tag_header, int flags) const
|
||||
{
|
||||
ID3v2_3::FrameHeader new_header(header, tag_header);
|
||||
// TODO: for now, we're not going to deal with compression
|
||||
new_header.ClearCompressed();
|
||||
new_header.SetSize(data_size);
|
||||
|
||||
uint32_t current_length=0;
|
||||
new_header.SerializedSize(¤t_length);
|
||||
|
||||
if (new_header.Unsynchronised())
|
||||
{
|
||||
if (new_header.Compressed())
|
||||
{
|
||||
uint8_t data_length[4];
|
||||
bytewriter_s byte_writer;
|
||||
bytewriter_init(&byte_writer, data_length, 4);
|
||||
bytewriter_write_u32_be(&byte_writer, data_size);
|
||||
current_length += ID3v2::Util::SynchronisedSize(&data_length, 4);
|
||||
}
|
||||
|
||||
if (new_header.Grouped())
|
||||
current_length += ID3v2::Util::SynchronisedSize(&group_identity, 1);
|
||||
current_length += ID3v2::Util::SynchronisedSize(data, data_size);
|
||||
}
|
||||
else
|
||||
{
|
||||
current_length += new_header.FrameSize();
|
||||
}
|
||||
|
||||
*length = current_length;
|
||||
return NErr_Success;
|
||||
}
|
||||
|
||||
int ID3v2_3::Frame::Serialize(void *output, uint32_t *written, const ID3v2::Header &tag_header, int flags) const
|
||||
{
|
||||
size_t current_length = FrameHeaderBase::SIZE;
|
||||
uint8_t *data_ptr = (uint8_t *)output;
|
||||
ID3v2_3::FrameHeader new_header(header, tag_header);
|
||||
// TODO: for now, we're not going to deal with compression
|
||||
new_header.ClearCompressed();
|
||||
new_header.SetSize(data_size);
|
||||
|
||||
// write frame header
|
||||
uint32_t header_size;
|
||||
new_header.Serialize(data_ptr, &header_size);
|
||||
data_ptr += header_size;
|
||||
if (new_header.Unsynchronised())
|
||||
{
|
||||
if (new_header.Compressed())
|
||||
{
|
||||
uint8_t data_length[4];
|
||||
bytewriter_s byte_writer;
|
||||
bytewriter_init(&byte_writer, data_length, 4);
|
||||
bytewriter_write_u32_be(&byte_writer, data_size);
|
||||
current_length += ID3v2::Util::SynchroniseTo(data_ptr, &data_length, 4);
|
||||
data_ptr+=4;
|
||||
}
|
||||
|
||||
if (new_header.Grouped())
|
||||
current_length += ID3v2::Util::SynchroniseTo(data_ptr++, &group_identity, 1);
|
||||
current_length += ID3v2::Util::SynchroniseTo(data_ptr, data, data_size);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (new_header.Compressed())
|
||||
{
|
||||
bytewriter_s byte_writer;
|
||||
bytewriter_init(&byte_writer, data_ptr, 4);
|
||||
bytewriter_write_u32_be(&byte_writer, data_size);
|
||||
data_ptr+=4;
|
||||
}
|
||||
|
||||
if (new_header.Grouped())
|
||||
{
|
||||
*data_ptr++ = group_identity;
|
||||
current_length++;
|
||||
}
|
||||
memcpy(data_ptr, data, data_size);
|
||||
current_length += data_size;
|
||||
}
|
||||
*written = current_length;
|
||||
return NErr_Success;
|
||||
}
|
||||
|
||||
const int8_t *ID3v2_3::Frame::GetIdentifier() const
|
||||
{
|
||||
return header.GetIdentifier();
|
||||
}
|
||||
|
||||
bool ID3v2_3::Frame::Encrypted() const
|
||||
{
|
||||
return header.Encrypted();
|
||||
}
|
||||
|
||||
bool ID3v2_3::Frame::Compressed() const
|
||||
{
|
||||
return header.Compressed();
|
||||
}
|
||||
|
||||
bool ID3v2_3::Frame::Grouped() const
|
||||
{
|
||||
return header.Grouped();
|
||||
}
|
||||
|
||||
bool ID3v2_3::Frame::ReadOnly() const
|
||||
{
|
||||
return header.ReadOnly();
|
||||
}
|
||||
|
||||
bool ID3v2_3::Frame::TagAlterPreservation() const
|
||||
{
|
||||
return header.TagAlterPreservation();
|
||||
}
|
||||
|
||||
bool ID3v2_3::Frame::FileAlterPreservation() const
|
||||
{
|
||||
return header.FileAlterPreservation();
|
||||
}
|
||||
|
||||
|
||||
/* === ID3v2.4 === */
|
||||
ID3v2_4::Frame::Frame(const ID3v2::Header &_header, const int8_t *id, int flags) : header(_header, id, flags)
|
||||
{
|
||||
}
|
||||
|
||||
ID3v2_4::Frame::Frame(const FrameHeader &_header) : header(_header)
|
||||
{
|
||||
}
|
||||
|
||||
/* helper function
|
||||
reads num_bytes from input into output, dealing with re-synchronization and length checking
|
||||
increments input pointer
|
||||
increments bytes_read value by number of input bytes read (different from num_bytes when data is unsynchronized
|
||||
decrements input_len by bytes read
|
||||
decrements output_len by bytes written
|
||||
*/
|
||||
bool ID3v2_4::Frame::ReadData(void *output, const void *&input, size_t &input_len, size_t &frame_len, size_t num_bytes, size_t *bytes_read) const
|
||||
{
|
||||
/* verify that we have enough data in the frame */
|
||||
if (num_bytes > frame_len)
|
||||
return false;
|
||||
|
||||
/* verify that we have enough data in the buffer */
|
||||
size_t bytes_to_read = num_bytes;
|
||||
|
||||
if (bytes_to_read > input_len)
|
||||
return false;
|
||||
|
||||
/* read data */
|
||||
|
||||
*bytes_read += num_bytes;
|
||||
memcpy(output, input, num_bytes);
|
||||
|
||||
/* increment input pointer */
|
||||
input = (const uint8_t *)input + bytes_to_read;
|
||||
|
||||
/* decrement sizes */
|
||||
frame_len -= num_bytes;
|
||||
input_len -= bytes_to_read;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* benski> this function is a bit complex
|
||||
we have two things to worry about, and can have any combination of the two
|
||||
1) Is the data 'unsynchronized'
|
||||
2) Is the data compressed (zlib)
|
||||
|
||||
we keep track of three sizes:
|
||||
len - number of bytes in input buffer
|
||||
data_size - number of bytes of output data buffer
|
||||
frame_size - number of bytes of data in frame AFTER re-synchronization
|
||||
|
||||
frame_size==data_size when compression is OFF
|
||||
*/
|
||||
int ID3v2_4::Frame::Parse(const void *_data, size_t len, size_t *read)
|
||||
{
|
||||
*read = 0;
|
||||
size_t frame_size = header.FrameSize();
|
||||
|
||||
// TODO: if frame_size >= 128, verify size. iTunes v2.4 parser bug ...
|
||||
|
||||
|
||||
/* Check for group identity. If this exists, we'll store it separate from the raw data */
|
||||
/* Note: ID3v2.4 puts group identity BEFORE data length indicator, where as v2.3 has it the other way */
|
||||
if (header.Grouped())
|
||||
{
|
||||
// read 1 byte for group identity
|
||||
if (ReadData(&group_identity, _data, len, frame_size, 1, read) == false)
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (header.Compressed() || header.DataLengthIndicated())
|
||||
{
|
||||
// read 4 bytes of decompressed size
|
||||
uint8_t raw_size[4];
|
||||
if (ReadData(raw_size, _data, len, frame_size, 4, read) == false)
|
||||
return 1;
|
||||
|
||||
bytereader_value_t byte_reader;
|
||||
bytereader_init(&byte_reader, raw_size, 4);
|
||||
|
||||
data_size = bytereader_read_u32_be(&byte_reader);
|
||||
}
|
||||
|
||||
if (!(header.Compressed() || header.DataLengthIndicated()))
|
||||
{
|
||||
data_size = frame_size;
|
||||
}
|
||||
|
||||
/* check to make sure that we have enough input data to read the data */
|
||||
|
||||
if (frame_size > len)
|
||||
return 1;
|
||||
|
||||
if (!header.Compressed() && header.Unsynchronised())
|
||||
{
|
||||
data_size = ID3v2::Util::UnsynchronisedOutputSize(_data, frame_size);
|
||||
}
|
||||
|
||||
/* allocate memory (real data_size) */
|
||||
data = malloc(data_size);
|
||||
if (!data)
|
||||
return NErr_OutOfMemory;
|
||||
|
||||
/* === Read the data === */
|
||||
if (header.Compressed())
|
||||
{
|
||||
if (header.Unsynchronised()) // compressed AND unsynchronized.. what a pain!!
|
||||
{
|
||||
// TODO: combined re-synchronization + inflation
|
||||
size_t sync_size = ID3v2::Util::UnsynchronisedOutputSize(_data, frame_size);
|
||||
void *temp = malloc(sync_size);
|
||||
if (!temp)
|
||||
return NErr_OutOfMemory;
|
||||
|
||||
*read += ID3v2::Util::UnsynchroniseTo(temp, _data, sync_size);
|
||||
|
||||
uLongf uncompressedSize = data_size;
|
||||
int ret = uncompress((Bytef *)data, &uncompressedSize, (const Bytef *)temp, sync_size);
|
||||
/* TODO: realloc and set data_size to uncompressedSize if uncompressedSize was actually lower */
|
||||
free(temp);
|
||||
if (ret != Z_OK)
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
uLongf uncompressedSize = data_size;
|
||||
if (uncompress((Bytef *)data, &uncompressedSize, (const Bytef *)_data, frame_size) != Z_OK)
|
||||
return 1;
|
||||
/* TODO: realloc and set data_size to uncompressedSize if uncompressedSize was actually lower */
|
||||
*read += frame_size;
|
||||
}
|
||||
}
|
||||
else if (header.Unsynchronised())
|
||||
{
|
||||
*read += ID3v2::Util::UnsynchroniseTo(data, _data, data_size);
|
||||
}
|
||||
else // normal data
|
||||
{
|
||||
memcpy(data, _data, data_size);
|
||||
*read += data_size;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ID3v2_4::Frame::SerializedSize(uint32_t *length, const ID3v2::Header &tag_header, int flags) const
|
||||
{
|
||||
ID3v2_4::FrameHeader new_header(header, tag_header);
|
||||
// TODO: for now, we're not going to deal with compression
|
||||
new_header.ClearCompressed();
|
||||
switch(flags & Serialize_UnsynchronizeMask)
|
||||
{
|
||||
case Serialize_Unsynchronize:
|
||||
// TODO:
|
||||
break;
|
||||
case Serialize_NoUnsynchronize:
|
||||
new_header.ClearUnsynchronized();
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO: this doesn't handle compression
|
||||
if (new_header.Unsynchronised())
|
||||
{
|
||||
size_t unsynchronized_data_size = ID3v2::Util::SynchronisedSize(data, data_size);
|
||||
new_header.SetSize(unsynchronized_data_size);
|
||||
}
|
||||
else
|
||||
{
|
||||
new_header.SetSize(data_size);
|
||||
}
|
||||
|
||||
|
||||
size_t current_length = ID3v2_4::FrameHeader::SIZE;
|
||||
|
||||
if (new_header.Unsynchronised())
|
||||
{
|
||||
if (new_header.DataLengthIndicated() || new_header.Compressed())
|
||||
{
|
||||
current_length += 4;
|
||||
}
|
||||
|
||||
if (new_header.Grouped())
|
||||
current_length += ID3v2::Util::SynchronisedSize(&group_identity, 1);
|
||||
current_length += ID3v2::Util::SynchronisedSize(data, data_size);
|
||||
}
|
||||
else
|
||||
{
|
||||
current_length += new_header.FrameSize();
|
||||
}
|
||||
|
||||
*length = current_length;
|
||||
return NErr_Success;
|
||||
}
|
||||
|
||||
int ID3v2_4::Frame::Serialize(void *output, uint32_t *written, const ID3v2::Header &tag_header, int flags) const
|
||||
{
|
||||
size_t current_length = ID3v2_4::FrameHeader::SIZE;
|
||||
uint8_t *data_ptr = (uint8_t *)output;
|
||||
ID3v2_4::FrameHeader new_header(header, tag_header);
|
||||
// TODO: for now, we're not going to deal with compression
|
||||
new_header.ClearCompressed();
|
||||
switch(flags & Serialize_UnsynchronizeMask)
|
||||
{
|
||||
case Serialize_Unsynchronize:
|
||||
// TODO:
|
||||
break;
|
||||
case Serialize_NoUnsynchronize:
|
||||
new_header.ClearUnsynchronized();
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO: this doesn't handle compression
|
||||
if (new_header.Unsynchronised())
|
||||
{
|
||||
size_t unsynchronized_data_size = ID3v2::Util::SynchronisedSize(data, data_size);
|
||||
new_header.SetSize(unsynchronized_data_size);
|
||||
}
|
||||
else
|
||||
{
|
||||
new_header.SetSize(data_size);
|
||||
}
|
||||
|
||||
// write frame header
|
||||
uint32_t header_size;
|
||||
new_header.Serialize(data_ptr, &header_size);
|
||||
data_ptr += header_size;
|
||||
|
||||
if (new_header.Compressed() || new_header.DataLengthIndicated())
|
||||
{
|
||||
bytewriter_s byte_writer;
|
||||
bytewriter_init(&byte_writer, data_ptr, 4);
|
||||
bytewriter_write_u32_be(&byte_writer, ID3v2::Util::Int32To28(data_size));
|
||||
data_ptr+=4;
|
||||
current_length+=4;
|
||||
}
|
||||
|
||||
if (new_header.Unsynchronised())
|
||||
{
|
||||
|
||||
if (Grouped())
|
||||
current_length += ID3v2::Util::SynchroniseTo(data_ptr++, &group_identity, 1);
|
||||
current_length += ID3v2::Util::SynchroniseTo(data_ptr, data, data_size);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
if (new_header.Grouped())
|
||||
{
|
||||
*data_ptr++ = group_identity;
|
||||
current_length++;
|
||||
}
|
||||
memcpy(data_ptr, data, data_size);
|
||||
current_length += data_size;
|
||||
}
|
||||
*written = current_length;
|
||||
return NErr_Success;
|
||||
}
|
||||
|
||||
const int8_t *ID3v2_4::Frame::GetIdentifier() const
|
||||
{
|
||||
return header.GetIdentifier();
|
||||
}
|
||||
|
||||
bool ID3v2_4::Frame::Encrypted() const
|
||||
{
|
||||
return header.Encrypted();
|
||||
}
|
||||
|
||||
bool ID3v2_4::Frame::Compressed() const
|
||||
{
|
||||
return header.Compressed();
|
||||
}
|
||||
|
||||
bool ID3v2_4::Frame::Grouped() const
|
||||
{
|
||||
return header.Grouped();
|
||||
}
|
||||
|
||||
bool ID3v2_4::Frame::ReadOnly() const
|
||||
{
|
||||
return header.ReadOnly();
|
||||
}
|
||||
|
||||
bool ID3v2_4::Frame::FrameUnsynchronised() const
|
||||
{
|
||||
return header.FrameUnsynchronised();
|
||||
}
|
||||
|
||||
bool ID3v2_4::Frame::DataLengthIndicated() const
|
||||
{
|
||||
return header.DataLengthIndicated();
|
||||
}
|
||||
|
||||
|
||||
bool ID3v2_4::Frame::TagAlterPreservation() const
|
||||
{
|
||||
return header.TagAlterPreservation();
|
||||
}
|
||||
|
||||
bool ID3v2_4::Frame::FileAlterPreservation() const
|
||||
{
|
||||
return header.FileAlterPreservation();
|
||||
}
|
117
Src/replicant/nsid3v2/frame.h
Normal file
117
Src/replicant/nsid3v2/frame.h
Normal 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;
|
||||
};
|
||||
}
|
247
Src/replicant/nsid3v2/frame_apic.cpp
Normal file
247
Src/replicant/nsid3v2/frame_apic.cpp
Normal 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;
|
||||
}
|
185
Src/replicant/nsid3v2/frame_comments.cpp
Normal file
185
Src/replicant/nsid3v2/frame_comments.cpp
Normal 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);
|
||||
}
|
167
Src/replicant/nsid3v2/frame_id.cpp
Normal file
167
Src/replicant/nsid3v2/frame_id.cpp
Normal 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);
|
||||
}
|
86
Src/replicant/nsid3v2/frame_object.cpp
Normal file
86
Src/replicant/nsid3v2/frame_object.cpp
Normal 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;
|
||||
}
|
95
Src/replicant/nsid3v2/frame_popm.cpp
Normal file
95
Src/replicant/nsid3v2/frame_popm.cpp
Normal 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;
|
||||
}
|
61
Src/replicant/nsid3v2/frame_private.cpp
Normal file
61
Src/replicant/nsid3v2/frame_private.cpp
Normal 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;
|
||||
}
|
95
Src/replicant/nsid3v2/frame_text.cpp
Normal file
95
Src/replicant/nsid3v2/frame_text.cpp
Normal 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);
|
||||
}
|
42
Src/replicant/nsid3v2/frame_url.cpp
Normal file
42
Src/replicant/nsid3v2/frame_url.cpp
Normal 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);
|
||||
}
|
||||
|
||||
|
166
Src/replicant/nsid3v2/frame_usertext.cpp
Normal file
166
Src/replicant/nsid3v2/frame_usertext.cpp
Normal 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);
|
||||
}
|
78
Src/replicant/nsid3v2/frame_userurl.cpp
Normal file
78
Src/replicant/nsid3v2/frame_userurl.cpp
Normal 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;
|
||||
}
|
265
Src/replicant/nsid3v2/frame_utils.cpp
Normal file
265
Src/replicant/nsid3v2/frame_utils.cpp
Normal 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;
|
||||
}
|
21
Src/replicant/nsid3v2/frame_utils.h
Normal file
21
Src/replicant/nsid3v2/frame_utils.h
Normal 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);
|
403
Src/replicant/nsid3v2/frameheader.cpp
Normal file
403
Src/replicant/nsid3v2/frameheader.cpp
Normal 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;
|
||||
}
|
124
Src/replicant/nsid3v2/frameheader.h
Normal file
124
Src/replicant/nsid3v2/frameheader.h
Normal 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;
|
||||
};
|
||||
}
|
61
Src/replicant/nsid3v2/frames.c
Normal file
61
Src/replicant/nsid3v2/frames.c
Normal 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;
|
||||
}
|
21
Src/replicant/nsid3v2/frames.h
Normal file
21
Src/replicant/nsid3v2/frames.h
Normal 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
|
186
Src/replicant/nsid3v2/header.cpp
Normal file
186
Src/replicant/nsid3v2/header.cpp
Normal 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;
|
||||
}
|
||||
}
|
44
Src/replicant/nsid3v2/header.h
Normal file
44
Src/replicant/nsid3v2/header.h
Normal 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;
|
||||
};
|
||||
}
|
190
Src/replicant/nsid3v2/nsid3v2.h
Normal file
190
Src/replicant/nsid3v2/nsid3v2.h
Normal 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
|
44
Src/replicant/nsid3v2/nsid3v2.sln
Normal file
44
Src/replicant/nsid3v2/nsid3v2.sln
Normal 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
|
194
Src/replicant/nsid3v2/nsid3v2.vcxproj
Normal file
194
Src/replicant/nsid3v2/nsid3v2.vcxproj
Normal 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>
|
366
Src/replicant/nsid3v2/nsid3v2_common.cpp
Normal file
366
Src/replicant/nsid3v2/nsid3v2_common.cpp
Normal 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;
|
||||
}
|
||||
|
32
Src/replicant/nsid3v2/precomp.h
Normal file
32
Src/replicant/nsid3v2/precomp.h
Normal 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
|
644
Src/replicant/nsid3v2/tag.cpp
Normal file
644
Src/replicant/nsid3v2/tag.cpp
Normal 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
106
Src/replicant/nsid3v2/tag.h
Normal 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;
|
||||
};
|
||||
}
|
161
Src/replicant/nsid3v2/util.cpp
Normal file
161
Src/replicant/nsid3v2/util.cpp
Normal 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;
|
||||
}
|
28
Src/replicant/nsid3v2/util.h
Normal file
28
Src/replicant/nsid3v2/util.h
Normal 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);
|
||||
}
|
||||
}
|
49
Src/replicant/nsid3v2/values.cpp
Normal file
49
Src/replicant/nsid3v2/values.cpp
Normal 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;
|
||||
}
|
||||
}
|
22
Src/replicant/nsid3v2/values.h
Normal file
22
Src/replicant/nsid3v2/values.h
Normal 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);
|
||||
}
|
||||
};
|
170
Src/replicant/nsid3v2/windows/frame_apic.cpp
Normal file
170
Src/replicant/nsid3v2/windows/frame_apic.cpp
Normal 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;
|
||||
}
|
228
Src/replicant/nsid3v2/windows/frame_comments.cpp
Normal file
228
Src/replicant/nsid3v2/windows/frame_comments.cpp
Normal 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;
|
||||
}
|
||||
|
73
Src/replicant/nsid3v2/windows/nsid3v2.cpp
Normal file
73
Src/replicant/nsid3v2/windows/nsid3v2.cpp
Normal 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
|
20
Src/replicant/nsid3v2/windows/nsid3v2.h
Normal file
20
Src/replicant/nsid3v2/windows/nsid3v2.h
Normal 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
|
Loading…
Add table
Add a link
Reference in a new issue