#include "tag.h" #include "frameheader.h" #include "frames.h" #include "foundation/error.h" #include #include #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); }