aboutsummaryrefslogtreecommitdiff
path: root/Src/replicant/nsid3v2/frame.cpp
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/frame.cpp
parent537bcbc86291b32fc04ae4133ce4d7cac8ebe9a7 (diff)
downloadwinamp-20d28e80a5c861a9d5f449ea911ab75b4f37ad0d.tar.gz
Initial community commit
Diffstat (limited to 'Src/replicant/nsid3v2/frame.cpp')
-rw-r--r--Src/replicant/nsid3v2/frame.cpp786
1 files changed, 786 insertions, 0 deletions
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();
+}