diff --git a/taglib/CMakeLists.txt b/taglib/CMakeLists.txt index 89cb8e12..c42700a1 100644 --- a/taglib/CMakeLists.txt +++ b/taglib/CMakeLists.txt @@ -79,6 +79,8 @@ set(tag_HDRS mpeg/id3v2/frames/unknownframe.h mpeg/id3v2/frames/unsynchronizedlyricsframe.h mpeg/id3v2/frames/urllinkframe.h + mpeg/id3v2/frames/chapterframe.h + mpeg/id3v2/frames/tableofcontentsframe.h ogg/oggfile.h ogg/oggpage.h ogg/oggpageheader.h @@ -171,6 +173,8 @@ set(frames_SRCS mpeg/id3v2/frames/unknownframe.cpp mpeg/id3v2/frames/unsynchronizedlyricsframe.cpp mpeg/id3v2/frames/urllinkframe.cpp + mpeg/id3v2/frames/chapterframe.cpp + mpeg/id3v2/frames/tableofcontentsframe.cpp ) set(ogg_SRCS diff --git a/taglib/mpeg/id3v2/frames/chapterframe.cpp b/taglib/mpeg/id3v2/frames/chapterframe.cpp new file mode 100644 index 00000000..8c5514e2 --- /dev/null +++ b/taglib/mpeg/id3v2/frames/chapterframe.cpp @@ -0,0 +1,266 @@ +/*************************************************************************** + copyright : (C) 2013 by Lukas Krejci + email : krejclu6@fel.cvut.cz + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include +#include +#include +#include + +#include "chapterframe.h" + +using namespace TagLib; +using namespace ID3v2; + +class ChapterFrame::ChapterFramePrivate +{ +public: + ByteVector elementID; + uint startTime; + uint endTime; + uint startOffset; + uint endOffset; + const FrameFactory *factory; + FrameListMap embeddedFrameListMap; + FrameList embeddedFrameList; +}; + +//////////////////////////////////////////////////////////////////////////////// +// public methods +//////////////////////////////////////////////////////////////////////////////// + +ChapterFrame::ChapterFrame(const ByteVector &data) : + ID3v2::Frame(data) +{ + d = new ChapterFramePrivate; + d->factory = FrameFactory::instance(); + setData(data); +} + +ChapterFrame::ChapterFrame(const ByteVector &eID, const uint &sT, const uint &eT, const uint &sO, const uint &eO, const FrameList &eF) : + ID3v2::Frame("CHAP") +{ + d = new ChapterFramePrivate; + d->elementID = eID; + d->startTime = sT; + d->endTime = eT; + d->startOffset = sO; + d->endOffset = eO; + FrameList l = eF; + for(FrameList::Iterator it = l.begin(); it != l.end(); ++it) + addEmbeddedFrame(*it); + d->factory = FrameFactory::instance(); +} + +ChapterFrame::~ChapterFrame() +{ + delete d; +} + +ByteVector ChapterFrame::elementID() const +{ + return d->elementID; +} + +uint ChapterFrame::startTime() const +{ + return d->startTime; +} + +uint ChapterFrame::endTime() const +{ + return d->endTime; +} + +uint ChapterFrame::startOffset() const +{ + return d->startOffset; +} + +uint ChapterFrame::endOffset() const +{ + return d->endOffset; +} + +void ChapterFrame::setElementID(const ByteVector &eID) +{ + d->elementID = eID; + if(eID.at(eID.size() - 1) != char(0)) + d->elementID.append(char(0)); +} + +void ChapterFrame::setStartTime(const uint &sT) +{ + d->startTime = sT; +} + +void ChapterFrame::setEndTime(const uint &eT) +{ + d->endTime = eT; +} + +void ChapterFrame::setStartOffset(const uint &sO) +{ + d->startOffset = sO; +} + +void ChapterFrame::setEndOffset(const uint &eO) +{ + d->endOffset = eO; +} + +const FrameListMap &ChapterFrame::embeddedFrameListMap() const +{ + return d->embeddedFrameListMap; +} + +const FrameList &ChapterFrame::embeddedFrameList() const +{ + return d->embeddedFrameList; +} + +const FrameList &ChapterFrame::embeddedFrameList(const ByteVector &frameID) const +{ + return d->embeddedFrameListMap[frameID]; +} + +void ChapterFrame::addEmbeddedFrame(Frame *frame) +{ + d->embeddedFrameList.append(frame); + d->embeddedFrameListMap[frame->frameID()].append(frame); +} + +void ChapterFrame::removeEmbeddedFrame(Frame *frame, bool del) +{ + // remove the frame from the frame list + FrameList::Iterator it = d->embeddedFrameList.find(frame); + d->embeddedFrameList.erase(it); + + // ...and from the frame list map + it = d->embeddedFrameListMap[frame->frameID()].find(frame); + d->embeddedFrameListMap[frame->frameID()].erase(it); + + // ...and delete as desired + if(del) + delete frame; +} + +void ChapterFrame::removeEmbeddedFrames(const ByteVector &id) +{ + FrameList l = d->embeddedFrameListMap[id]; + for(FrameList::Iterator it = l.begin(); it != l.end(); ++it) + removeEmbeddedFrame(*it, true); +} + +String ChapterFrame::toString() const +{ + return String::null; +} + +PropertyMap ChapterFrame::asProperties() const +{ + PropertyMap map; + + map.unsupportedData().append(frameID() + String("/") + d->elementID); + + return map; +} + +ChapterFrame *ChapterFrame::findByElementID(const Tag *tag, const ByteVector &eID) // static +{ + ID3v2::FrameList comments = tag->frameList("CHAP"); + + for(ID3v2::FrameList::ConstIterator it = comments.begin(); + it != comments.end(); + ++it) + { + ChapterFrame *frame = dynamic_cast(*it); + if(frame && frame->elementID() == eID) + return frame; + } + + return 0; +} + +void ChapterFrame::parseFields(const ByteVector &data) +{ + uint size = data.size(); + if(size < 18) { + debug("A CHAP frame must contain at least 18 bytes (1 byte element ID terminated by null and 4x4 bytes for start and end time and offset)."); + return; + } + + int pos = 0, embPos = 0; + d->elementID = readStringField(data, String::Latin1, &pos).data(String::Latin1); + d->elementID.append(char(0)); + d->startTime = data.mid(pos, 4).toUInt(true); + pos += 4; + d->endTime = data.mid(pos, 4).toUInt(true); + pos += 4; + d->startOffset = data.mid(pos, 4).toUInt(true); + pos += 4; + d->endOffset = data.mid(pos, 4).toUInt(true); + pos += 4; + size -= pos; + while((uint)embPos < size - Frame::headerSize(4)) + { + Frame *frame = d->factory->createFrame(data.mid(pos + embPos)); + + if(!frame) + return; + + // Checks to make sure that frame parsed correctly. + if(frame->size() <= 0) { + delete frame; + return; + } + + embPos += frame->size() + Frame::headerSize(4); + addEmbeddedFrame(frame); + } +} + +ByteVector ChapterFrame::renderFields() const +{ + ByteVector data; + + data.append(d->elementID); + data.append(ByteVector::fromUInt(d->startTime, true)); + data.append(ByteVector::fromUInt(d->endTime, true)); + data.append(ByteVector::fromUInt(d->startOffset, true)); + data.append(ByteVector::fromUInt(d->endOffset, true)); + FrameList l = d->embeddedFrameList; + for(FrameList::Iterator it = l.begin(); it != l.end(); ++it) + data.append((*it)->render()); + + return data; +} + +ChapterFrame::ChapterFrame(const ByteVector &data, Header *h) : + Frame(h) +{ + d = new ChapterFramePrivate; + d->factory = FrameFactory::instance(); + parseFields(fieldData(data)); +} diff --git a/taglib/mpeg/id3v2/frames/chapterframe.h b/taglib/mpeg/id3v2/frames/chapterframe.h new file mode 100644 index 00000000..84b42137 --- /dev/null +++ b/taglib/mpeg/id3v2/frames/chapterframe.h @@ -0,0 +1,242 @@ +/*************************************************************************** + copyright : (C) 2013 by Lukas Krejci + email : krejclu6@fel.cvut.cz + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_CHAPTERFRAME +#define TAGLIB_CHAPTERFRAME + +#include "id3v2tag.h" +#include "id3v2frame.h" +#include "taglib_export.h" + +namespace TagLib { + + namespace ID3v2 { + + /*! + * This is an implementation of ID3v2 chapter frames. The purpose of this + * frame is to describe a single chapter within an audio file. + */ + + //! An implementation of ID3v2 chapter frames + + class TAGLIB_EXPORT ChapterFrame : public ID3v2::Frame + { + friend class FrameFactory; + + public: + /*! + * Creates a chapter frame based on \a data. + */ + ChapterFrame(const ByteVector &data); + + /*! + * Creates a chapter frame with the element ID \a eID, + * start time \a sT, end time \a eT, start offset \a sO, + * end offset \a eO and embedded frames, that are in \a eF. + */ + ChapterFrame(const ByteVector &eID, const uint &sT, const uint &eT, const uint &sO, const uint &eO, const FrameList &eF); + + /*! + * Destroys the frame. + */ + ~ChapterFrame(); + + /*! + * Returns the element ID of the frame. Element ID + * is a null terminated string, however it's not human-readable. + * + * \see setElementID() + */ + ByteVector elementID() const; + + /*! + * Returns time of chapter's start (in miliseconds). + * + * \see setStartTime() + */ + uint startTime() const; + + /*! + * Returns time of chapter's end (in miliseconds). + * + * \see setEndTime() + */ + uint endTime() const; + + /*! + * Returns zero based byte offset (count of bytes from the beginning + * of the audio file) of chapter's start. + * + * \note If returned value is 0xFFFFFFFF, start time should be used instead. + * \see setStartOffset() + */ + uint startOffset() const; + + /*! + * Returns zero based byte offset (count of bytes from the beginning + * of the audio file) of chapter's end. + * + * \note If returned value is 0xFFFFFFFF, end time should be used instead. + * \see setEndOffset() + */ + uint endOffset() const; + + /*! + * Sets the element ID of the frame to \a eID. If \a eID isn't + * null terminated, a null char is appended automatically. + * + * \see elementID() + */ + void setElementID(const ByteVector &eID); + + /*! + * Sets time of chapter's start (in miliseconds) to \a sT. + * + * \see startTime() + */ + void setStartTime(const uint &sT); + + /*! + * Sets time of chapter's end (in miliseconds) to \a eT. + * + * \see endTime() + */ + void setEndTime(const uint &eT); + + /*! + * Sets zero based byte offset (count of bytes from the beginning + * of the audio file) of chapter's start to \a sO. + * + * \see startOffset() + */ + void setStartOffset(const uint &sO); + + /*! + * Sets zero based byte offset (count of bytes from the beginning + * of the audio file) of chapter's end to \a eO. + * + * \see endOffset() + */ + void setEndOffset(const uint &eO); + + /*! + * Returns a reference to the frame list map. This is an FrameListMap of + * all of the frames embedded in the CHAP frame. + * + * This is the most convenient structure for accessing the CHAP frame's + * embedded frames. Many frame types allow multiple instances of the same + * frame type so this is a map of lists. In most cases however there will + * only be a single frame of a certain type. + * + * \warning You should not modify this data structure directly, instead + * use addEmbeddedFrame() and removeEmbeddedFrame(). + * + * \see embeddedFrameList() + */ + const FrameListMap &embeddedFrameListMap() const; + + /*! + * Returns a reference to the embedded frame list. This is an FrameList + * of all of the frames embedded in the CHAP frame in the order that they + * were parsed. + * + * This can be useful if for example you want iterate over the CHAP frame's + * embedded frames in the order that they occur in the CHAP frame. + * + * \warning You should not modify this data structure directly, instead + * use addEmbeddedFrame() and removeEmbeddedFrame(). + */ + const FrameList &embeddedFrameList() const; + + /*! + * Returns the embedded frame list for frames with the id \a frameID + * or an empty list if there are no embedded frames of that type. This + * is just a convenience and is equivalent to: + * + * \code + * embeddedFrameListMap()[frameID]; + * \endcode + * + * \see embeddedFrameListMap() + */ + const FrameList &embeddedFrameList(const ByteVector &frameID) const; + + /*! + * Add an embedded frame to the CHAP frame. At this point the CHAP frame + * takes ownership of the embedded frame and will handle freeing its memory. + * + * \note Using this method will invalidate any pointers on the list + * returned by embeddedFrameList() + */ + void addEmbeddedFrame(Frame *frame); + + /*! + * Remove an embedded frame from the CHAP frame. If \a del is true the frame's + * memory will be freed; if it is false, it must be deleted by the user. + * + * \note Using this method will invalidate any pointers on the list + * returned by embeddedFrameList() + */ + void removeEmbeddedFrame(Frame *frame, bool del = true); + + /*! + * Remove all embedded frames of type \a id from the CHAP frame and free their + * memory. + * + * \note Using this method will invalidate any pointers on the list + * returned by embeddedFrameList() + */ + void removeEmbeddedFrames(const ByteVector &id); + + virtual String toString() const; + + PropertyMap asProperties() const; + + /*! + * CHAP frames each have a unique element ID. This searches for a CHAP + * frame with the element ID \a eID and returns a pointer to it. This + * can be used to link CTOC and CHAP frames together. + * + * \see elementID() + */ + static ChapterFrame *findByElementID(const Tag *tag, const ByteVector &eID); + + protected: + virtual void parseFields(const ByteVector &data); + virtual ByteVector renderFields() const; + + private: + ChapterFrame(const ChapterFrame &); + ChapterFrame &operator=(const ChapterFrame &); + + ChapterFrame(const ByteVector &data, Header *h); + + class ChapterFramePrivate; + ChapterFramePrivate *d; + }; + } +} + +#endif diff --git a/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp b/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp new file mode 100644 index 00000000..f986eb9a --- /dev/null +++ b/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp @@ -0,0 +1,293 @@ +/*************************************************************************** + copyright : (C) 2013 by Lukas Krejci + email : krejclu6@fel.cvut.cz + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include +#include +#include + +#include "tableofcontentsframe.h" + +using namespace TagLib; +using namespace ID3v2; + +class TableOfContentsFrame::TableOfContentsFramePrivate +{ +public: + ByteVector elementID; + bool isTopLevel; + bool isOrdered; + ByteVectorList childElements; + const FrameFactory *factory; + FrameListMap embeddedFrameListMap; + FrameList embeddedFrameList; +}; + +//////////////////////////////////////////////////////////////////////////////// +// public methods +//////////////////////////////////////////////////////////////////////////////// + +TableOfContentsFrame::TableOfContentsFrame(const ByteVector &data) : + ID3v2::Frame(data) +{ + d = new TableOfContentsFramePrivate; + d->factory = FrameFactory::instance(); + setData(data); +} + +TableOfContentsFrame::TableOfContentsFrame(const ByteVector &eID, const ByteVectorList &ch, const FrameList &eF) : + ID3v2::Frame("CTOC") +{ + d = new TableOfContentsFramePrivate; + d->elementID = eID; + d->childElements = ch; + FrameList l = eF; + for(FrameList::Iterator it = l.begin(); it != l.end(); ++it) + addEmbeddedFrame(*it); + d->factory = FrameFactory::instance(); +} + +TableOfContentsFrame::~TableOfContentsFrame() +{ + delete d; +} + +ByteVector TableOfContentsFrame::elementID() const +{ + return d->elementID; +} + +bool TableOfContentsFrame::isTopLevel() const +{ + return d->isTopLevel; +} + +bool TableOfContentsFrame::isOrdered() const +{ + return d->isOrdered; +} + +uint TableOfContentsFrame::entryCount() const +{ + return d->childElements.size(); +} + +ByteVectorList TableOfContentsFrame::childElements() const +{ + return d->childElements; +} + +void TableOfContentsFrame::setElementID(const ByteVector &eID) +{ + d->elementID = eID; + if(eID.at(eID.size() - 1) != char(0)) + d->elementID.append(char(0)); +} + +void TableOfContentsFrame::setIsTopLevel(const bool &t) +{ + d->isTopLevel = t; +} + +void TableOfContentsFrame::setIsOrdered(const bool &o) +{ + d->isOrdered = o; +} + +void TableOfContentsFrame::setChildElements(const ByteVectorList &l) +{ + d->childElements = l; +} + +void TableOfContentsFrame::addChildElement(const ByteVector &cE) +{ + d->childElements.append(cE); +} + +void TableOfContentsFrame::removeChildElement(const ByteVector &cE) +{ + ByteVectorList::Iterator it = d->childElements.find(cE); + d->childElements.erase(it); +} + +const FrameListMap &TableOfContentsFrame::embeddedFrameListMap() const +{ + return d->embeddedFrameListMap; +} + +const FrameList &TableOfContentsFrame::embeddedFrameList() const +{ + return d->embeddedFrameList; +} + +const FrameList &TableOfContentsFrame::embeddedFrameList(const ByteVector &frameID) const +{ + return d->embeddedFrameListMap[frameID]; +} + +void TableOfContentsFrame::addEmbeddedFrame(Frame *frame) +{ + d->embeddedFrameList.append(frame); + d->embeddedFrameListMap[frame->frameID()].append(frame); +} + +void TableOfContentsFrame::removeEmbeddedFrame(Frame *frame, bool del) +{ + // remove the frame from the frame list + FrameList::Iterator it = d->embeddedFrameList.find(frame); + d->embeddedFrameList.erase(it); + + // ...and from the frame list map + it = d->embeddedFrameListMap[frame->frameID()].find(frame); + d->embeddedFrameListMap[frame->frameID()].erase(it); + + // ...and delete as desired + if(del) + delete frame; +} + +void TableOfContentsFrame::removeEmbeddedFrames(const ByteVector &id) +{ + FrameList l = d->embeddedFrameListMap[id]; + for(FrameList::Iterator it = l.begin(); it != l.end(); ++it) + removeEmbeddedFrame(*it, true); +} + +String TableOfContentsFrame::toString() const +{ + return String::null; +} + +PropertyMap TableOfContentsFrame::asProperties() const +{ + PropertyMap map; + + map.unsupportedData().append(frameID() + String("/") + d->elementID); + + return map; +} + +TableOfContentsFrame *TableOfContentsFrame::findByElementID(const ID3v2::Tag *tag, const ByteVector &eID) // static +{ + ID3v2::FrameList tablesOfContents = tag->frameList("CTOC"); + + for(ID3v2::FrameList::ConstIterator it = tablesOfContents.begin(); + it != tablesOfContents.end(); + ++it) + { + TableOfContentsFrame *frame = dynamic_cast(*it); + if(frame && frame->elementID() == eID) + return frame; + } + + return 0; +} + +TableOfContentsFrame *TableOfContentsFrame::findTopLevel(const Tag *tag) // static +{ + ID3v2::FrameList tablesOfContents = tag->frameList("CTOC"); + + for(ID3v2::FrameList::ConstIterator it = tablesOfContents.begin(); + it != tablesOfContents.end(); + ++it) + { + TableOfContentsFrame *frame = dynamic_cast(*it); + if(frame && frame->isTopLevel() == true) + return frame; + } + + return 0; +} + +void TableOfContentsFrame::parseFields(const ByteVector &data) +{ + uint size = data.size(); + if(size < 6) { + debug("A CTOC frame must contain at least 6 bytes (1 byte element ID terminated by null, 1 byte flags, 1 byte entry count and 1 byte child element ID terminated by null."); + return; + } + + int pos = 0, embPos = 0; + d->elementID = readStringField(data, String::Latin1, &pos).data(String::Latin1); + d->elementID.append(char(0)); + d->isTopLevel = (data.at(pos) & 2) > 0; + d->isOrdered = (data.at(pos++) & 1) > 0; + uint entryCount = data.at(pos++); + for(uint i = 0; i < entryCount; i++) + { + ByteVector childElementID = readStringField(data, String::Latin1, &pos).data(String::Latin1); + childElementID.append(char(0)); + d->childElements.append(childElementID); + } + + size -= pos; + while((uint)embPos < size - Frame::headerSize(4)) + { + Frame *frame = d->factory->createFrame(data.mid(pos + embPos)); + + if(!frame) + return; + + // Checks to make sure that frame parsed correctly. + if(frame->size() <= 0) { + delete frame; + return; + } + + embPos += frame->size() + Frame::headerSize(4); + addEmbeddedFrame(frame); + } +} + +ByteVector TableOfContentsFrame::renderFields() const +{ + ByteVector data; + + data.append(d->elementID); + char flags = 0; + if(d->isTopLevel) + flags += 2; + if(d->isOrdered) + flags += 1; + data.append(flags); + data.append((char)(entryCount())); + ByteVectorList::ConstIterator it = d->childElements.begin(); + while(it != d->childElements.end()) { + data.append(*it); + it++; + } + FrameList l = d->embeddedFrameList; + for(FrameList::Iterator it = l.begin(); it != l.end(); ++it) + data.append((*it)->render()); + + return data; +} + +TableOfContentsFrame::TableOfContentsFrame(const ByteVector &data, Header *h) : + Frame(h) +{ + d = new TableOfContentsFramePrivate; + d->factory = FrameFactory::instance(); + parseFields(fieldData(data)); +} diff --git a/taglib/mpeg/id3v2/frames/tableofcontentsframe.h b/taglib/mpeg/id3v2/frames/tableofcontentsframe.h new file mode 100644 index 00000000..bf578d42 --- /dev/null +++ b/taglib/mpeg/id3v2/frames/tableofcontentsframe.h @@ -0,0 +1,255 @@ +/*************************************************************************** + copyright : (C) 2013 by Lukas Krejci + email : krejclu6@fel.cvut.cz + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_TABLEOFCONTENTSFRAME +#define TAGLIB_TABLEOFCONTENTSFRAME + +#include "id3v2tag.h" +#include "id3v2frame.h" + +namespace TagLib { + + namespace ID3v2 { + + /*! + * This is an implementation of ID3v2 table of contents frames. Purpose + * of this frame is to allow a table of contents to be defined. + */ + + //! An implementation of ID3v2 table of contents frames + + class TAGLIB_EXPORT TableOfContentsFrame : public ID3v2::Frame + { + friend class FrameFactory; + + public: + /*! + * Creates a table of contents frame based on \a data. + */ + TableOfContentsFrame(const ByteVector &data); + + /*! + * Creates a table of contents frame with the element ID \a eID, + * the child elements \a ch and embedded frames, that are in \a eF. + */ + TableOfContentsFrame(const ByteVector &eID, const ByteVectorList &ch, const FrameList &eF); + + /*! + * Destroys the frame. + */ + ~TableOfContentsFrame(); + + /*! + * Returns the elementID of the frame. Element ID + * is a null terminated string, however it's not human-readable. + * + * \see setElementID() + */ + ByteVector elementID() const; + + /*! + * Returns true, if the frame is top-level (doen't have + * any parent CTOC frame). + * + * \see setIsTopLevel() + */ + bool isTopLevel() const; + + /*! + * Returns true, if the child elements list entries + * are ordered. + * + * \see setIsOrdered() + */ + bool isOrdered() const; + + /*! + * Returns count of child elements of the frame. It allways + * corresponds to size of child elements list. + * + * \see childElements() + */ + uint entryCount() const; + + /*! + * Returns list of child elements of the frame. + * + * \see setChildElements() + */ + ByteVectorList childElements() const; + + /*! + * Sets the elementID of the frame to \a eID. If \a eID isn't + * null terminated, a null char is appended automatically. + * + * \see elementID() + */ + void setElementID(const ByteVector &eID); + + /*! + * Sets, if the frame is top-level (doen't have + * any parent CTOC frame). + * + * \see isTopLevel() + */ + void setIsTopLevel(const bool &t); + + /*! + * Sets, if the child elements list entries + * are ordered. + * + * \see isOrdered() + */ + void setIsOrdered(const bool &o); + + /*! + * Sets list of child elements of the frame to \a l. + * + * \see childElements() + */ + void setChildElements(const ByteVectorList &l); + + /*! + * Adds \a cE to list of child elements of the frame. + * + * \see childElements() + */ + void addChildElement(const ByteVector &cE); + + /*! + * Removes \a cE to list of child elements of the frame. + * + * \see childElements() + */ + void removeChildElement(const ByteVector &cE); + + /*! + * Returns a reference to the frame list map. This is an FrameListMap of + * all of the frames embedded in the CTOC frame. + * + * This is the most convenient structure for accessing the CTOC frame's + * embedded frames. Many frame types allow multiple instances of the same + * frame type so this is a map of lists. In most cases however there will + * only be a single frame of a certain type. + * + * \warning You should not modify this data structure directly, instead + * use addEmbeddedFrame() and removeEmbeddedFrame(). + * + * \see embeddedFrameList() + */ + const FrameListMap &embeddedFrameListMap() const; + + /*! + * Returns a reference to the embedded frame list. This is an FrameList + * of all of the frames embedded in the CTOC frame in the order that they + * were parsed. + * + * This can be useful if for example you want iterate over the CTOC frame's + * embedded frames in the order that they occur in the CTOC frame. + * + * \warning You should not modify this data structure directly, instead + * use addEmbeddedFrame() and removeEmbeddedFrame(). + */ + const FrameList &embeddedFrameList() const; + + /*! + * Returns the embedded frame list for frames with the id \a frameID + * or an empty list if there are no embedded frames of that type. This + * is just a convenience and is equivalent to: + * + * \code + * embeddedFrameListMap()[frameID]; + * \endcode + * + * \see embeddedFrameListMap() + */ + const FrameList &embeddedFrameList(const ByteVector &frameID) const; + + /*! + * Add an embedded frame to the CTOC frame. At this point the CTOC frame + * takes ownership of the embedded frame and will handle freeing its memory. + * + * \note Using this method will invalidate any pointers on the list + * returned by embeddedFrameList() + */ + void addEmbeddedFrame(Frame *frame); + + /*! + * Remove an embedded frame from the CTOC frame. If \a del is true the frame's + * memory will be freed; if it is false, it must be deleted by the user. + * + * \note Using this method will invalidate any pointers on the list + * returned by embeddedFrameList() + */ + void removeEmbeddedFrame(Frame *frame, bool del = true); + + /*! + * Remove all embedded frames of type \a id from the CTOC frame and free their + * memory. + * + * \note Using this method will invalidate any pointers on the list + * returned by embeddedFrameList() + */ + void removeEmbeddedFrames(const ByteVector &id); + + virtual String toString() const; + + PropertyMap asProperties() const; + + /*! + * CTOC frames each have a unique element ID. This searches for a CTOC + * frame with the element ID \a eID and returns a pointer to it. This + * can be used to link together parent and child CTOC frames. + * + * \see elementID() + */ + static TableOfContentsFrame *findByElementID(const Tag *tag, const ByteVector &eID); + + /*! + * CTOC frames each contain a flag that indicates, if CTOC frame is top-level (there isn't + * any frame, which contains this frame in its child elements list). Only a single frame + * within tag can be top-level. This searches for a top-level CTOC frame. + * + * \see isTopLevel() + */ + static TableOfContentsFrame *findTopLevel(const Tag *tag); + + protected: + virtual void parseFields(const ByteVector &data); + virtual ByteVector renderFields() const; + + private: + TableOfContentsFrame(const TableOfContentsFrame &); + TableOfContentsFrame &operator=(const TableOfContentsFrame &); + + TableOfContentsFrame(const ByteVector &data, Header *h); + + class TableOfContentsFramePrivate; + TableOfContentsFramePrivate *d; + }; + } +} + +#endif diff --git a/taglib/mpeg/id3v2/id3v2framefactory.cpp b/taglib/mpeg/id3v2/id3v2framefactory.cpp index b2c32ce4..9055be56 100644 --- a/taglib/mpeg/id3v2/id3v2framefactory.cpp +++ b/taglib/mpeg/id3v2/id3v2framefactory.cpp @@ -47,6 +47,8 @@ #include "frames/ownershipframe.h" #include "frames/synchronizedlyricsframe.h" #include "frames/eventtimingcodesframe.h" +#include "frames/chapterframe.h" +#include "frames/tableofcontentsframe.h" using namespace TagLib; using namespace ID3v2; @@ -274,6 +276,16 @@ Frame *FrameFactory::createFrame(const ByteVector &origData, Header *tagHeader) d->setTextEncoding(f); return f; } + + // Chapter (ID3v2 chapters 1.0) + + if(frameID == "CHAP") + return new ChapterFrame(data, header); + + // Table of contents (ID3v2 chapters 1.0) + + if(frameID == "CTOC") + return new TableOfContentsFrame(data, header); return new UnknownFrame(data, header); } diff --git a/tests/test_id3v2.cpp b/tests/test_id3v2.cpp index fadcae2f..2e04bdba 100644 --- a/tests/test_id3v2.cpp +++ b/tests/test_id3v2.cpp @@ -22,6 +22,8 @@ #include #include #include +#include +#include #include #include #include @@ -88,6 +90,10 @@ class TestID3v2 : public CppUnit::TestFixture CPPUNIT_TEST(testPropertyInterface2); CPPUNIT_TEST(testDeleteFrame); CPPUNIT_TEST(testSaveAndStripID3v1ShouldNotAddFrameFromID3v1ToId3v2); + CPPUNIT_TEST(testParseChapterFrame); + CPPUNIT_TEST(testRenderChapterFrame); + CPPUNIT_TEST(testParseTableOfContentsFrame); + CPPUNIT_TEST(testRenderTableOfContentsFrame); CPPUNIT_TEST_SUITE_END(); public: @@ -856,7 +862,120 @@ public: MPEG::File f(newname.c_str()); CPPUNIT_ASSERT(!f.ID3v2Tag()->frameListMap().contains("TPE1")); } - + + void testParseChapterFrame() + { + ID3v2::ChapterFrame f( + ByteVector("CHAP" // Frame ID + "\x00\x00\x00\x20" // Frame size + "\x00\x00" // Frame flags + "\x43\x00" // Element ID + "\x00\x00\x00\x03" // Start time + "\x00\x00\x00\x05" // End time + "\x00\x00\x00\x02" // Start offset + "\x00\x00\x00\x03" // End offset + "TIT2" // Embedded frame ID + "\x00\x00\x00\x04" // Embedded frame size + "\x00\x00" // Embedded frame flags + "\x00" // TIT2 frame text encoding + "CH1", 42)); // Chapter title + CPPUNIT_ASSERT_EQUAL(ByteVector("\x43\x00", 2), + f.elementID()); + CPPUNIT_ASSERT((uint)0x03 == f.startTime()); + CPPUNIT_ASSERT((uint)0x05 == f.endTime()); + CPPUNIT_ASSERT((uint)0x02 == f.startOffset()); + CPPUNIT_ASSERT((uint)0x03 == f.endOffset()); + CPPUNIT_ASSERT((uint)0x01 == f.embeddedFrameList().size()); + CPPUNIT_ASSERT(f.embeddedFrameList("TIT2").size() == 1); + CPPUNIT_ASSERT(f.embeddedFrameList("TIT2")[0]->toString() == "CH1"); + } + + void testRenderChapterFrame() + { + ID3v2::ChapterFrame f("CHAP"); + f.setElementID(ByteVector("\x43\x00", 2)); + f.setStartTime(3); + f.setEndTime(5); + f.setStartOffset(2); + f.setEndOffset(3); + ID3v2::TextIdentificationFrame eF("TIT2"); + eF.setText("CH1"); + f.addEmbeddedFrame(&eF); + CPPUNIT_ASSERT_EQUAL( + ByteVector("CHAP" // Frame ID + "\x00\x00\x00\x20" // Frame size + "\x00\x00" // Frame flags + "\x43\x00" // Element ID + "\x00\x00\x00\x03" // Start time + "\x00\x00\x00\x05" // End time + "\x00\x00\x00\x02" // Start offset + "\x00\x00\x00\x03" // End offset + "TIT2" // Embedded frame ID + "\x00\x00\x00\x04" // Embedded frame size + "\x00\x00" // Embedded frame flags + "\x00" // TIT2 frame text encoding + "CH1", 42), // Chapter title + f.render()); + } + + void testParseTableOfContentsFrame() + { + ID3v2::TableOfContentsFrame f( + ByteVector("CTOC" // Frame ID + "\x00\x00\x00\x16" // Frame size + "\x00\x00" // Frame flags + "\x54\x00" // Element ID + "\x01" // CTOC flags + "\x02" // Entry count + "\x43\x00" // First entry + "\x44\x00" // Second entry + "TIT2" // Embedded frame ID + "\x00\x00\x00\x04" // Embedded frame size + "\x00\x00" // Embedded frame flags + "\x00" // TIT2 frame text encoding + "TC1", 32)); // Table of contents title + CPPUNIT_ASSERT_EQUAL(ByteVector("\x54\x00", 2), + f.elementID()); + CPPUNIT_ASSERT(!f.isTopLevel()); + CPPUNIT_ASSERT(f.isOrdered()); + CPPUNIT_ASSERT((uint)0x02 == f.entryCount()); + CPPUNIT_ASSERT_EQUAL(ByteVector("\x43\x00", 2), + f.childElements()[0]); + CPPUNIT_ASSERT_EQUAL(ByteVector("\x44\x00", 2), + f.childElements()[1]); + CPPUNIT_ASSERT((uint)0x01 == f.embeddedFrameList().size()); + CPPUNIT_ASSERT(f.embeddedFrameList("TIT2").size() == 1); + CPPUNIT_ASSERT(f.embeddedFrameList("TIT2")[0]->toString() == "TC1"); + } + + void testRenderTableOfContentsFrame() + { + ID3v2::TableOfContentsFrame f("CTOC"); + f.setElementID(ByteVector("\x54\x00", 2)); + f.setIsTopLevel(false); + f.setIsOrdered(true); + f.addChildElement(ByteVector("\x43\x00", 2)); + f.addChildElement(ByteVector("\x44\x00", 2)); + ID3v2::TextIdentificationFrame eF("TIT2"); + eF.setText("TC1"); + f.addEmbeddedFrame(&eF); + CPPUNIT_ASSERT_EQUAL( + ByteVector("CTOC" // Frame ID + "\x00\x00\x00\x16" // Frame size + "\x00\x00" // Frame flags + "\x54\x00" // Element ID + "\x01" // CTOC flags + "\x02" // Entry count + "\x43\x00" // First entry + "\x44\x00" // Second entry + "TIT2" // Embedded frame ID + "\x00\x00\x00\x04" // Embedded frame size + "\x00\x00" // Embedded frame flags + "\x00" // TIT2 frame text encoding + "TC1", 32), // Table of contents title + f.render()); + } + }; CPPUNIT_TEST_SUITE_REGISTRATION(TestID3v2);