From 08ae0e8c6376523a854fe5552d1df12993f84ac1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Krej=C4=8D=C3=AD?= Date: Sat, 20 Apr 2013 15:52:52 +0200 Subject: [PATCH 01/19] Created CPP and H files for CTOC and CHAP frames. --- taglib/mpeg/id3v2/frames/chapterframe.cpp | 148 +++++++++++++++ taglib/mpeg/id3v2/frames/chapterframe.h | 168 ++++++++++++++++++ .../id3v2/frames/tableofcontentsframe.cpp | 148 +++++++++++++++ .../mpeg/id3v2/frames/tableofcontentsframe.h | 162 +++++++++++++++++ 4 files changed, 626 insertions(+) create mode 100644 taglib/mpeg/id3v2/frames/chapterframe.cpp create mode 100644 taglib/mpeg/id3v2/frames/chapterframe.h create mode 100644 taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp create mode 100644 taglib/mpeg/id3v2/frames/tableofcontentsframe.h diff --git a/taglib/mpeg/id3v2/frames/chapterframe.cpp b/taglib/mpeg/id3v2/frames/chapterframe.cpp new file mode 100644 index 00000000..a0e842e0 --- /dev/null +++ b/taglib/mpeg/id3v2/frames/chapterframe.cpp @@ -0,0 +1,148 @@ +/*************************************************************************** + copyright : (C) 2002 - 2008 by Scott Wheeler + email : wheeler@kde.org + ***************************************************************************/ + +/*************************************************************************** + * 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 "id3v2tag.h" +#include "uniquefileidentifierframe.h" + +using namespace TagLib; +using namespace ID3v2; + +class UniqueFileIdentifierFrame::UniqueFileIdentifierFramePrivate +{ +public: + String owner; + ByteVector identifier; +}; + +//////////////////////////////////////////////////////////////////////////////// +// public methods +//////////////////////////////////////////////////////////////////////////////// + +UniqueFileIdentifierFrame::UniqueFileIdentifierFrame(const ByteVector &data) : + ID3v2::Frame(data) +{ + d = new UniqueFileIdentifierFramePrivate; + setData(data); +} + +UniqueFileIdentifierFrame::UniqueFileIdentifierFrame(const String &owner, const ByteVector &id) : + ID3v2::Frame("UFID") +{ + d = new UniqueFileIdentifierFramePrivate; + d->owner = owner; + d->identifier = id; +} + +UniqueFileIdentifierFrame::~UniqueFileIdentifierFrame() +{ + delete d; +} + +String UniqueFileIdentifierFrame::owner() const +{ + return d->owner; +} + +ByteVector UniqueFileIdentifierFrame::identifier() const +{ + return d->identifier; +} + +void UniqueFileIdentifierFrame::setOwner(const String &s) +{ + d->owner = s; +} + +void UniqueFileIdentifierFrame::setIdentifier(const ByteVector &v) +{ + d->identifier = v; +} + +String UniqueFileIdentifierFrame::toString() const +{ + return String::null; +} + +PropertyMap UniqueFileIdentifierFrame::asProperties() const +{ + PropertyMap map; + if(d->owner == "http://musicbrainz.org") { + map.insert("MUSICBRAINZ_TRACKID", String(d->identifier)); + } + else { + map.unsupportedData().append(frameID() + String("/") + d->owner); + } + return map; +} + +UniqueFileIdentifierFrame *UniqueFileIdentifierFrame::findByOwner(const ID3v2::Tag *tag, const String &o) // static +{ + ID3v2::FrameList comments = tag->frameList("UFID"); + + for(ID3v2::FrameList::ConstIterator it = comments.begin(); + it != comments.end(); + ++it) + { + UniqueFileIdentifierFrame *frame = dynamic_cast(*it); + if(frame && frame->owner() == o) + return frame; + } + + return 0; +} + +void UniqueFileIdentifierFrame::parseFields(const ByteVector &data) +{ + if(data.size() < 1) { + debug("An UFID frame must contain at least 1 byte."); + return; + } + + int pos = 0; + d->owner = readStringField(data, String::Latin1, &pos); + d->identifier = data.mid(pos); +} + +ByteVector UniqueFileIdentifierFrame::renderFields() const +{ + ByteVector data; + + data.append(d->owner.data(String::Latin1)); + data.append(char(0)); + data.append(d->identifier); + + return data; +} + +UniqueFileIdentifierFrame::UniqueFileIdentifierFrame(const ByteVector &data, Header *h) : + Frame(h) +{ + d = new UniqueFileIdentifierFramePrivate; + 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..192711ce --- /dev/null +++ b/taglib/mpeg/id3v2/frames/chapterframe.h @@ -0,0 +1,168 @@ +/*************************************************************************** + copyright : (C) 2002 - 2008 by Scott Wheeler + email : wheeler@kde.org + ***************************************************************************/ + +/*************************************************************************** + * 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 "id3v2frame.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 + * and end offset \a eO. + */ + ChapterFrame(const ByteVector &eID, const int &sT, const int &eT, const int &sO, const int &eO); + + /*! + * Destroys the frame. + */ + ~ChapterFrame(); + + /*! + * 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 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. + * + * \see setStartOffset() + */ + uint startOffset() const; + + /*! + * Returns zero based byte offset (count of bytes from the beginning + * of the audio file) of chapter's end. + * + * \see setEndOffset() + */ + uint endOffset() const; + + /*! + * Sets the elementID of the frame to \a eID. + * + * \warning Element ID must be null terminated. + * \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 endOffset(const uint &eO); + + 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. + * + * \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..a0e842e0 --- /dev/null +++ b/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp @@ -0,0 +1,148 @@ +/*************************************************************************** + copyright : (C) 2002 - 2008 by Scott Wheeler + email : wheeler@kde.org + ***************************************************************************/ + +/*************************************************************************** + * 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 "id3v2tag.h" +#include "uniquefileidentifierframe.h" + +using namespace TagLib; +using namespace ID3v2; + +class UniqueFileIdentifierFrame::UniqueFileIdentifierFramePrivate +{ +public: + String owner; + ByteVector identifier; +}; + +//////////////////////////////////////////////////////////////////////////////// +// public methods +//////////////////////////////////////////////////////////////////////////////// + +UniqueFileIdentifierFrame::UniqueFileIdentifierFrame(const ByteVector &data) : + ID3v2::Frame(data) +{ + d = new UniqueFileIdentifierFramePrivate; + setData(data); +} + +UniqueFileIdentifierFrame::UniqueFileIdentifierFrame(const String &owner, const ByteVector &id) : + ID3v2::Frame("UFID") +{ + d = new UniqueFileIdentifierFramePrivate; + d->owner = owner; + d->identifier = id; +} + +UniqueFileIdentifierFrame::~UniqueFileIdentifierFrame() +{ + delete d; +} + +String UniqueFileIdentifierFrame::owner() const +{ + return d->owner; +} + +ByteVector UniqueFileIdentifierFrame::identifier() const +{ + return d->identifier; +} + +void UniqueFileIdentifierFrame::setOwner(const String &s) +{ + d->owner = s; +} + +void UniqueFileIdentifierFrame::setIdentifier(const ByteVector &v) +{ + d->identifier = v; +} + +String UniqueFileIdentifierFrame::toString() const +{ + return String::null; +} + +PropertyMap UniqueFileIdentifierFrame::asProperties() const +{ + PropertyMap map; + if(d->owner == "http://musicbrainz.org") { + map.insert("MUSICBRAINZ_TRACKID", String(d->identifier)); + } + else { + map.unsupportedData().append(frameID() + String("/") + d->owner); + } + return map; +} + +UniqueFileIdentifierFrame *UniqueFileIdentifierFrame::findByOwner(const ID3v2::Tag *tag, const String &o) // static +{ + ID3v2::FrameList comments = tag->frameList("UFID"); + + for(ID3v2::FrameList::ConstIterator it = comments.begin(); + it != comments.end(); + ++it) + { + UniqueFileIdentifierFrame *frame = dynamic_cast(*it); + if(frame && frame->owner() == o) + return frame; + } + + return 0; +} + +void UniqueFileIdentifierFrame::parseFields(const ByteVector &data) +{ + if(data.size() < 1) { + debug("An UFID frame must contain at least 1 byte."); + return; + } + + int pos = 0; + d->owner = readStringField(data, String::Latin1, &pos); + d->identifier = data.mid(pos); +} + +ByteVector UniqueFileIdentifierFrame::renderFields() const +{ + ByteVector data; + + data.append(d->owner.data(String::Latin1)); + data.append(char(0)); + data.append(d->identifier); + + return data; +} + +UniqueFileIdentifierFrame::UniqueFileIdentifierFrame(const ByteVector &data, Header *h) : + Frame(h) +{ + d = new UniqueFileIdentifierFramePrivate; + 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..54fb68b4 --- /dev/null +++ b/taglib/mpeg/id3v2/frames/tableofcontentsframe.h @@ -0,0 +1,162 @@ +/*************************************************************************** + copyright : (C) 2002 - 2008 by Scott Wheeler + email : wheeler@kde.org + ***************************************************************************/ + +/*************************************************************************** + * 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 "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 and + * the child elements \a ch. + */ + UniqueFileIdentifierFrame(const ByteVector &eID, const List &ch); + + /*! + * Destroys the frame. + */ + ~UniqueFileIdentifierFrame(); + + /*! + * 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. + * + * \note Return type should be uint8_t. + * \see childElements() + */ + unsigned char entryCount() const; + + /*! + * Returns list of child elements of the frame. + * + * \see setChildElements() + */ + List childElements() const; + + /*! + * Sets the elementID of the frame to \a eID. + * + * \warning Element ID must be null terminated. + * \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 List &l); + + 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. + * + * \see elementID() + */ + static UniqueFileIdentifierFrame *findByElementID(const Tag *tag, const ByteVector &eID); + + 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 From 4be12794305c7db11df46c22d79b4eb17a98a07f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Krej=C4=8D=C3=AD?= Date: Sat, 20 Apr 2013 16:49:57 +0200 Subject: [PATCH 02/19] Added basic members of ChapterFrame and TableOfContentsFrame classes. Fixed minor bugs in ChapterFrame and TableOfContentsFrame headers. --- taglib/mpeg/id3v2/frames/chapterframe.cpp | 97 +++++++++++++------ taglib/mpeg/id3v2/frames/chapterframe.h | 5 +- .../id3v2/frames/tableofcontentsframe.cpp | 90 +++++++++++------ .../mpeg/id3v2/frames/tableofcontentsframe.h | 15 +-- 4 files changed, 139 insertions(+), 68 deletions(-) diff --git a/taglib/mpeg/id3v2/frames/chapterframe.cpp b/taglib/mpeg/id3v2/frames/chapterframe.cpp index a0e842e0..1474db1a 100644 --- a/taglib/mpeg/id3v2/frames/chapterframe.cpp +++ b/taglib/mpeg/id3v2/frames/chapterframe.cpp @@ -28,60 +28,96 @@ #include #include "id3v2tag.h" -#include "uniquefileidentifierframe.h" +#include "chapterframe.h" using namespace TagLib; using namespace ID3v2; -class UniqueFileIdentifierFrame::UniqueFileIdentifierFramePrivate +class ChapterFrame::ChapterFramePrivate { public: - String owner; - ByteVector identifier; + ByteVector elementID; + uint startTime; + uint endTime; + uint startOffset; + uint endOffset; }; //////////////////////////////////////////////////////////////////////////////// // public methods //////////////////////////////////////////////////////////////////////////////// -UniqueFileIdentifierFrame::UniqueFileIdentifierFrame(const ByteVector &data) : +ChapterFrame::ChapterFrame(const ByteVector &data) : ID3v2::Frame(data) { - d = new UniqueFileIdentifierFramePrivate; + d = new ChapterFramePrivate; setData(data); } -UniqueFileIdentifierFrame::UniqueFileIdentifierFrame(const String &owner, const ByteVector &id) : - ID3v2::Frame("UFID") +ChapterFrame::ChapterFrame(const ByteVector &eID, const int &sT, const int &eT, const int &sO, const int &eO) : + ID3v2::Frame("CHAP") { - d = new UniqueFileIdentifierFramePrivate; - d->owner = owner; - d->identifier = id; + d = new ChapterFramePrivate; + d->elementID = eID; + d->startTime = sT; + d->endTime = eT; + d->startOffset = sO; + d->endOffset = e0; } -UniqueFileIdentifierFrame::~UniqueFileIdentifierFrame() +ChapterFrame::~ChapterFrame() { delete d; } -String UniqueFileIdentifierFrame::owner() const +ByteVector ChapterFrame::elementID() const { - return d->owner; + return d->elementID; } -ByteVector UniqueFileIdentifierFrame::identifier() const +uint ChapterFrame::startTime() const { - return d->identifier; + return d->startTime; } -void UniqueFileIdentifierFrame::setOwner(const String &s) +uint ChapterFrame::endTime() const { - d->owner = s; + return d->endTime; } -void UniqueFileIdentifierFrame::setIdentifier(const ByteVector &v) +uint ChapterFrame::startOffset() const { - d->identifier = v; + return d->startOffset; +} + +uint ChapterFrame::endOffset() const +{ + return d->endOffset; +} + +void ChapterFrame::setElementID(const ByteVector &eID) +{ + d->elementID = eID; +} + +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; } String UniqueFileIdentifierFrame::toString() const @@ -89,8 +125,9 @@ String UniqueFileIdentifierFrame::toString() const return String::null; } -PropertyMap UniqueFileIdentifierFrame::asProperties() const +PropertyMap ChapterFrame::asProperties() const { + //DODELAT PropertyMap map; if(d->owner == "http://musicbrainz.org") { map.insert("MUSICBRAINZ_TRACKID", String(d->identifier)); @@ -101,24 +138,25 @@ PropertyMap UniqueFileIdentifierFrame::asProperties() const return map; } -UniqueFileIdentifierFrame *UniqueFileIdentifierFrame::findByOwner(const ID3v2::Tag *tag, const String &o) // static +ChapterFrame *ChapterFrame::findByElementID(const Tag *tag, const ByteVector &eID) // static { - ID3v2::FrameList comments = tag->frameList("UFID"); + ID3v2::FrameList comments = tag->frameList("CHAP"); for(ID3v2::FrameList::ConstIterator it = comments.begin(); it != comments.end(); ++it) { - UniqueFileIdentifierFrame *frame = dynamic_cast(*it); - if(frame && frame->owner() == o) + ChapterFrame *frame = dynamic_cast(*it); + if(frame && frame->elementID() == eID) return frame; } return 0; } -void UniqueFileIdentifierFrame::parseFields(const ByteVector &data) +void ChapterFrame::parseFields(const ByteVector &data) { + //DODELAT if(data.size() < 1) { debug("An UFID frame must contain at least 1 byte."); return; @@ -129,8 +167,9 @@ void UniqueFileIdentifierFrame::parseFields(const ByteVector &data) d->identifier = data.mid(pos); } -ByteVector UniqueFileIdentifierFrame::renderFields() const +ByteVector ChapterFrame::renderFields() const { + //DODELAT ByteVector data; data.append(d->owner.data(String::Latin1)); @@ -140,9 +179,9 @@ ByteVector UniqueFileIdentifierFrame::renderFields() const return data; } -UniqueFileIdentifierFrame::UniqueFileIdentifierFrame(const ByteVector &data, Header *h) : +ChapterFrame::ChapterFrame(const ByteVector &data, Header *h) : Frame(h) { - d = new UniqueFileIdentifierFramePrivate; + d = new ChapterFramePrivate; parseFields(fieldData(data)); } diff --git a/taglib/mpeg/id3v2/frames/chapterframe.h b/taglib/mpeg/id3v2/frames/chapterframe.h index 192711ce..ce9096cf 100644 --- a/taglib/mpeg/id3v2/frames/chapterframe.h +++ b/taglib/mpeg/id3v2/frames/chapterframe.h @@ -54,7 +54,7 @@ namespace TagLib { * start time \a sT, end time \a eT, start offset \a sO * and end offset \a eO. */ - ChapterFrame(const ByteVector &eID, const int &sT, const int &eT, const int &sO, const int &eO); + ChapterFrame(const ByteVector &eID, const uint &sT, const uint &eT, const uint &sO, const uint &eO); /*! * Destroys the frame. @@ -143,7 +143,8 @@ namespace TagLib { /*! * 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. + * 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() */ diff --git a/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp b/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp index a0e842e0..1cb24b50 100644 --- a/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp +++ b/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp @@ -28,69 +28,97 @@ #include #include "id3v2tag.h" -#include "uniquefileidentifierframe.h" +#include "tableofcontentsframe.h" using namespace TagLib; using namespace ID3v2; -class UniqueFileIdentifierFrame::UniqueFileIdentifierFramePrivate +class TableOfContentsFrame::TableOfContentsFramePrivate { public: - String owner; - ByteVector identifier; + ByteVector elementID; + bool isTopLevel; + bool isOrdered; + ByteVectorList childElements; }; //////////////////////////////////////////////////////////////////////////////// // public methods //////////////////////////////////////////////////////////////////////////////// -UniqueFileIdentifierFrame::UniqueFileIdentifierFrame(const ByteVector &data) : +TableOfContentsFrame::TableOfContentsFrame(const ByteVector &data) : ID3v2::Frame(data) { - d = new UniqueFileIdentifierFramePrivate; + d = new TableOfContentsFramePrivate; setData(data); } -UniqueFileIdentifierFrame::UniqueFileIdentifierFrame(const String &owner, const ByteVector &id) : - ID3v2::Frame("UFID") +TableOfContentsFrame::TableOfContentsFrame(const ByteVector &eID, const ByteVectorList &ch) : + ID3v2::Frame("CTOC") { - d = new UniqueFileIdentifierFramePrivate; - d->owner = owner; - d->identifier = id; + d = new TableOfContentsFramePrivate; + d->elementID = eID; + d->childElements = ch; } -UniqueFileIdentifierFrame::~UniqueFileIdentifierFrame() +TableOfContentsFrame::~TableOfContentsFrame() { delete d; } -String UniqueFileIdentifierFrame::owner() const +ByteVector TableOfContentsFrame::elementID() const { - return d->owner; + return d->elementID; } -ByteVector UniqueFileIdentifierFrame::identifier() const +bool TableOfContentsFrame::isTopLevel() const { - return d->identifier; + return d->isTopLevel; } -void UniqueFileIdentifierFrame::setOwner(const String &s) +bool TableOfContentsFrame::isOrdered() const { - d->owner = s; + return d->isOrdered; } -void UniqueFileIdentifierFrame::setIdentifier(const ByteVector &v) +unsigned char TableOfContentsFrame::entryCount() const { - d->identifier = v; + return (unsigned char)(d->childElements.size()); } -String UniqueFileIdentifierFrame::toString() const +ByteVectorList TableOfContentsFrame::childElements const +{ + return d->childElements; +} + +void TableOfContentsFrame::setElementID(const ByteVector &eID) +{ + d->elementID = eID; +} + +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; +} + +String TableOfContentsFrame::toString() const { return String::null; } -PropertyMap UniqueFileIdentifierFrame::asProperties() const +PropertyMap TableOfContentsFrame::asProperties() const { + //DODELAT PropertyMap map; if(d->owner == "http://musicbrainz.org") { map.insert("MUSICBRAINZ_TRACKID", String(d->identifier)); @@ -101,24 +129,25 @@ PropertyMap UniqueFileIdentifierFrame::asProperties() const return map; } -UniqueFileIdentifierFrame *UniqueFileIdentifierFrame::findByOwner(const ID3v2::Tag *tag, const String &o) // static +TableOfContentsFrame *TableOfContentsFrame::findByElementID(const ID3v2::Tag *tag, const ByteVector &eID) // static { - ID3v2::FrameList comments = tag->frameList("UFID"); + ID3v2::FrameList comments = tag->frameList("CTOC"); for(ID3v2::FrameList::ConstIterator it = comments.begin(); it != comments.end(); ++it) { - UniqueFileIdentifierFrame *frame = dynamic_cast(*it); - if(frame && frame->owner() == o) + TableOfContentsFrame *frame = dynamic_cast(*it); + if(frame && frame->elementID() == eID) return frame; } return 0; } -void UniqueFileIdentifierFrame::parseFields(const ByteVector &data) +void TableOfContentsFrame::parseFields(const ByteVector &data) { + //DODELAT if(data.size() < 1) { debug("An UFID frame must contain at least 1 byte."); return; @@ -129,8 +158,9 @@ void UniqueFileIdentifierFrame::parseFields(const ByteVector &data) d->identifier = data.mid(pos); } -ByteVector UniqueFileIdentifierFrame::renderFields() const +ByteVector TableOfContentsFrame::renderFields() const { + //DODELAT ByteVector data; data.append(d->owner.data(String::Latin1)); @@ -140,9 +170,9 @@ ByteVector UniqueFileIdentifierFrame::renderFields() const return data; } -UniqueFileIdentifierFrame::UniqueFileIdentifierFrame(const ByteVector &data, Header *h) : +TableOfContentsFrame::TableOfContentsFrame(const ByteVector &data, Header *h) : Frame(h) { - d = new UniqueFileIdentifierFramePrivate; + d = new TableOfContentsFramePrivate; parseFields(fieldData(data)); } diff --git a/taglib/mpeg/id3v2/frames/tableofcontentsframe.h b/taglib/mpeg/id3v2/frames/tableofcontentsframe.h index 54fb68b4..0d189dfc 100644 --- a/taglib/mpeg/id3v2/frames/tableofcontentsframe.h +++ b/taglib/mpeg/id3v2/frames/tableofcontentsframe.h @@ -53,12 +53,12 @@ namespace TagLib { * Creates a table of contents frame with the element ID \a eID and * the child elements \a ch. */ - UniqueFileIdentifierFrame(const ByteVector &eID, const List &ch); + TableOfContentsFrame(const ByteVector &eID, const ByteVectorList &ch); /*! * Destroys the frame. */ - ~UniqueFileIdentifierFrame(); + ~TableOfContentsFrame(); /*! * Returns the elementID of the frame. Element ID @@ -88,7 +88,7 @@ namespace TagLib { * Returns count of child elements of the frame. It allways * corresponds to size of child elements list. * - * \note Return type should be uint8_t. + * \note Return type should be uint8_t, not unsigned char. * \see childElements() */ unsigned char entryCount() const; @@ -98,7 +98,7 @@ namespace TagLib { * * \see setChildElements() */ - List childElements() const; + ByteVectorList childElements() const; /*! * Sets the elementID of the frame to \a eID. @@ -129,7 +129,7 @@ namespace TagLib { * * \see childElements() */ - void setChildElements(const List &l); + void setChildElements(const ByteVectorList &l); virtual String toString() const; @@ -137,11 +137,12 @@ namespace TagLib { /*! * 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. + * 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 UniqueFileIdentifierFrame *findByElementID(const Tag *tag, const ByteVector &eID); + static TableOfContentsFrame *findByElementID(const Tag *tag, const ByteVector &eID); protected: virtual void parseFields(const ByteVector &data); From fcfd9f59fefd434c8947008448505bb1f6e1d476 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Krej=C4=8D=C3=AD?= Date: Sun, 21 Apr 2013 16:16:57 +0200 Subject: [PATCH 03/19] Finished parseFields, renderFields and asProperty methods of ChapterFrame and TableOfContentsFrame classes. Methods setElementID of ChapterFrame and TableOfContentsFrame classes now automatically terminates new element ID with null. --- taglib/mpeg/id3v2/frames/chapterframe.cpp | 41 +++++----- taglib/mpeg/id3v2/frames/chapterframe.h | 6 +- .../id3v2/frames/tableofcontentsframe.cpp | 75 +++++++++++++------ .../mpeg/id3v2/frames/tableofcontentsframe.h | 16 +++- taglib/mpeg/id3v2/id3v2framefactory.cpp | 12 +++ 5 files changed, 104 insertions(+), 46 deletions(-) diff --git a/taglib/mpeg/id3v2/frames/chapterframe.cpp b/taglib/mpeg/id3v2/frames/chapterframe.cpp index 1474db1a..633a6d8b 100644 --- a/taglib/mpeg/id3v2/frames/chapterframe.cpp +++ b/taglib/mpeg/id3v2/frames/chapterframe.cpp @@ -98,6 +98,8 @@ uint ChapterFrame::endOffset() const 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) @@ -120,21 +122,17 @@ void ChapterFrame::setEndOffset(const uint &eO) d->endOffset = eO; } -String UniqueFileIdentifierFrame::toString() const +String ChapterFrame::toString() const { return String::null; } -PropertyMap ChapterFrame::asProperties() const +PropertyMap UniqueFileIdentifierFrame::asProperties() const { - //DODELAT PropertyMap map; - if(d->owner == "http://musicbrainz.org") { - map.insert("MUSICBRAINZ_TRACKID", String(d->identifier)); - } - else { - map.unsupportedData().append(frameID() + String("/") + d->owner); - } + + map.unsupportedData().append(frameID() + String("/") + d->elementID); + return map; } @@ -156,25 +154,32 @@ ChapterFrame *ChapterFrame::findByElementID(const Tag *tag, const ByteVector &eI void ChapterFrame::parseFields(const ByteVector &data) { - //DODELAT - if(data.size() < 1) { - debug("An UFID frame must contain at least 1 byte."); + if(data.size() < 18) { + debug("An 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; - d->owner = readStringField(data, String::Latin1, &pos); - d->identifier = data.mid(pos); + 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); } ByteVector ChapterFrame::renderFields() const { - //DODELAT ByteVector data; - data.append(d->owner.data(String::Latin1)); - data.append(char(0)); - data.append(d->identifier); + 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)); return data; } diff --git a/taglib/mpeg/id3v2/frames/chapterframe.h b/taglib/mpeg/id3v2/frames/chapterframe.h index ce9096cf..1ecbbbfd 100644 --- a/taglib/mpeg/id3v2/frames/chapterframe.h +++ b/taglib/mpeg/id3v2/frames/chapterframe.h @@ -62,7 +62,7 @@ namespace TagLib { ~ChapterFrame(); /*! - * Returns the elementID of the frame. Element ID + * Returns the element ID of the frame. Element ID * is a null terminated string, however it's not human-readable. * * \see setElementID() @@ -100,9 +100,9 @@ namespace TagLib { uint endOffset() const; /*! - * Sets the elementID of the frame to \a eID. + * Sets the element ID of the frame to \a eID. If \a eID isn't + * null terminated, a null char is appended automatically. * - * \warning Element ID must be null terminated. * \see elementID() */ void setElementID(const ByteVector &eID); diff --git a/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp b/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp index 1cb24b50..39df25d6 100644 --- a/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp +++ b/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp @@ -81,9 +81,9 @@ bool TableOfContentsFrame::isOrdered() const return d->isOrdered; } -unsigned char TableOfContentsFrame::entryCount() const +uint TableOfContentsFrame::entryCount() const { - return (unsigned char)(d->childElements.size()); + return d->childElements.size(); } ByteVectorList TableOfContentsFrame::childElements const @@ -94,6 +94,8 @@ ByteVectorList TableOfContentsFrame::childElements const 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) @@ -118,23 +120,19 @@ String TableOfContentsFrame::toString() const PropertyMap TableOfContentsFrame::asProperties() const { - //DODELAT PropertyMap map; - if(d->owner == "http://musicbrainz.org") { - map.insert("MUSICBRAINZ_TRACKID", String(d->identifier)); - } - else { - map.unsupportedData().append(frameID() + String("/") + d->owner); - } + + map.unsupportedData().append(frameID() + String("/") + d->elementID); + return map; } TableOfContentsFrame *TableOfContentsFrame::findByElementID(const ID3v2::Tag *tag, const ByteVector &eID) // static { - ID3v2::FrameList comments = tag->frameList("CTOC"); + ID3v2::FrameList tablesOfContents = tag->frameList("CTOC"); - for(ID3v2::FrameList::ConstIterator it = comments.begin(); - it != comments.end(); + for(ID3v2::FrameList::ConstIterator it = tablesOfContents.begin(); + it != tablesOfContents.end(); ++it) { TableOfContentsFrame *frame = dynamic_cast(*it); @@ -145,28 +143,63 @@ TableOfContentsFrame *TableOfContentsFrame::findByElementID(const ID3v2::Tag *ta 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) { - //DODELAT - if(data.size() < 1) { - debug("An UFID frame must contain at least 1 byte."); + if(data.size() < 6) { + debug("An 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; - d->owner = readStringField(data, String::Latin1, &pos); - d->identifier = data.mid(pos); + 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(int i = 0; i < entryCount; i++) + { + ByteVector childElementID = readStringField(data, String::Latin1, &pos).data(String::Latin1); + childElementID.append(char(0)); + d->childElements.append(childElementID); + } } ByteVector TableOfContentsFrame::renderFields() const { - //DODELAT ByteVector data; - data.append(d->owner.data(String::Latin1)); + data.append(d->elementID); data.append(char(0)); - data.append(d->identifier); - + char flags = 0; + if(d->isTopLevel) + flags += 2; + if(d->isOrdered) + flags += 1; + data.append(flags); + data.append((char)(entryCount())); + ConstIterator it = d->childElements.begin(); + while(it != d->childElements.end()) { + data.append(*it); + data.append(char(0)); + it++; + } + return data; } diff --git a/taglib/mpeg/id3v2/frames/tableofcontentsframe.h b/taglib/mpeg/id3v2/frames/tableofcontentsframe.h index 0d189dfc..63ab96ee 100644 --- a/taglib/mpeg/id3v2/frames/tableofcontentsframe.h +++ b/taglib/mpeg/id3v2/frames/tableofcontentsframe.h @@ -88,10 +88,9 @@ namespace TagLib { * Returns count of child elements of the frame. It allways * corresponds to size of child elements list. * - * \note Return type should be uint8_t, not unsigned char. * \see childElements() */ - unsigned char entryCount() const; + uint entryCount() const; /*! * Returns list of child elements of the frame. @@ -101,9 +100,9 @@ namespace TagLib { ByteVectorList childElements() const; /*! - * Sets the elementID of the frame to \a eID. + * Sets the elementID of the frame to \a eID. If \a eID isn't + * null terminated, a null char is appended automatically. * - * \warning Element ID must be null terminated. * \see elementID() */ void setElementID(const ByteVector &eID); @@ -143,6 +142,15 @@ namespace TagLib { * \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); diff --git a/taglib/mpeg/id3v2/id3v2framefactory.cpp b/taglib/mpeg/id3v2/id3v2framefactory.cpp index c7d74214..05eb7f21 100644 --- a/taglib/mpeg/id3v2/id3v2framefactory.cpp +++ b/taglib/mpeg/id3v2/id3v2framefactory.cpp @@ -45,6 +45,8 @@ #include "frames/popularimeterframe.h" #include "frames/privateframe.h" #include "frames/ownershipframe.h" +#include "frames/chapterframe.h" +#include "frames/tableofcontentsframe.h" using namespace TagLib; using namespace ID3v2; @@ -258,6 +260,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); } From bcad792e759ad548cae3ad3268e786b9a7bc273c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Krej=C4=8D=C3=AD?= Date: Fri, 26 Apr 2013 23:16:06 +0200 Subject: [PATCH 04/19] Fixed error in childElements function. --- taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp b/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp index 39df25d6..bb43f5a6 100644 --- a/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp +++ b/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp @@ -86,7 +86,7 @@ uint TableOfContentsFrame::entryCount() const return d->childElements.size(); } -ByteVectorList TableOfContentsFrame::childElements const +ByteVectorList TableOfContentsFrame::childElements() const { return d->childElements; } From 4815dbba68fd889e18afdfc05b61edc95a64f472 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Krej=C4=8D=C3=AD?= Date: Sat, 27 Apr 2013 15:42:23 +0200 Subject: [PATCH 05/19] Fixed errors in ChapterFrame constructor. Fixed errors in ChapterFrame method renderFields. Fixed errors in TableOfContentsFrame method parseFields. Added ChapterFrame and TableOfContentsFrame headers and sources to CMakeLists.txt. Added some basic testing of CHAP and CTOC frames parsing. --- taglib/CMakeLists.txt | 4 ++ taglib/mpeg/id3v2/frames/chapterframe.cpp | 14 ++-- taglib/mpeg/id3v2/frames/chapterframe.h | 5 +- .../id3v2/frames/tableofcontentsframe.cpp | 6 +- tests/test_id3v2.cpp | 64 +++++++++++++++++++ 5 files changed, 82 insertions(+), 11 deletions(-) diff --git a/taglib/CMakeLists.txt b/taglib/CMakeLists.txt index a940caf5..2789f749 100644 --- a/taglib/CMakeLists.txt +++ b/taglib/CMakeLists.txt @@ -76,6 +76,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 @@ -166,6 +168,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 index 633a6d8b..497f1a41 100644 --- a/taglib/mpeg/id3v2/frames/chapterframe.cpp +++ b/taglib/mpeg/id3v2/frames/chapterframe.cpp @@ -54,7 +54,7 @@ ChapterFrame::ChapterFrame(const ByteVector &data) : setData(data); } -ChapterFrame::ChapterFrame(const ByteVector &eID, const int &sT, const int &eT, const int &sO, const int &eO) : +ChapterFrame::ChapterFrame(const ByteVector &eID, const uint &sT, const uint &eT, const uint &sO, const uint &eO) : ID3v2::Frame("CHAP") { d = new ChapterFramePrivate; @@ -62,7 +62,7 @@ ChapterFrame::ChapterFrame(const ByteVector &eID, const int &sT, const int &eT, d->startTime = sT; d->endTime = eT; d->startOffset = sO; - d->endOffset = e0; + d->endOffset = eO; } ChapterFrame::~ChapterFrame() @@ -127,7 +127,7 @@ String ChapterFrame::toString() const return String::null; } -PropertyMap UniqueFileIdentifierFrame::asProperties() const +PropertyMap ChapterFrame::asProperties() const { PropertyMap map; @@ -176,10 +176,10 @@ 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)); + 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)); return data; } diff --git a/taglib/mpeg/id3v2/frames/chapterframe.h b/taglib/mpeg/id3v2/frames/chapterframe.h index 1ecbbbfd..4a53cb57 100644 --- a/taglib/mpeg/id3v2/frames/chapterframe.h +++ b/taglib/mpeg/id3v2/frames/chapterframe.h @@ -27,6 +27,7 @@ #define TAGLIB_CHAPTERFRAME #include "id3v2frame.h" +#include "taglib_export.h" namespace TagLib { @@ -87,6 +88,7 @@ namespace TagLib { * 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; @@ -95,6 +97,7 @@ namespace TagLib { * 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; @@ -135,7 +138,7 @@ namespace TagLib { * * \see endOffset() */ - void endOffset(const uint &eO); + void setEndOffset(const uint &eO); virtual String toString() const; diff --git a/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp b/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp index bb43f5a6..1836f721 100644 --- a/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp +++ b/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp @@ -169,10 +169,10 @@ void TableOfContentsFrame::parseFields(const ByteVector &data) int pos = 0; d->elementID = readStringField(data, String::Latin1, &pos).data(String::Latin1); d->elementID.append(char(0)); - d->isTopLevel = (data.at(pos++) & 2) > 0; + d->isTopLevel = (data.at(pos) & 2) > 0; d->isOrdered = (data.at(pos++) & 1) > 0; uint entryCount = data.at(pos++); - for(int i = 0; i < entryCount; i++) + for(uint i = 0; i < entryCount; i++) { ByteVector childElementID = readStringField(data, String::Latin1, &pos).data(String::Latin1); childElementID.append(char(0)); @@ -193,7 +193,7 @@ ByteVector TableOfContentsFrame::renderFields() const flags += 1; data.append(flags); data.append((char)(entryCount())); - ConstIterator it = d->childElements.begin(); + ByteVectorList::ConstIterator it = d->childElements.begin(); while(it != d->childElements.end()) { data.append(*it); data.append(char(0)); diff --git a/tests/test_id3v2.cpp b/tests/test_id3v2.cpp index 48faf306..b696a088 100644 --- a/tests/test_id3v2.cpp +++ b/tests/test_id3v2.cpp @@ -15,6 +15,8 @@ #include #include #include +#include +#include #include #include #include @@ -77,6 +79,8 @@ class TestID3v2 : public CppUnit::TestFixture CPPUNIT_TEST(testPropertyInterface2); CPPUNIT_TEST(testDeleteFrame); CPPUNIT_TEST(testSaveAndStripID3v1ShouldNotAddFrameFromID3v1ToId3v2); + CPPUNIT_TEST(testChapters); + CPPUNIT_TEST(testTableOfContents); CPPUNIT_TEST_SUITE_END(); public: @@ -734,6 +738,66 @@ public: CPPUNIT_ASSERT(!f.ID3v2Tag()->frameListMap().contains("TPE1")); } + void testChaptersParsing() + { + ID3v2::ChapterFrame f( + ByteVector("CHAP" // Frame ID + "\x00\x00\x00\x12" // 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", 28)); // End offset + 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()); + } + + void testChapters() + { + ID3v2::ChapterFrame f( + ByteVector("CHAP" // Frame ID + "\x00\x00\x00\x12" // 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", 28)); // End offset + 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()); + } + + void testTableOfContents() + { + ID3v2::TableOfContentsFrame f( + ByteVector("CTOC" // Frame ID + "\x00\x00\x00\x08" // Frame size + "\x00\x00" // Frame flags + "\x54\x00" // Element ID + "\x01" // CTOC flags + "\x02" // Entry count + "\x43\x00" // First entry + "\x44\x00", 18)); // Second entry + 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_TEST_SUITE_REGISTRATION(TestID3v2); From 65006952f3f31d334f88c020ddaf84075d202096 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Krej=C4=8D=C3=AD?= Date: Sat, 27 Apr 2013 16:09:15 +0200 Subject: [PATCH 06/19] Changed copyright and e-mail in modified files. --- taglib/mpeg/id3v2/frames/chapterframe.cpp | 4 ++-- taglib/mpeg/id3v2/frames/chapterframe.h | 4 ++-- taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp | 4 ++-- taglib/mpeg/id3v2/frames/tableofcontentsframe.h | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/taglib/mpeg/id3v2/frames/chapterframe.cpp b/taglib/mpeg/id3v2/frames/chapterframe.cpp index 497f1a41..5787ad7f 100644 --- a/taglib/mpeg/id3v2/frames/chapterframe.cpp +++ b/taglib/mpeg/id3v2/frames/chapterframe.cpp @@ -1,6 +1,6 @@ /*************************************************************************** - copyright : (C) 2002 - 2008 by Scott Wheeler - email : wheeler@kde.org + copyright : (C) 2013 by Lukas Krejci + email : krejclu6@fel.cvut.cz ***************************************************************************/ /*************************************************************************** diff --git a/taglib/mpeg/id3v2/frames/chapterframe.h b/taglib/mpeg/id3v2/frames/chapterframe.h index 4a53cb57..6c3c1784 100644 --- a/taglib/mpeg/id3v2/frames/chapterframe.h +++ b/taglib/mpeg/id3v2/frames/chapterframe.h @@ -1,6 +1,6 @@ /*************************************************************************** - copyright : (C) 2002 - 2008 by Scott Wheeler - email : wheeler@kde.org + copyright : (C) 2013 by Lukas Krejci + email : krejclu6@fel.cvut.cz ***************************************************************************/ /*************************************************************************** diff --git a/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp b/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp index 1836f721..b1defeb8 100644 --- a/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp +++ b/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp @@ -1,6 +1,6 @@ /*************************************************************************** - copyright : (C) 2002 - 2008 by Scott Wheeler - email : wheeler@kde.org + copyright : (C) 2013 by Lukas Krejci + email : krejclu6@fel.cvut.cz ***************************************************************************/ /*************************************************************************** diff --git a/taglib/mpeg/id3v2/frames/tableofcontentsframe.h b/taglib/mpeg/id3v2/frames/tableofcontentsframe.h index 63ab96ee..9ab815e2 100644 --- a/taglib/mpeg/id3v2/frames/tableofcontentsframe.h +++ b/taglib/mpeg/id3v2/frames/tableofcontentsframe.h @@ -1,6 +1,6 @@ /*************************************************************************** - copyright : (C) 2002 - 2008 by Scott Wheeler - email : wheeler@kde.org + copyright : (C) 2013 by Lukas Krejci + email : krejclu6@fel.cvut.cz ***************************************************************************/ /*************************************************************************** From cbd6f73431d8b66fef12c23a5501ec9ed714b155 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Krej=C4=8D=C3=AD?= Date: Sat, 4 May 2013 21:25:55 +0200 Subject: [PATCH 07/19] Removed duplicated CHAP frame testing funtion. --- tests/test_id3v2.cpp | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/tests/test_id3v2.cpp b/tests/test_id3v2.cpp index b696a088..7182210c 100644 --- a/tests/test_id3v2.cpp +++ b/tests/test_id3v2.cpp @@ -737,25 +737,6 @@ public: MPEG::File f(newname.c_str()); CPPUNIT_ASSERT(!f.ID3v2Tag()->frameListMap().contains("TPE1")); } - - void testChaptersParsing() - { - ID3v2::ChapterFrame f( - ByteVector("CHAP" // Frame ID - "\x00\x00\x00\x12" // 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", 28)); // End offset - 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()); - } void testChapters() { From 5ed2d88f78e696fe927fcba3cc34e364ba1ca70d Mon Sep 17 00:00:00 2001 From: Lukas Krejci Date: Tue, 8 Oct 2013 18:19:15 +0200 Subject: [PATCH 08/19] Added functions for work with embedded frames. Added embedded frames parsing. Added embedded frames rendering. Modified constructor of CHAP and CTOC frame, so it can accept list of embedded frames. Added unit tests for CHAP and CTOC frames parsing and rendering (with support of embedded frames). Fixed bugs in rendering of CTOC frames. Added functions for adding and removing child elements in CTOC frames. --- taglib/mpeg/id3v2/frames/chapterframe.cpp | 86 ++++++++++++++- taglib/mpeg/id3v2/frames/chapterframe.h | 76 ++++++++++++- .../id3v2/frames/tableofcontentsframe.cpp | 97 ++++++++++++++-- .../mpeg/id3v2/frames/tableofcontentsframe.h | 90 ++++++++++++++- tests/test_id3v2.cpp | 104 +++++++++++++++--- 5 files changed, 419 insertions(+), 34 deletions(-) diff --git a/taglib/mpeg/id3v2/frames/chapterframe.cpp b/taglib/mpeg/id3v2/frames/chapterframe.cpp index 5787ad7f..717ea108 100644 --- a/taglib/mpeg/id3v2/frames/chapterframe.cpp +++ b/taglib/mpeg/id3v2/frames/chapterframe.cpp @@ -26,8 +26,8 @@ #include #include #include +#include -#include "id3v2tag.h" #include "chapterframe.h" using namespace TagLib; @@ -41,6 +41,9 @@ public: uint endTime; uint startOffset; uint endOffset; + const FrameFactory *factory; + FrameListMap embeddedFrameListMap; + FrameList embeddedFrameList; }; //////////////////////////////////////////////////////////////////////////////// @@ -51,10 +54,11 @@ 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) : +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; @@ -63,6 +67,10 @@ ChapterFrame::ChapterFrame(const ByteVector &eID, const uint &sT, const uint &eT 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() @@ -122,6 +130,49 @@ 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; @@ -154,12 +205,13 @@ ChapterFrame *ChapterFrame::findByElementID(const Tag *tag, const ByteVector &eI void ChapterFrame::parseFields(const ByteVector &data) { - if(data.size() < 18) { - debug("An 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)."); + 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; + 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); @@ -169,6 +221,24 @@ void ChapterFrame::parseFields(const ByteVector &data) 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 @@ -180,7 +250,10 @@ ByteVector ChapterFrame::renderFields() const 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; } @@ -188,5 +261,6 @@ 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 index 6c3c1784..84b42137 100644 --- a/taglib/mpeg/id3v2/frames/chapterframe.h +++ b/taglib/mpeg/id3v2/frames/chapterframe.h @@ -26,6 +26,7 @@ #ifndef TAGLIB_CHAPTERFRAME #define TAGLIB_CHAPTERFRAME +#include "id3v2tag.h" #include "id3v2frame.h" #include "taglib_export.h" @@ -52,10 +53,10 @@ namespace TagLib { /*! * Creates a chapter frame with the element ID \a eID, - * start time \a sT, end time \a eT, start offset \a sO - * and end offset \a eO. + * 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); + ChapterFrame(const ByteVector &eID, const uint &sT, const uint &eT, const uint &sO, const uint &eO, const FrameList &eF); /*! * Destroys the frame. @@ -139,6 +140,75 @@ namespace TagLib { * \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; diff --git a/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp b/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp index b1defeb8..9f51ebfd 100644 --- a/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp +++ b/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp @@ -27,7 +27,6 @@ #include #include -#include "id3v2tag.h" #include "tableofcontentsframe.h" using namespace TagLib; @@ -40,6 +39,9 @@ public: bool isTopLevel; bool isOrdered; ByteVectorList childElements; + const FrameFactory *factory; + FrameListMap embeddedFrameListMap; + FrameList embeddedFrameList; }; //////////////////////////////////////////////////////////////////////////////// @@ -50,15 +52,20 @@ 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) : +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() @@ -113,6 +120,60 @@ 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; @@ -161,12 +222,13 @@ TableOfContentsFrame *TableOfContentsFrame::findTopLevel(const Tag *tag) // stat void TableOfContentsFrame::parseFields(const ByteVector &data) { - if(data.size() < 6) { - debug("An 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."); + 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; + 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; @@ -178,6 +240,24 @@ void TableOfContentsFrame::parseFields(const ByteVector &data) 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 @@ -185,7 +265,6 @@ ByteVector TableOfContentsFrame::renderFields() const ByteVector data; data.append(d->elementID); - data.append(char(0)); char flags = 0; if(d->isTopLevel) flags += 2; @@ -196,9 +275,12 @@ ByteVector TableOfContentsFrame::renderFields() const ByteVectorList::ConstIterator it = d->childElements.begin(); while(it != d->childElements.end()) { data.append(*it); - data.append(char(0)); + //data.append(char(0)); it++; } + FrameList l = d->embeddedFrameList; + for(FrameList::Iterator it = l.begin(); it != l.end(); ++it) + data.append((*it)->render()); return data; } @@ -207,5 +289,6 @@ 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 index 9ab815e2..bf578d42 100644 --- a/taglib/mpeg/id3v2/frames/tableofcontentsframe.h +++ b/taglib/mpeg/id3v2/frames/tableofcontentsframe.h @@ -26,6 +26,7 @@ #ifndef TAGLIB_TABLEOFCONTENTSFRAME #define TAGLIB_TABLEOFCONTENTSFRAME +#include "id3v2tag.h" #include "id3v2frame.h" namespace TagLib { @@ -50,10 +51,10 @@ namespace TagLib { TableOfContentsFrame(const ByteVector &data); /*! - * Creates a table of contents frame with the element ID \a eID and - * the child elements \a ch. + * 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); + TableOfContentsFrame(const ByteVector &eID, const ByteVectorList &ch, const FrameList &eF); /*! * Destroys the frame. @@ -129,6 +130,89 @@ namespace TagLib { * \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; diff --git a/tests/test_id3v2.cpp b/tests/test_id3v2.cpp index 7182210c..dd1cac7c 100644 --- a/tests/test_id3v2.cpp +++ b/tests/test_id3v2.cpp @@ -73,14 +73,16 @@ class TestID3v2 : public CppUnit::TestFixture CPPUNIT_TEST(testUpdateDate22); CPPUNIT_TEST(testDowngradeTo23); // CPPUNIT_TEST(testUpdateFullDate22); TODO TYE+TDA should be upgraded to TDRC together - CPPUNIT_TEST(testCompressedFrameWithBrokenLength); + //CPPUNIT_TEST(testCompressedFrameWithBrokenLength); CPPUNIT_TEST(testW000); CPPUNIT_TEST(testPropertyInterface); CPPUNIT_TEST(testPropertyInterface2); CPPUNIT_TEST(testDeleteFrame); CPPUNIT_TEST(testSaveAndStripID3v1ShouldNotAddFrameFromID3v1ToId3v2); - CPPUNIT_TEST(testChapters); - CPPUNIT_TEST(testTableOfContents); + CPPUNIT_TEST(testParseChapterFrame); + CPPUNIT_TEST(testRenderChapterFrame); + CPPUNIT_TEST(testParseTableOfContentsFrame); + CPPUNIT_TEST(testRenderTableOfContentsFrame); CPPUNIT_TEST_SUITE_END(); public: @@ -738,36 +740,77 @@ public: CPPUNIT_ASSERT(!f.ID3v2Tag()->frameListMap().contains("TPE1")); } - void testChapters() + void testParseChapterFrame() { ID3v2::ChapterFrame f( ByteVector("CHAP" // Frame ID - "\x00\x00\x00\x12" // Frame size + "\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", 28)); // End offset + "\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 testTableOfContents() + 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\x08" // Frame size + "\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", 18)); // Second entry + "\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()); @@ -777,6 +820,37 @@ public: 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()); } }; From 1ce5385e3033cc613bc8e2ac87f5e3f40b0ee571 Mon Sep 17 00:00:00 2001 From: Lukas Krejci Date: Sun, 13 Oct 2013 18:38:54 +0200 Subject: [PATCH 09/19] Uncommenting unit test. --- tests/test_id3v2.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_id3v2.cpp b/tests/test_id3v2.cpp index dd1cac7c..06f911f8 100644 --- a/tests/test_id3v2.cpp +++ b/tests/test_id3v2.cpp @@ -73,7 +73,7 @@ class TestID3v2 : public CppUnit::TestFixture CPPUNIT_TEST(testUpdateDate22); CPPUNIT_TEST(testDowngradeTo23); // CPPUNIT_TEST(testUpdateFullDate22); TODO TYE+TDA should be upgraded to TDRC together - //CPPUNIT_TEST(testCompressedFrameWithBrokenLength); + CPPUNIT_TEST(testCompressedFrameWithBrokenLength); CPPUNIT_TEST(testW000); CPPUNIT_TEST(testPropertyInterface); CPPUNIT_TEST(testPropertyInterface2); From 0864634ea628e23156434ba6644e8f510d075e29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Krej=C4=8D=C3=AD?= Date: Sat, 20 Apr 2013 15:52:52 +0200 Subject: [PATCH 10/19] Created CPP and H files for CTOC and CHAP frames. --- taglib/mpeg/id3v2/frames/chapterframe.cpp | 148 +++++++++++++++ taglib/mpeg/id3v2/frames/chapterframe.h | 168 ++++++++++++++++++ .../id3v2/frames/tableofcontentsframe.cpp | 148 +++++++++++++++ .../mpeg/id3v2/frames/tableofcontentsframe.h | 162 +++++++++++++++++ 4 files changed, 626 insertions(+) create mode 100644 taglib/mpeg/id3v2/frames/chapterframe.cpp create mode 100644 taglib/mpeg/id3v2/frames/chapterframe.h create mode 100644 taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp create mode 100644 taglib/mpeg/id3v2/frames/tableofcontentsframe.h diff --git a/taglib/mpeg/id3v2/frames/chapterframe.cpp b/taglib/mpeg/id3v2/frames/chapterframe.cpp new file mode 100644 index 00000000..a0e842e0 --- /dev/null +++ b/taglib/mpeg/id3v2/frames/chapterframe.cpp @@ -0,0 +1,148 @@ +/*************************************************************************** + copyright : (C) 2002 - 2008 by Scott Wheeler + email : wheeler@kde.org + ***************************************************************************/ + +/*************************************************************************** + * 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 "id3v2tag.h" +#include "uniquefileidentifierframe.h" + +using namespace TagLib; +using namespace ID3v2; + +class UniqueFileIdentifierFrame::UniqueFileIdentifierFramePrivate +{ +public: + String owner; + ByteVector identifier; +}; + +//////////////////////////////////////////////////////////////////////////////// +// public methods +//////////////////////////////////////////////////////////////////////////////// + +UniqueFileIdentifierFrame::UniqueFileIdentifierFrame(const ByteVector &data) : + ID3v2::Frame(data) +{ + d = new UniqueFileIdentifierFramePrivate; + setData(data); +} + +UniqueFileIdentifierFrame::UniqueFileIdentifierFrame(const String &owner, const ByteVector &id) : + ID3v2::Frame("UFID") +{ + d = new UniqueFileIdentifierFramePrivate; + d->owner = owner; + d->identifier = id; +} + +UniqueFileIdentifierFrame::~UniqueFileIdentifierFrame() +{ + delete d; +} + +String UniqueFileIdentifierFrame::owner() const +{ + return d->owner; +} + +ByteVector UniqueFileIdentifierFrame::identifier() const +{ + return d->identifier; +} + +void UniqueFileIdentifierFrame::setOwner(const String &s) +{ + d->owner = s; +} + +void UniqueFileIdentifierFrame::setIdentifier(const ByteVector &v) +{ + d->identifier = v; +} + +String UniqueFileIdentifierFrame::toString() const +{ + return String::null; +} + +PropertyMap UniqueFileIdentifierFrame::asProperties() const +{ + PropertyMap map; + if(d->owner == "http://musicbrainz.org") { + map.insert("MUSICBRAINZ_TRACKID", String(d->identifier)); + } + else { + map.unsupportedData().append(frameID() + String("/") + d->owner); + } + return map; +} + +UniqueFileIdentifierFrame *UniqueFileIdentifierFrame::findByOwner(const ID3v2::Tag *tag, const String &o) // static +{ + ID3v2::FrameList comments = tag->frameList("UFID"); + + for(ID3v2::FrameList::ConstIterator it = comments.begin(); + it != comments.end(); + ++it) + { + UniqueFileIdentifierFrame *frame = dynamic_cast(*it); + if(frame && frame->owner() == o) + return frame; + } + + return 0; +} + +void UniqueFileIdentifierFrame::parseFields(const ByteVector &data) +{ + if(data.size() < 1) { + debug("An UFID frame must contain at least 1 byte."); + return; + } + + int pos = 0; + d->owner = readStringField(data, String::Latin1, &pos); + d->identifier = data.mid(pos); +} + +ByteVector UniqueFileIdentifierFrame::renderFields() const +{ + ByteVector data; + + data.append(d->owner.data(String::Latin1)); + data.append(char(0)); + data.append(d->identifier); + + return data; +} + +UniqueFileIdentifierFrame::UniqueFileIdentifierFrame(const ByteVector &data, Header *h) : + Frame(h) +{ + d = new UniqueFileIdentifierFramePrivate; + 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..192711ce --- /dev/null +++ b/taglib/mpeg/id3v2/frames/chapterframe.h @@ -0,0 +1,168 @@ +/*************************************************************************** + copyright : (C) 2002 - 2008 by Scott Wheeler + email : wheeler@kde.org + ***************************************************************************/ + +/*************************************************************************** + * 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 "id3v2frame.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 + * and end offset \a eO. + */ + ChapterFrame(const ByteVector &eID, const int &sT, const int &eT, const int &sO, const int &eO); + + /*! + * Destroys the frame. + */ + ~ChapterFrame(); + + /*! + * 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 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. + * + * \see setStartOffset() + */ + uint startOffset() const; + + /*! + * Returns zero based byte offset (count of bytes from the beginning + * of the audio file) of chapter's end. + * + * \see setEndOffset() + */ + uint endOffset() const; + + /*! + * Sets the elementID of the frame to \a eID. + * + * \warning Element ID must be null terminated. + * \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 endOffset(const uint &eO); + + 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. + * + * \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..a0e842e0 --- /dev/null +++ b/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp @@ -0,0 +1,148 @@ +/*************************************************************************** + copyright : (C) 2002 - 2008 by Scott Wheeler + email : wheeler@kde.org + ***************************************************************************/ + +/*************************************************************************** + * 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 "id3v2tag.h" +#include "uniquefileidentifierframe.h" + +using namespace TagLib; +using namespace ID3v2; + +class UniqueFileIdentifierFrame::UniqueFileIdentifierFramePrivate +{ +public: + String owner; + ByteVector identifier; +}; + +//////////////////////////////////////////////////////////////////////////////// +// public methods +//////////////////////////////////////////////////////////////////////////////// + +UniqueFileIdentifierFrame::UniqueFileIdentifierFrame(const ByteVector &data) : + ID3v2::Frame(data) +{ + d = new UniqueFileIdentifierFramePrivate; + setData(data); +} + +UniqueFileIdentifierFrame::UniqueFileIdentifierFrame(const String &owner, const ByteVector &id) : + ID3v2::Frame("UFID") +{ + d = new UniqueFileIdentifierFramePrivate; + d->owner = owner; + d->identifier = id; +} + +UniqueFileIdentifierFrame::~UniqueFileIdentifierFrame() +{ + delete d; +} + +String UniqueFileIdentifierFrame::owner() const +{ + return d->owner; +} + +ByteVector UniqueFileIdentifierFrame::identifier() const +{ + return d->identifier; +} + +void UniqueFileIdentifierFrame::setOwner(const String &s) +{ + d->owner = s; +} + +void UniqueFileIdentifierFrame::setIdentifier(const ByteVector &v) +{ + d->identifier = v; +} + +String UniqueFileIdentifierFrame::toString() const +{ + return String::null; +} + +PropertyMap UniqueFileIdentifierFrame::asProperties() const +{ + PropertyMap map; + if(d->owner == "http://musicbrainz.org") { + map.insert("MUSICBRAINZ_TRACKID", String(d->identifier)); + } + else { + map.unsupportedData().append(frameID() + String("/") + d->owner); + } + return map; +} + +UniqueFileIdentifierFrame *UniqueFileIdentifierFrame::findByOwner(const ID3v2::Tag *tag, const String &o) // static +{ + ID3v2::FrameList comments = tag->frameList("UFID"); + + for(ID3v2::FrameList::ConstIterator it = comments.begin(); + it != comments.end(); + ++it) + { + UniqueFileIdentifierFrame *frame = dynamic_cast(*it); + if(frame && frame->owner() == o) + return frame; + } + + return 0; +} + +void UniqueFileIdentifierFrame::parseFields(const ByteVector &data) +{ + if(data.size() < 1) { + debug("An UFID frame must contain at least 1 byte."); + return; + } + + int pos = 0; + d->owner = readStringField(data, String::Latin1, &pos); + d->identifier = data.mid(pos); +} + +ByteVector UniqueFileIdentifierFrame::renderFields() const +{ + ByteVector data; + + data.append(d->owner.data(String::Latin1)); + data.append(char(0)); + data.append(d->identifier); + + return data; +} + +UniqueFileIdentifierFrame::UniqueFileIdentifierFrame(const ByteVector &data, Header *h) : + Frame(h) +{ + d = new UniqueFileIdentifierFramePrivate; + 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..54fb68b4 --- /dev/null +++ b/taglib/mpeg/id3v2/frames/tableofcontentsframe.h @@ -0,0 +1,162 @@ +/*************************************************************************** + copyright : (C) 2002 - 2008 by Scott Wheeler + email : wheeler@kde.org + ***************************************************************************/ + +/*************************************************************************** + * 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 "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 and + * the child elements \a ch. + */ + UniqueFileIdentifierFrame(const ByteVector &eID, const List &ch); + + /*! + * Destroys the frame. + */ + ~UniqueFileIdentifierFrame(); + + /*! + * 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. + * + * \note Return type should be uint8_t. + * \see childElements() + */ + unsigned char entryCount() const; + + /*! + * Returns list of child elements of the frame. + * + * \see setChildElements() + */ + List childElements() const; + + /*! + * Sets the elementID of the frame to \a eID. + * + * \warning Element ID must be null terminated. + * \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 List &l); + + 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. + * + * \see elementID() + */ + static UniqueFileIdentifierFrame *findByElementID(const Tag *tag, const ByteVector &eID); + + 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 From 3a1040d55b25e41680ca3ae6e101a5eb8fe5d19a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Krej=C4=8D=C3=AD?= Date: Sat, 20 Apr 2013 16:49:57 +0200 Subject: [PATCH 11/19] Added basic members of ChapterFrame and TableOfContentsFrame classes. Fixed minor bugs in ChapterFrame and TableOfContentsFrame headers. --- taglib/mpeg/id3v2/frames/chapterframe.cpp | 97 +++++++++++++------ taglib/mpeg/id3v2/frames/chapterframe.h | 5 +- .../id3v2/frames/tableofcontentsframe.cpp | 90 +++++++++++------ .../mpeg/id3v2/frames/tableofcontentsframe.h | 15 +-- 4 files changed, 139 insertions(+), 68 deletions(-) diff --git a/taglib/mpeg/id3v2/frames/chapterframe.cpp b/taglib/mpeg/id3v2/frames/chapterframe.cpp index a0e842e0..1474db1a 100644 --- a/taglib/mpeg/id3v2/frames/chapterframe.cpp +++ b/taglib/mpeg/id3v2/frames/chapterframe.cpp @@ -28,60 +28,96 @@ #include #include "id3v2tag.h" -#include "uniquefileidentifierframe.h" +#include "chapterframe.h" using namespace TagLib; using namespace ID3v2; -class UniqueFileIdentifierFrame::UniqueFileIdentifierFramePrivate +class ChapterFrame::ChapterFramePrivate { public: - String owner; - ByteVector identifier; + ByteVector elementID; + uint startTime; + uint endTime; + uint startOffset; + uint endOffset; }; //////////////////////////////////////////////////////////////////////////////// // public methods //////////////////////////////////////////////////////////////////////////////// -UniqueFileIdentifierFrame::UniqueFileIdentifierFrame(const ByteVector &data) : +ChapterFrame::ChapterFrame(const ByteVector &data) : ID3v2::Frame(data) { - d = new UniqueFileIdentifierFramePrivate; + d = new ChapterFramePrivate; setData(data); } -UniqueFileIdentifierFrame::UniqueFileIdentifierFrame(const String &owner, const ByteVector &id) : - ID3v2::Frame("UFID") +ChapterFrame::ChapterFrame(const ByteVector &eID, const int &sT, const int &eT, const int &sO, const int &eO) : + ID3v2::Frame("CHAP") { - d = new UniqueFileIdentifierFramePrivate; - d->owner = owner; - d->identifier = id; + d = new ChapterFramePrivate; + d->elementID = eID; + d->startTime = sT; + d->endTime = eT; + d->startOffset = sO; + d->endOffset = e0; } -UniqueFileIdentifierFrame::~UniqueFileIdentifierFrame() +ChapterFrame::~ChapterFrame() { delete d; } -String UniqueFileIdentifierFrame::owner() const +ByteVector ChapterFrame::elementID() const { - return d->owner; + return d->elementID; } -ByteVector UniqueFileIdentifierFrame::identifier() const +uint ChapterFrame::startTime() const { - return d->identifier; + return d->startTime; } -void UniqueFileIdentifierFrame::setOwner(const String &s) +uint ChapterFrame::endTime() const { - d->owner = s; + return d->endTime; } -void UniqueFileIdentifierFrame::setIdentifier(const ByteVector &v) +uint ChapterFrame::startOffset() const { - d->identifier = v; + return d->startOffset; +} + +uint ChapterFrame::endOffset() const +{ + return d->endOffset; +} + +void ChapterFrame::setElementID(const ByteVector &eID) +{ + d->elementID = eID; +} + +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; } String UniqueFileIdentifierFrame::toString() const @@ -89,8 +125,9 @@ String UniqueFileIdentifierFrame::toString() const return String::null; } -PropertyMap UniqueFileIdentifierFrame::asProperties() const +PropertyMap ChapterFrame::asProperties() const { + //DODELAT PropertyMap map; if(d->owner == "http://musicbrainz.org") { map.insert("MUSICBRAINZ_TRACKID", String(d->identifier)); @@ -101,24 +138,25 @@ PropertyMap UniqueFileIdentifierFrame::asProperties() const return map; } -UniqueFileIdentifierFrame *UniqueFileIdentifierFrame::findByOwner(const ID3v2::Tag *tag, const String &o) // static +ChapterFrame *ChapterFrame::findByElementID(const Tag *tag, const ByteVector &eID) // static { - ID3v2::FrameList comments = tag->frameList("UFID"); + ID3v2::FrameList comments = tag->frameList("CHAP"); for(ID3v2::FrameList::ConstIterator it = comments.begin(); it != comments.end(); ++it) { - UniqueFileIdentifierFrame *frame = dynamic_cast(*it); - if(frame && frame->owner() == o) + ChapterFrame *frame = dynamic_cast(*it); + if(frame && frame->elementID() == eID) return frame; } return 0; } -void UniqueFileIdentifierFrame::parseFields(const ByteVector &data) +void ChapterFrame::parseFields(const ByteVector &data) { + //DODELAT if(data.size() < 1) { debug("An UFID frame must contain at least 1 byte."); return; @@ -129,8 +167,9 @@ void UniqueFileIdentifierFrame::parseFields(const ByteVector &data) d->identifier = data.mid(pos); } -ByteVector UniqueFileIdentifierFrame::renderFields() const +ByteVector ChapterFrame::renderFields() const { + //DODELAT ByteVector data; data.append(d->owner.data(String::Latin1)); @@ -140,9 +179,9 @@ ByteVector UniqueFileIdentifierFrame::renderFields() const return data; } -UniqueFileIdentifierFrame::UniqueFileIdentifierFrame(const ByteVector &data, Header *h) : +ChapterFrame::ChapterFrame(const ByteVector &data, Header *h) : Frame(h) { - d = new UniqueFileIdentifierFramePrivate; + d = new ChapterFramePrivate; parseFields(fieldData(data)); } diff --git a/taglib/mpeg/id3v2/frames/chapterframe.h b/taglib/mpeg/id3v2/frames/chapterframe.h index 192711ce..ce9096cf 100644 --- a/taglib/mpeg/id3v2/frames/chapterframe.h +++ b/taglib/mpeg/id3v2/frames/chapterframe.h @@ -54,7 +54,7 @@ namespace TagLib { * start time \a sT, end time \a eT, start offset \a sO * and end offset \a eO. */ - ChapterFrame(const ByteVector &eID, const int &sT, const int &eT, const int &sO, const int &eO); + ChapterFrame(const ByteVector &eID, const uint &sT, const uint &eT, const uint &sO, const uint &eO); /*! * Destroys the frame. @@ -143,7 +143,8 @@ namespace TagLib { /*! * 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. + * 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() */ diff --git a/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp b/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp index a0e842e0..1cb24b50 100644 --- a/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp +++ b/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp @@ -28,69 +28,97 @@ #include #include "id3v2tag.h" -#include "uniquefileidentifierframe.h" +#include "tableofcontentsframe.h" using namespace TagLib; using namespace ID3v2; -class UniqueFileIdentifierFrame::UniqueFileIdentifierFramePrivate +class TableOfContentsFrame::TableOfContentsFramePrivate { public: - String owner; - ByteVector identifier; + ByteVector elementID; + bool isTopLevel; + bool isOrdered; + ByteVectorList childElements; }; //////////////////////////////////////////////////////////////////////////////// // public methods //////////////////////////////////////////////////////////////////////////////// -UniqueFileIdentifierFrame::UniqueFileIdentifierFrame(const ByteVector &data) : +TableOfContentsFrame::TableOfContentsFrame(const ByteVector &data) : ID3v2::Frame(data) { - d = new UniqueFileIdentifierFramePrivate; + d = new TableOfContentsFramePrivate; setData(data); } -UniqueFileIdentifierFrame::UniqueFileIdentifierFrame(const String &owner, const ByteVector &id) : - ID3v2::Frame("UFID") +TableOfContentsFrame::TableOfContentsFrame(const ByteVector &eID, const ByteVectorList &ch) : + ID3v2::Frame("CTOC") { - d = new UniqueFileIdentifierFramePrivate; - d->owner = owner; - d->identifier = id; + d = new TableOfContentsFramePrivate; + d->elementID = eID; + d->childElements = ch; } -UniqueFileIdentifierFrame::~UniqueFileIdentifierFrame() +TableOfContentsFrame::~TableOfContentsFrame() { delete d; } -String UniqueFileIdentifierFrame::owner() const +ByteVector TableOfContentsFrame::elementID() const { - return d->owner; + return d->elementID; } -ByteVector UniqueFileIdentifierFrame::identifier() const +bool TableOfContentsFrame::isTopLevel() const { - return d->identifier; + return d->isTopLevel; } -void UniqueFileIdentifierFrame::setOwner(const String &s) +bool TableOfContentsFrame::isOrdered() const { - d->owner = s; + return d->isOrdered; } -void UniqueFileIdentifierFrame::setIdentifier(const ByteVector &v) +unsigned char TableOfContentsFrame::entryCount() const { - d->identifier = v; + return (unsigned char)(d->childElements.size()); } -String UniqueFileIdentifierFrame::toString() const +ByteVectorList TableOfContentsFrame::childElements const +{ + return d->childElements; +} + +void TableOfContentsFrame::setElementID(const ByteVector &eID) +{ + d->elementID = eID; +} + +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; +} + +String TableOfContentsFrame::toString() const { return String::null; } -PropertyMap UniqueFileIdentifierFrame::asProperties() const +PropertyMap TableOfContentsFrame::asProperties() const { + //DODELAT PropertyMap map; if(d->owner == "http://musicbrainz.org") { map.insert("MUSICBRAINZ_TRACKID", String(d->identifier)); @@ -101,24 +129,25 @@ PropertyMap UniqueFileIdentifierFrame::asProperties() const return map; } -UniqueFileIdentifierFrame *UniqueFileIdentifierFrame::findByOwner(const ID3v2::Tag *tag, const String &o) // static +TableOfContentsFrame *TableOfContentsFrame::findByElementID(const ID3v2::Tag *tag, const ByteVector &eID) // static { - ID3v2::FrameList comments = tag->frameList("UFID"); + ID3v2::FrameList comments = tag->frameList("CTOC"); for(ID3v2::FrameList::ConstIterator it = comments.begin(); it != comments.end(); ++it) { - UniqueFileIdentifierFrame *frame = dynamic_cast(*it); - if(frame && frame->owner() == o) + TableOfContentsFrame *frame = dynamic_cast(*it); + if(frame && frame->elementID() == eID) return frame; } return 0; } -void UniqueFileIdentifierFrame::parseFields(const ByteVector &data) +void TableOfContentsFrame::parseFields(const ByteVector &data) { + //DODELAT if(data.size() < 1) { debug("An UFID frame must contain at least 1 byte."); return; @@ -129,8 +158,9 @@ void UniqueFileIdentifierFrame::parseFields(const ByteVector &data) d->identifier = data.mid(pos); } -ByteVector UniqueFileIdentifierFrame::renderFields() const +ByteVector TableOfContentsFrame::renderFields() const { + //DODELAT ByteVector data; data.append(d->owner.data(String::Latin1)); @@ -140,9 +170,9 @@ ByteVector UniqueFileIdentifierFrame::renderFields() const return data; } -UniqueFileIdentifierFrame::UniqueFileIdentifierFrame(const ByteVector &data, Header *h) : +TableOfContentsFrame::TableOfContentsFrame(const ByteVector &data, Header *h) : Frame(h) { - d = new UniqueFileIdentifierFramePrivate; + d = new TableOfContentsFramePrivate; parseFields(fieldData(data)); } diff --git a/taglib/mpeg/id3v2/frames/tableofcontentsframe.h b/taglib/mpeg/id3v2/frames/tableofcontentsframe.h index 54fb68b4..0d189dfc 100644 --- a/taglib/mpeg/id3v2/frames/tableofcontentsframe.h +++ b/taglib/mpeg/id3v2/frames/tableofcontentsframe.h @@ -53,12 +53,12 @@ namespace TagLib { * Creates a table of contents frame with the element ID \a eID and * the child elements \a ch. */ - UniqueFileIdentifierFrame(const ByteVector &eID, const List &ch); + TableOfContentsFrame(const ByteVector &eID, const ByteVectorList &ch); /*! * Destroys the frame. */ - ~UniqueFileIdentifierFrame(); + ~TableOfContentsFrame(); /*! * Returns the elementID of the frame. Element ID @@ -88,7 +88,7 @@ namespace TagLib { * Returns count of child elements of the frame. It allways * corresponds to size of child elements list. * - * \note Return type should be uint8_t. + * \note Return type should be uint8_t, not unsigned char. * \see childElements() */ unsigned char entryCount() const; @@ -98,7 +98,7 @@ namespace TagLib { * * \see setChildElements() */ - List childElements() const; + ByteVectorList childElements() const; /*! * Sets the elementID of the frame to \a eID. @@ -129,7 +129,7 @@ namespace TagLib { * * \see childElements() */ - void setChildElements(const List &l); + void setChildElements(const ByteVectorList &l); virtual String toString() const; @@ -137,11 +137,12 @@ namespace TagLib { /*! * 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. + * 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 UniqueFileIdentifierFrame *findByElementID(const Tag *tag, const ByteVector &eID); + static TableOfContentsFrame *findByElementID(const Tag *tag, const ByteVector &eID); protected: virtual void parseFields(const ByteVector &data); From c5f92584625e3f8b409150da60574c3475993408 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Krej=C4=8D=C3=AD?= Date: Sun, 21 Apr 2013 16:16:57 +0200 Subject: [PATCH 12/19] Finished parseFields, renderFields and asProperty methods of ChapterFrame and TableOfContentsFrame classes. Methods setElementID of ChapterFrame and TableOfContentsFrame classes now automatically terminates new element ID with null. --- taglib/mpeg/id3v2/frames/chapterframe.cpp | 41 +++++----- taglib/mpeg/id3v2/frames/chapterframe.h | 6 +- .../id3v2/frames/tableofcontentsframe.cpp | 75 +++++++++++++------ .../mpeg/id3v2/frames/tableofcontentsframe.h | 16 +++- taglib/mpeg/id3v2/id3v2framefactory.cpp | 12 +++ 5 files changed, 104 insertions(+), 46 deletions(-) diff --git a/taglib/mpeg/id3v2/frames/chapterframe.cpp b/taglib/mpeg/id3v2/frames/chapterframe.cpp index 1474db1a..633a6d8b 100644 --- a/taglib/mpeg/id3v2/frames/chapterframe.cpp +++ b/taglib/mpeg/id3v2/frames/chapterframe.cpp @@ -98,6 +98,8 @@ uint ChapterFrame::endOffset() const 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) @@ -120,21 +122,17 @@ void ChapterFrame::setEndOffset(const uint &eO) d->endOffset = eO; } -String UniqueFileIdentifierFrame::toString() const +String ChapterFrame::toString() const { return String::null; } -PropertyMap ChapterFrame::asProperties() const +PropertyMap UniqueFileIdentifierFrame::asProperties() const { - //DODELAT PropertyMap map; - if(d->owner == "http://musicbrainz.org") { - map.insert("MUSICBRAINZ_TRACKID", String(d->identifier)); - } - else { - map.unsupportedData().append(frameID() + String("/") + d->owner); - } + + map.unsupportedData().append(frameID() + String("/") + d->elementID); + return map; } @@ -156,25 +154,32 @@ ChapterFrame *ChapterFrame::findByElementID(const Tag *tag, const ByteVector &eI void ChapterFrame::parseFields(const ByteVector &data) { - //DODELAT - if(data.size() < 1) { - debug("An UFID frame must contain at least 1 byte."); + if(data.size() < 18) { + debug("An 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; - d->owner = readStringField(data, String::Latin1, &pos); - d->identifier = data.mid(pos); + 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); } ByteVector ChapterFrame::renderFields() const { - //DODELAT ByteVector data; - data.append(d->owner.data(String::Latin1)); - data.append(char(0)); - data.append(d->identifier); + 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)); return data; } diff --git a/taglib/mpeg/id3v2/frames/chapterframe.h b/taglib/mpeg/id3v2/frames/chapterframe.h index ce9096cf..1ecbbbfd 100644 --- a/taglib/mpeg/id3v2/frames/chapterframe.h +++ b/taglib/mpeg/id3v2/frames/chapterframe.h @@ -62,7 +62,7 @@ namespace TagLib { ~ChapterFrame(); /*! - * Returns the elementID of the frame. Element ID + * Returns the element ID of the frame. Element ID * is a null terminated string, however it's not human-readable. * * \see setElementID() @@ -100,9 +100,9 @@ namespace TagLib { uint endOffset() const; /*! - * Sets the elementID of the frame to \a eID. + * Sets the element ID of the frame to \a eID. If \a eID isn't + * null terminated, a null char is appended automatically. * - * \warning Element ID must be null terminated. * \see elementID() */ void setElementID(const ByteVector &eID); diff --git a/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp b/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp index 1cb24b50..39df25d6 100644 --- a/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp +++ b/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp @@ -81,9 +81,9 @@ bool TableOfContentsFrame::isOrdered() const return d->isOrdered; } -unsigned char TableOfContentsFrame::entryCount() const +uint TableOfContentsFrame::entryCount() const { - return (unsigned char)(d->childElements.size()); + return d->childElements.size(); } ByteVectorList TableOfContentsFrame::childElements const @@ -94,6 +94,8 @@ ByteVectorList TableOfContentsFrame::childElements const 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) @@ -118,23 +120,19 @@ String TableOfContentsFrame::toString() const PropertyMap TableOfContentsFrame::asProperties() const { - //DODELAT PropertyMap map; - if(d->owner == "http://musicbrainz.org") { - map.insert("MUSICBRAINZ_TRACKID", String(d->identifier)); - } - else { - map.unsupportedData().append(frameID() + String("/") + d->owner); - } + + map.unsupportedData().append(frameID() + String("/") + d->elementID); + return map; } TableOfContentsFrame *TableOfContentsFrame::findByElementID(const ID3v2::Tag *tag, const ByteVector &eID) // static { - ID3v2::FrameList comments = tag->frameList("CTOC"); + ID3v2::FrameList tablesOfContents = tag->frameList("CTOC"); - for(ID3v2::FrameList::ConstIterator it = comments.begin(); - it != comments.end(); + for(ID3v2::FrameList::ConstIterator it = tablesOfContents.begin(); + it != tablesOfContents.end(); ++it) { TableOfContentsFrame *frame = dynamic_cast(*it); @@ -145,28 +143,63 @@ TableOfContentsFrame *TableOfContentsFrame::findByElementID(const ID3v2::Tag *ta 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) { - //DODELAT - if(data.size() < 1) { - debug("An UFID frame must contain at least 1 byte."); + if(data.size() < 6) { + debug("An 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; - d->owner = readStringField(data, String::Latin1, &pos); - d->identifier = data.mid(pos); + 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(int i = 0; i < entryCount; i++) + { + ByteVector childElementID = readStringField(data, String::Latin1, &pos).data(String::Latin1); + childElementID.append(char(0)); + d->childElements.append(childElementID); + } } ByteVector TableOfContentsFrame::renderFields() const { - //DODELAT ByteVector data; - data.append(d->owner.data(String::Latin1)); + data.append(d->elementID); data.append(char(0)); - data.append(d->identifier); - + char flags = 0; + if(d->isTopLevel) + flags += 2; + if(d->isOrdered) + flags += 1; + data.append(flags); + data.append((char)(entryCount())); + ConstIterator it = d->childElements.begin(); + while(it != d->childElements.end()) { + data.append(*it); + data.append(char(0)); + it++; + } + return data; } diff --git a/taglib/mpeg/id3v2/frames/tableofcontentsframe.h b/taglib/mpeg/id3v2/frames/tableofcontentsframe.h index 0d189dfc..63ab96ee 100644 --- a/taglib/mpeg/id3v2/frames/tableofcontentsframe.h +++ b/taglib/mpeg/id3v2/frames/tableofcontentsframe.h @@ -88,10 +88,9 @@ namespace TagLib { * Returns count of child elements of the frame. It allways * corresponds to size of child elements list. * - * \note Return type should be uint8_t, not unsigned char. * \see childElements() */ - unsigned char entryCount() const; + uint entryCount() const; /*! * Returns list of child elements of the frame. @@ -101,9 +100,9 @@ namespace TagLib { ByteVectorList childElements() const; /*! - * Sets the elementID of the frame to \a eID. + * Sets the elementID of the frame to \a eID. If \a eID isn't + * null terminated, a null char is appended automatically. * - * \warning Element ID must be null terminated. * \see elementID() */ void setElementID(const ByteVector &eID); @@ -143,6 +142,15 @@ namespace TagLib { * \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); 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); } From 17841e89aeabd0a11a7434a28a01d24c2bb0b1f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Krej=C4=8D=C3=AD?= Date: Fri, 26 Apr 2013 23:16:06 +0200 Subject: [PATCH 13/19] Fixed error in childElements function. --- taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp b/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp index 39df25d6..bb43f5a6 100644 --- a/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp +++ b/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp @@ -86,7 +86,7 @@ uint TableOfContentsFrame::entryCount() const return d->childElements.size(); } -ByteVectorList TableOfContentsFrame::childElements const +ByteVectorList TableOfContentsFrame::childElements() const { return d->childElements; } From 7d99b8276ae545c05e549c6ae63a9605af1681c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Krej=C4=8D=C3=AD?= Date: Sat, 27 Apr 2013 15:42:23 +0200 Subject: [PATCH 14/19] Fixed errors in ChapterFrame constructor. Fixed errors in ChapterFrame method renderFields. Fixed errors in TableOfContentsFrame method parseFields. Added ChapterFrame and TableOfContentsFrame headers and sources to CMakeLists.txt. Added some basic testing of CHAP and CTOC frames parsing. --- taglib/CMakeLists.txt | 4 ++ taglib/mpeg/id3v2/frames/chapterframe.cpp | 14 ++-- taglib/mpeg/id3v2/frames/chapterframe.h | 5 +- .../id3v2/frames/tableofcontentsframe.cpp | 6 +- tests/test_id3v2.cpp | 64 +++++++++++++++++++ 5 files changed, 82 insertions(+), 11 deletions(-) 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 index 633a6d8b..497f1a41 100644 --- a/taglib/mpeg/id3v2/frames/chapterframe.cpp +++ b/taglib/mpeg/id3v2/frames/chapterframe.cpp @@ -54,7 +54,7 @@ ChapterFrame::ChapterFrame(const ByteVector &data) : setData(data); } -ChapterFrame::ChapterFrame(const ByteVector &eID, const int &sT, const int &eT, const int &sO, const int &eO) : +ChapterFrame::ChapterFrame(const ByteVector &eID, const uint &sT, const uint &eT, const uint &sO, const uint &eO) : ID3v2::Frame("CHAP") { d = new ChapterFramePrivate; @@ -62,7 +62,7 @@ ChapterFrame::ChapterFrame(const ByteVector &eID, const int &sT, const int &eT, d->startTime = sT; d->endTime = eT; d->startOffset = sO; - d->endOffset = e0; + d->endOffset = eO; } ChapterFrame::~ChapterFrame() @@ -127,7 +127,7 @@ String ChapterFrame::toString() const return String::null; } -PropertyMap UniqueFileIdentifierFrame::asProperties() const +PropertyMap ChapterFrame::asProperties() const { PropertyMap map; @@ -176,10 +176,10 @@ 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)); + 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)); return data; } diff --git a/taglib/mpeg/id3v2/frames/chapterframe.h b/taglib/mpeg/id3v2/frames/chapterframe.h index 1ecbbbfd..4a53cb57 100644 --- a/taglib/mpeg/id3v2/frames/chapterframe.h +++ b/taglib/mpeg/id3v2/frames/chapterframe.h @@ -27,6 +27,7 @@ #define TAGLIB_CHAPTERFRAME #include "id3v2frame.h" +#include "taglib_export.h" namespace TagLib { @@ -87,6 +88,7 @@ namespace TagLib { * 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; @@ -95,6 +97,7 @@ namespace TagLib { * 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; @@ -135,7 +138,7 @@ namespace TagLib { * * \see endOffset() */ - void endOffset(const uint &eO); + void setEndOffset(const uint &eO); virtual String toString() const; diff --git a/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp b/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp index bb43f5a6..1836f721 100644 --- a/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp +++ b/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp @@ -169,10 +169,10 @@ void TableOfContentsFrame::parseFields(const ByteVector &data) int pos = 0; d->elementID = readStringField(data, String::Latin1, &pos).data(String::Latin1); d->elementID.append(char(0)); - d->isTopLevel = (data.at(pos++) & 2) > 0; + d->isTopLevel = (data.at(pos) & 2) > 0; d->isOrdered = (data.at(pos++) & 1) > 0; uint entryCount = data.at(pos++); - for(int i = 0; i < entryCount; i++) + for(uint i = 0; i < entryCount; i++) { ByteVector childElementID = readStringField(data, String::Latin1, &pos).data(String::Latin1); childElementID.append(char(0)); @@ -193,7 +193,7 @@ ByteVector TableOfContentsFrame::renderFields() const flags += 1; data.append(flags); data.append((char)(entryCount())); - ConstIterator it = d->childElements.begin(); + ByteVectorList::ConstIterator it = d->childElements.begin(); while(it != d->childElements.end()) { data.append(*it); data.append(char(0)); diff --git a/tests/test_id3v2.cpp b/tests/test_id3v2.cpp index fadcae2f..117436e3 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,8 @@ class TestID3v2 : public CppUnit::TestFixture CPPUNIT_TEST(testPropertyInterface2); CPPUNIT_TEST(testDeleteFrame); CPPUNIT_TEST(testSaveAndStripID3v1ShouldNotAddFrameFromID3v1ToId3v2); + CPPUNIT_TEST(testChapters); + CPPUNIT_TEST(testTableOfContents); CPPUNIT_TEST_SUITE_END(); public: @@ -857,6 +861,66 @@ public: CPPUNIT_ASSERT(!f.ID3v2Tag()->frameListMap().contains("TPE1")); } + void testChaptersParsing() + { + ID3v2::ChapterFrame f( + ByteVector("CHAP" // Frame ID + "\x00\x00\x00\x12" // 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", 28)); // End offset + 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()); + } + + void testChapters() + { + ID3v2::ChapterFrame f( + ByteVector("CHAP" // Frame ID + "\x00\x00\x00\x12" // 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", 28)); // End offset + 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()); + } + + void testTableOfContents() + { + ID3v2::TableOfContentsFrame f( + ByteVector("CTOC" // Frame ID + "\x00\x00\x00\x08" // Frame size + "\x00\x00" // Frame flags + "\x54\x00" // Element ID + "\x01" // CTOC flags + "\x02" // Entry count + "\x43\x00" // First entry + "\x44\x00", 18)); // Second entry + 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_TEST_SUITE_REGISTRATION(TestID3v2); From 98ed58f910df8d746a32c5ffddf9d7135e7a125e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Krej=C4=8D=C3=AD?= Date: Sat, 27 Apr 2013 16:09:15 +0200 Subject: [PATCH 15/19] Changed copyright and e-mail in modified files. --- taglib/mpeg/id3v2/frames/chapterframe.cpp | 4 ++-- taglib/mpeg/id3v2/frames/chapterframe.h | 4 ++-- taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp | 4 ++-- taglib/mpeg/id3v2/frames/tableofcontentsframe.h | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/taglib/mpeg/id3v2/frames/chapterframe.cpp b/taglib/mpeg/id3v2/frames/chapterframe.cpp index 497f1a41..5787ad7f 100644 --- a/taglib/mpeg/id3v2/frames/chapterframe.cpp +++ b/taglib/mpeg/id3v2/frames/chapterframe.cpp @@ -1,6 +1,6 @@ /*************************************************************************** - copyright : (C) 2002 - 2008 by Scott Wheeler - email : wheeler@kde.org + copyright : (C) 2013 by Lukas Krejci + email : krejclu6@fel.cvut.cz ***************************************************************************/ /*************************************************************************** diff --git a/taglib/mpeg/id3v2/frames/chapterframe.h b/taglib/mpeg/id3v2/frames/chapterframe.h index 4a53cb57..6c3c1784 100644 --- a/taglib/mpeg/id3v2/frames/chapterframe.h +++ b/taglib/mpeg/id3v2/frames/chapterframe.h @@ -1,6 +1,6 @@ /*************************************************************************** - copyright : (C) 2002 - 2008 by Scott Wheeler - email : wheeler@kde.org + copyright : (C) 2013 by Lukas Krejci + email : krejclu6@fel.cvut.cz ***************************************************************************/ /*************************************************************************** diff --git a/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp b/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp index 1836f721..b1defeb8 100644 --- a/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp +++ b/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp @@ -1,6 +1,6 @@ /*************************************************************************** - copyright : (C) 2002 - 2008 by Scott Wheeler - email : wheeler@kde.org + copyright : (C) 2013 by Lukas Krejci + email : krejclu6@fel.cvut.cz ***************************************************************************/ /*************************************************************************** diff --git a/taglib/mpeg/id3v2/frames/tableofcontentsframe.h b/taglib/mpeg/id3v2/frames/tableofcontentsframe.h index 63ab96ee..9ab815e2 100644 --- a/taglib/mpeg/id3v2/frames/tableofcontentsframe.h +++ b/taglib/mpeg/id3v2/frames/tableofcontentsframe.h @@ -1,6 +1,6 @@ /*************************************************************************** - copyright : (C) 2002 - 2008 by Scott Wheeler - email : wheeler@kde.org + copyright : (C) 2013 by Lukas Krejci + email : krejclu6@fel.cvut.cz ***************************************************************************/ /*************************************************************************** From 140fb2b3f64a1ac40cf50527b9e3f5b691c20a94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Krej=C4=8D=C3=AD?= Date: Sat, 4 May 2013 21:25:55 +0200 Subject: [PATCH 16/19] Removed duplicated CHAP frame testing funtion. --- tests/test_id3v2.cpp | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/tests/test_id3v2.cpp b/tests/test_id3v2.cpp index 117436e3..b34fcbe6 100644 --- a/tests/test_id3v2.cpp +++ b/tests/test_id3v2.cpp @@ -860,25 +860,6 @@ public: MPEG::File f(newname.c_str()); CPPUNIT_ASSERT(!f.ID3v2Tag()->frameListMap().contains("TPE1")); } - - void testChaptersParsing() - { - ID3v2::ChapterFrame f( - ByteVector("CHAP" // Frame ID - "\x00\x00\x00\x12" // 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", 28)); // End offset - 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()); - } void testChapters() { From 5c5c89e8d99e9b04f2130d6be2270b0f098fcf43 Mon Sep 17 00:00:00 2001 From: Lukas Krejci Date: Tue, 8 Oct 2013 18:19:15 +0200 Subject: [PATCH 17/19] Added functions for work with embedded frames. Added embedded frames parsing. Added embedded frames rendering. Modified constructor of CHAP and CTOC frame, so it can accept list of embedded frames. Added unit tests for CHAP and CTOC frames parsing and rendering (with support of embedded frames). Fixed bugs in rendering of CTOC frames. Added functions for adding and removing child elements in CTOC frames. --- taglib/mpeg/id3v2/frames/chapterframe.cpp | 86 ++++++++++++++- taglib/mpeg/id3v2/frames/chapterframe.h | 76 ++++++++++++- .../id3v2/frames/tableofcontentsframe.cpp | 97 ++++++++++++++-- .../mpeg/id3v2/frames/tableofcontentsframe.h | 90 ++++++++++++++- tests/test_id3v2.cpp | 104 +++++++++++++++--- 5 files changed, 419 insertions(+), 34 deletions(-) diff --git a/taglib/mpeg/id3v2/frames/chapterframe.cpp b/taglib/mpeg/id3v2/frames/chapterframe.cpp index 5787ad7f..717ea108 100644 --- a/taglib/mpeg/id3v2/frames/chapterframe.cpp +++ b/taglib/mpeg/id3v2/frames/chapterframe.cpp @@ -26,8 +26,8 @@ #include #include #include +#include -#include "id3v2tag.h" #include "chapterframe.h" using namespace TagLib; @@ -41,6 +41,9 @@ public: uint endTime; uint startOffset; uint endOffset; + const FrameFactory *factory; + FrameListMap embeddedFrameListMap; + FrameList embeddedFrameList; }; //////////////////////////////////////////////////////////////////////////////// @@ -51,10 +54,11 @@ 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) : +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; @@ -63,6 +67,10 @@ ChapterFrame::ChapterFrame(const ByteVector &eID, const uint &sT, const uint &eT 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() @@ -122,6 +130,49 @@ 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; @@ -154,12 +205,13 @@ ChapterFrame *ChapterFrame::findByElementID(const Tag *tag, const ByteVector &eI void ChapterFrame::parseFields(const ByteVector &data) { - if(data.size() < 18) { - debug("An 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)."); + 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; + 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); @@ -169,6 +221,24 @@ void ChapterFrame::parseFields(const ByteVector &data) 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 @@ -180,7 +250,10 @@ ByteVector ChapterFrame::renderFields() const 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; } @@ -188,5 +261,6 @@ 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 index 6c3c1784..84b42137 100644 --- a/taglib/mpeg/id3v2/frames/chapterframe.h +++ b/taglib/mpeg/id3v2/frames/chapterframe.h @@ -26,6 +26,7 @@ #ifndef TAGLIB_CHAPTERFRAME #define TAGLIB_CHAPTERFRAME +#include "id3v2tag.h" #include "id3v2frame.h" #include "taglib_export.h" @@ -52,10 +53,10 @@ namespace TagLib { /*! * Creates a chapter frame with the element ID \a eID, - * start time \a sT, end time \a eT, start offset \a sO - * and end offset \a eO. + * 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); + ChapterFrame(const ByteVector &eID, const uint &sT, const uint &eT, const uint &sO, const uint &eO, const FrameList &eF); /*! * Destroys the frame. @@ -139,6 +140,75 @@ namespace TagLib { * \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; diff --git a/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp b/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp index b1defeb8..9f51ebfd 100644 --- a/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp +++ b/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp @@ -27,7 +27,6 @@ #include #include -#include "id3v2tag.h" #include "tableofcontentsframe.h" using namespace TagLib; @@ -40,6 +39,9 @@ public: bool isTopLevel; bool isOrdered; ByteVectorList childElements; + const FrameFactory *factory; + FrameListMap embeddedFrameListMap; + FrameList embeddedFrameList; }; //////////////////////////////////////////////////////////////////////////////// @@ -50,15 +52,20 @@ 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) : +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() @@ -113,6 +120,60 @@ 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; @@ -161,12 +222,13 @@ TableOfContentsFrame *TableOfContentsFrame::findTopLevel(const Tag *tag) // stat void TableOfContentsFrame::parseFields(const ByteVector &data) { - if(data.size() < 6) { - debug("An 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."); + 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; + 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; @@ -178,6 +240,24 @@ void TableOfContentsFrame::parseFields(const ByteVector &data) 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 @@ -185,7 +265,6 @@ ByteVector TableOfContentsFrame::renderFields() const ByteVector data; data.append(d->elementID); - data.append(char(0)); char flags = 0; if(d->isTopLevel) flags += 2; @@ -196,9 +275,12 @@ ByteVector TableOfContentsFrame::renderFields() const ByteVectorList::ConstIterator it = d->childElements.begin(); while(it != d->childElements.end()) { data.append(*it); - data.append(char(0)); + //data.append(char(0)); it++; } + FrameList l = d->embeddedFrameList; + for(FrameList::Iterator it = l.begin(); it != l.end(); ++it) + data.append((*it)->render()); return data; } @@ -207,5 +289,6 @@ 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 index 9ab815e2..bf578d42 100644 --- a/taglib/mpeg/id3v2/frames/tableofcontentsframe.h +++ b/taglib/mpeg/id3v2/frames/tableofcontentsframe.h @@ -26,6 +26,7 @@ #ifndef TAGLIB_TABLEOFCONTENTSFRAME #define TAGLIB_TABLEOFCONTENTSFRAME +#include "id3v2tag.h" #include "id3v2frame.h" namespace TagLib { @@ -50,10 +51,10 @@ namespace TagLib { TableOfContentsFrame(const ByteVector &data); /*! - * Creates a table of contents frame with the element ID \a eID and - * the child elements \a ch. + * 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); + TableOfContentsFrame(const ByteVector &eID, const ByteVectorList &ch, const FrameList &eF); /*! * Destroys the frame. @@ -129,6 +130,89 @@ namespace TagLib { * \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; diff --git a/tests/test_id3v2.cpp b/tests/test_id3v2.cpp index b34fcbe6..79c406e4 100644 --- a/tests/test_id3v2.cpp +++ b/tests/test_id3v2.cpp @@ -84,14 +84,16 @@ class TestID3v2 : public CppUnit::TestFixture CPPUNIT_TEST(testUpdateDate22); CPPUNIT_TEST(testDowngradeTo23); // CPPUNIT_TEST(testUpdateFullDate22); TODO TYE+TDA should be upgraded to TDRC together - CPPUNIT_TEST(testCompressedFrameWithBrokenLength); + //CPPUNIT_TEST(testCompressedFrameWithBrokenLength); CPPUNIT_TEST(testW000); CPPUNIT_TEST(testPropertyInterface); CPPUNIT_TEST(testPropertyInterface2); CPPUNIT_TEST(testDeleteFrame); CPPUNIT_TEST(testSaveAndStripID3v1ShouldNotAddFrameFromID3v1ToId3v2); - CPPUNIT_TEST(testChapters); - CPPUNIT_TEST(testTableOfContents); + CPPUNIT_TEST(testParseChapterFrame); + CPPUNIT_TEST(testRenderChapterFrame); + CPPUNIT_TEST(testParseTableOfContentsFrame); + CPPUNIT_TEST(testRenderTableOfContentsFrame); CPPUNIT_TEST_SUITE_END(); public: @@ -861,36 +863,77 @@ public: CPPUNIT_ASSERT(!f.ID3v2Tag()->frameListMap().contains("TPE1")); } - void testChapters() + void testParseChapterFrame() { ID3v2::ChapterFrame f( ByteVector("CHAP" // Frame ID - "\x00\x00\x00\x12" // Frame size + "\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", 28)); // End offset + "\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 testTableOfContents() + 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\x08" // Frame size + "\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", 18)); // Second entry + "\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()); @@ -900,6 +943,37 @@ public: 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()); } }; From 500b3e630b18250393a5a65fb698ab934f21c7c8 Mon Sep 17 00:00:00 2001 From: Lukas Krejci Date: Sun, 13 Oct 2013 18:38:54 +0200 Subject: [PATCH 18/19] Uncommenting unit test. --- tests/test_id3v2.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_id3v2.cpp b/tests/test_id3v2.cpp index 79c406e4..2e04bdba 100644 --- a/tests/test_id3v2.cpp +++ b/tests/test_id3v2.cpp @@ -84,7 +84,7 @@ class TestID3v2 : public CppUnit::TestFixture CPPUNIT_TEST(testUpdateDate22); CPPUNIT_TEST(testDowngradeTo23); // CPPUNIT_TEST(testUpdateFullDate22); TODO TYE+TDA should be upgraded to TDRC together - //CPPUNIT_TEST(testCompressedFrameWithBrokenLength); + CPPUNIT_TEST(testCompressedFrameWithBrokenLength); CPPUNIT_TEST(testW000); CPPUNIT_TEST(testPropertyInterface); CPPUNIT_TEST(testPropertyInterface2); From a192db07c0a55d244a7e38affecd1454e7294ba8 Mon Sep 17 00:00:00 2001 From: Lukas Krejci Date: Wed, 11 Jun 2014 17:52:05 +0200 Subject: [PATCH 19/19] Code clean-up. --- taglib/mpeg/id3v2/frames/chapterframe.cpp | 2 +- taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/taglib/mpeg/id3v2/frames/chapterframe.cpp b/taglib/mpeg/id3v2/frames/chapterframe.cpp index 717ea108..8c5514e2 100644 --- a/taglib/mpeg/id3v2/frames/chapterframe.cpp +++ b/taglib/mpeg/id3v2/frames/chapterframe.cpp @@ -80,7 +80,7 @@ ChapterFrame::~ChapterFrame() ByteVector ChapterFrame::elementID() const { - return d->elementID; + return d->elementID; } uint ChapterFrame::startTime() const diff --git a/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp b/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp index 9f51ebfd..f986eb9a 100644 --- a/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp +++ b/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp @@ -275,7 +275,6 @@ ByteVector TableOfContentsFrame::renderFields() const ByteVectorList::ConstIterator it = d->childElements.begin(); while(it != d->childElements.end()) { data.append(*it); - //data.append(char(0)); it++; } FrameList l = d->embeddedFrameList;