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