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/35] 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/35] 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/35] 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/35] 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/35] 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/35] 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/35] 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/35] 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/35] 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 1723e9b18aa6f24cacf878f34ad34ad887b22bce Mon Sep 17 00:00:00 2001 From: Michael Helmling Date: Thu, 3 Apr 2014 21:07:29 +0200 Subject: [PATCH 10/35] Fix #162: Xiph and APE generic getters return space-concatenated values --- taglib/ape/apetag.cpp | 10 +++++----- taglib/ogg/xiphcomment.cpp | 12 ++++++------ tests/test_apetag.cpp | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/taglib/ape/apetag.cpp b/taglib/ape/apetag.cpp index 664ee3a8..3a30fd4a 100644 --- a/taglib/ape/apetag.cpp +++ b/taglib/ape/apetag.cpp @@ -89,35 +89,35 @@ String APE::Tag::title() const { if(d->itemListMap["TITLE"].isEmpty()) return String::null; - return d->itemListMap["TITLE"].toString(); + return d->itemListMap["TITLE"].values().toString(); } String APE::Tag::artist() const { if(d->itemListMap["ARTIST"].isEmpty()) return String::null; - return d->itemListMap["ARTIST"].toString(); + return d->itemListMap["ARTIST"].values().toString(); } String APE::Tag::album() const { if(d->itemListMap["ALBUM"].isEmpty()) return String::null; - return d->itemListMap["ALBUM"].toString(); + return d->itemListMap["ALBUM"].values().toString(); } String APE::Tag::comment() const { if(d->itemListMap["COMMENT"].isEmpty()) return String::null; - return d->itemListMap["COMMENT"].toString(); + return d->itemListMap["COMMENT"].values().toString(); } String APE::Tag::genre() const { if(d->itemListMap["GENRE"].isEmpty()) return String::null; - return d->itemListMap["GENRE"].toString(); + return d->itemListMap["GENRE"].values().toString(); } TagLib::uint APE::Tag::year() const diff --git a/taglib/ogg/xiphcomment.cpp b/taglib/ogg/xiphcomment.cpp index 51c2f9f0..9462607f 100644 --- a/taglib/ogg/xiphcomment.cpp +++ b/taglib/ogg/xiphcomment.cpp @@ -63,33 +63,33 @@ String Ogg::XiphComment::title() const { if(d->fieldListMap["TITLE"].isEmpty()) return String::null; - return d->fieldListMap["TITLE"].front(); + return d->fieldListMap["TITLE"].toString(); } String Ogg::XiphComment::artist() const { if(d->fieldListMap["ARTIST"].isEmpty()) return String::null; - return d->fieldListMap["ARTIST"].front(); + return d->fieldListMap["ARTIST"].toString(); } String Ogg::XiphComment::album() const { if(d->fieldListMap["ALBUM"].isEmpty()) return String::null; - return d->fieldListMap["ALBUM"].front(); + return d->fieldListMap["ALBUM"].toString(); } String Ogg::XiphComment::comment() const { if(!d->fieldListMap["DESCRIPTION"].isEmpty()) { d->commentField = "DESCRIPTION"; - return d->fieldListMap["DESCRIPTION"].front(); + return d->fieldListMap["DESCRIPTION"].toString(); } if(!d->fieldListMap["COMMENT"].isEmpty()) { d->commentField = "COMMENT"; - return d->fieldListMap["COMMENT"].front(); + return d->fieldListMap["COMMENT"].toString(); } return String::null; @@ -99,7 +99,7 @@ String Ogg::XiphComment::genre() const { if(d->fieldListMap["GENRE"].isEmpty()) return String::null; - return d->fieldListMap["GENRE"].front(); + return d->fieldListMap["GENRE"].toString(); } TagLib::uint Ogg::XiphComment::year() const diff --git a/tests/test_apetag.cpp b/tests/test_apetag.cpp index 845828f5..1a66cdd5 100644 --- a/tests/test_apetag.cpp +++ b/tests/test_apetag.cpp @@ -52,7 +52,7 @@ public: tag.setProperties(dict); CPPUNIT_ASSERT_EQUAL(String("17"), tag.itemListMap()["TRACK"].values()[0]); CPPUNIT_ASSERT_EQUAL(2u, tag.itemListMap()["ARTIST"].values().size()); - CPPUNIT_ASSERT_EQUAL(String("artist 1"), tag.artist()); + CPPUNIT_ASSERT_EQUAL(String("artist 1 artist 2"), tag.artist()); CPPUNIT_ASSERT_EQUAL(17u, tag.track()); } 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 11/35] 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 12/35] 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 13/35] 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 14/35] 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 15/35] 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 16/35] 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 17/35] 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 18/35] 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 19/35] 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 20/35] 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; From 663f10d51d2b1846d4fb1ef6dfe3949345a4945f Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Thu, 19 Jun 2014 11:55:52 +0900 Subject: [PATCH 21/35] Fixed out-of-bounds access in findVector(). --- taglib/toolkit/tbytevector.cpp | 44 +++++++++++++++++----------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/taglib/toolkit/tbytevector.cpp b/taglib/toolkit/tbytevector.cpp index 566a20fe..9522f0af 100644 --- a/taglib/toolkit/tbytevector.cpp +++ b/taglib/toolkit/tbytevector.cpp @@ -100,7 +100,7 @@ static const uint crcTable[256] = { }; /*! - * A templatized straightforward find that works with the types + * A templatized straightforward find that works with the types * std::vector::iterator and std::vector::reverse_iterator. */ template @@ -109,7 +109,7 @@ int findChar( char c, uint offset, int byteAlign) { const size_t dataSize = dataEnd - dataBegin; - if(dataSize == 0 || offset > dataSize - 1) + if(dataSize == 0 || offset + 1 > dataSize) return -1; // n % 0 is invalid @@ -126,7 +126,7 @@ int findChar( } /*! - * A templatized KMP find that works with the types + * A templatized KMP find that works with the types * std::vector::iterator and std::vector::reverse_iterator. */ template @@ -137,7 +137,7 @@ int findVector( { const size_t dataSize = dataEnd - dataBegin; const size_t patternSize = patternEnd - patternBegin; - if(patternSize > dataSize || offset > dataSize - 1) + if(dataSize == 0 || offset + patternSize > dataSize) return -1; // n % 0 is invalid @@ -213,7 +213,7 @@ T toNumber(const ByteVector &v, size_t offset, bool mostSignificantByteFirst) static const bool isBigEndian = (Utils::SystemByteOrder == Utils::BigEndian); const bool swap = (mostSignificantByteFirst != isBigEndian); - if(offset + sizeof(T) > v.size()) + if(offset + sizeof(T) > v.size()) return toNumber(v, offset, v.size() - offset, mostSignificantByteFirst); // Uses memcpy instead of reinterpret_cast to avoid an alignment exception. @@ -245,8 +245,8 @@ public: { } - DataPrivate(const std::vector &v, uint offset, uint length) - : data(v.begin() + offset, v.begin() + offset + length) + DataPrivate(const std::vector &v, uint offset, uint length) + : data(v.begin() + offset, v.begin() + offset + length) { } @@ -256,8 +256,8 @@ public: { } - DataPrivate(uint len, char c) - : data(len, c) + DataPrivate(uint len, char c) + : data(len, c) { } @@ -267,11 +267,11 @@ public: class ByteVector::ByteVectorPrivate : public RefCounter { public: - ByteVectorPrivate() + ByteVectorPrivate() : RefCounter() , data(new DataPrivate()) , offset(0) - , length(0) + , length(0) { } @@ -292,7 +292,7 @@ public: { } - ByteVectorPrivate(uint l, char c) + ByteVectorPrivate(uint l, char c) : RefCounter() , data(new DataPrivate(l, c)) , offset(0) @@ -300,14 +300,14 @@ public: { } - ByteVectorPrivate(const char *s, uint l) + ByteVectorPrivate(const char *s, uint l) : RefCounter() , data(new DataPrivate(s, s + l)) , offset(0) , length(l) { } - + void detach() { if(data->count() > 1) { @@ -385,7 +385,7 @@ ByteVector::ByteVector(uint size, char value) { } -ByteVector::ByteVector(const ByteVector &v) +ByteVector::ByteVector(const ByteVector &v) : d(v.d) { d->ref(); @@ -488,9 +488,9 @@ bool ByteVector::containsAt(const ByteVector &pattern, uint offset, uint pattern // do some sanity checking -- all of these things are needed for the search to be valid const uint compareLength = patternLength - patternOffset; - if(offset + compareLength > size() || patternOffset >= pattern.size() || patternLength == 0) + if(offset + compareLength > size() || patternOffset >= pattern.size() || patternLength == 0) return false; - + return (::memcmp(data() + offset, pattern.data() + patternOffset, compareLength) == 0); } @@ -512,7 +512,7 @@ ByteVector &ByteVector::replace(const ByteVector &pattern, const ByteVector &wit const size_t withSize = with.size(); const size_t patternSize = pattern.size(); const ptrdiff_t diff = withSize - patternSize; - + size_t offset = 0; while (true) { @@ -524,16 +524,16 @@ ByteVector &ByteVector::replace(const ByteVector &pattern, const ByteVector &wit if(diff < 0) { ::memmove( - data() + offset + withSize, - data() + offset + patternSize, + data() + offset + withSize, + data() + offset + patternSize, size() - offset - patternSize); resize(size() + diff); } else if(diff > 0) { resize(size() + diff); ::memmove( - data() + offset + withSize, - data() + offset + patternSize, + data() + offset + withSize, + data() + offset + patternSize, size() - diff - offset - patternSize); } From 29377fc8f17f5d9b3c8c2e582b22e0a2955cb494 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Thu, 19 Jun 2014 12:51:44 +0900 Subject: [PATCH 22/35] Added some SetLastError()s to get correct error codes in Win32. --- taglib/toolkit/tfilestream.cpp | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/taglib/toolkit/tfilestream.cpp b/taglib/toolkit/tfilestream.cpp index eae52573..f9895d78 100644 --- a/taglib/toolkit/tfilestream.cpp +++ b/taglib/toolkit/tfilestream.cpp @@ -36,7 +36,7 @@ using namespace TagLib; -namespace +namespace { #ifdef _WIN32 @@ -79,7 +79,7 @@ namespace DWORD length; if(WriteFile(file, buffer.data(), static_cast(buffer.size()), &length, NULL)) return static_cast(length); - else + else return 0; } @@ -151,13 +151,13 @@ FileStream::FileStream(FileName fileName, bool openReadOnly) else d->file = openFile(fileName, true); - if(d->file == InvalidFileHandle) + if(d->file == InvalidFileHandle) { # ifdef _WIN32 debug("Could not open file " + fileName.toString()); # else debug("Could not open file " + String(static_cast(d->name))); -# endif +# endif } } @@ -192,7 +192,7 @@ ByteVector FileStream::readBlock(ulong length) const size_t count = readFile(d->file, buffer); buffer.resize(static_cast(count)); - + return buffer; } @@ -262,7 +262,7 @@ void FileStream::insert(const ByteVector &data, ulong start, ulong replace) { // Seek to the current read position and read the data that we're about // to overwrite. Appropriately increment the readPosition. - + seek(readPosition); const size_t bytesRead = readFile(d->file, aboutToOverwrite); aboutToOverwrite.resize(bytesRead); @@ -288,7 +288,7 @@ void FileStream::insert(const ByteVector &data, ulong start, ulong replace) writePosition += buffer.size(); // Make the current buffer the data that we read in the beginning. - + buffer = aboutToOverwrite; } } @@ -365,7 +365,12 @@ void FileStream::seek(long offset, Position p) return; } + SetLastError(NO_ERROR); SetFilePointer(d->file, offset, NULL, whence); + if(GetLastError() == ERROR_NEGATIVE_SEEK) { + SetLastError(NO_ERROR); + SetFilePointer(d->file, 0, NULL, FILE_BEGIN); + } if(GetLastError() != NO_ERROR) { debug("File::seek() -- Failed to set the file pointer."); } @@ -410,6 +415,7 @@ long FileStream::tell() const { #ifdef _WIN32 + SetLastError(NO_ERROR); const DWORD position = SetFilePointer(d->file, 0, NULL, FILE_CURRENT); if(GetLastError() == NO_ERROR) { return static_cast(position); @@ -435,6 +441,7 @@ long FileStream::length() #ifdef _WIN32 + SetLastError(NO_ERROR); const DWORD fileSize = GetFileSize(d->file, NULL); if(GetLastError() == NO_ERROR) { return static_cast(fileSize); @@ -469,6 +476,8 @@ void FileStream::truncate(long length) const long currentPos = tell(); seek(length); + + SetLastError(NO_ERROR); SetEndOfFile(d->file); if(GetLastError() != NO_ERROR) { debug("File::truncate() -- Failed to truncate the file."); From cb3abf15ad4aa10c3cdc5e7746b3a071e9907bda Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Thu, 19 Jun 2014 13:22:50 +0900 Subject: [PATCH 23/35] Added a test for out-of-bounds access in findVector(). --- tests/test_bytevector.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/test_bytevector.cpp b/tests/test_bytevector.cpp index eca74f8f..c6a96198 100644 --- a/tests/test_bytevector.cpp +++ b/tests/test_bytevector.cpp @@ -122,7 +122,7 @@ public: CPPUNIT_ASSERT(i.containsAt(j, 6, 1)); CPPUNIT_ASSERT(i.containsAt(j, 6, 1, 3)); } - + void testFind1() { CPPUNIT_ASSERT_EQUAL(4, ByteVector("....SggO."). find("SggO")); @@ -135,6 +135,12 @@ public: CPPUNIT_ASSERT_EQUAL(-1, ByteVector("....SggO."). find("SggO", 6)); CPPUNIT_ASSERT_EQUAL(-1, ByteVector("....SggO."). find("SggO", 7)); CPPUNIT_ASSERT_EQUAL(-1, ByteVector("....SggO."). find("SggO", 8)); + + // Intentional out-of-bounds access. + ByteVector v("0123456789x"); + v.resize(10); + v.data()[10] = 'x'; + CPPUNIT_ASSERT_EQUAL(-1, v.find("789x", 7)); } void testFind2() From 653b631aea231d6cd9ec8825f55495d2280bbe63 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Thu, 19 Jun 2014 16:53:29 +0900 Subject: [PATCH 24/35] Added a check for patternSize and removed a useless check. --- taglib/toolkit/tbytevector.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/taglib/toolkit/tbytevector.cpp b/taglib/toolkit/tbytevector.cpp index 9522f0af..1ecd93d4 100644 --- a/taglib/toolkit/tbytevector.cpp +++ b/taglib/toolkit/tbytevector.cpp @@ -109,7 +109,7 @@ int findChar( char c, uint offset, int byteAlign) { const size_t dataSize = dataEnd - dataBegin; - if(dataSize == 0 || offset + 1 > dataSize) + if(offset + 1 > dataSize) return -1; // n % 0 is invalid @@ -137,7 +137,7 @@ int findVector( { const size_t dataSize = dataEnd - dataBegin; const size_t patternSize = patternEnd - patternBegin; - if(dataSize == 0 || offset + patternSize > dataSize) + if(patternSize == 0 || offset + patternSize > dataSize) return -1; // n % 0 is invalid From fadb57e4cdfd1a83de227eaa33899dc68f471136 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Thu, 19 Jun 2014 18:27:08 +0900 Subject: [PATCH 25/35] Added a Windows-friendly build option ZLIB_SOURCE. --- CMakeLists.txt | 5 +++++ ConfigureChecks.cmake | 1 - INSTALL | 1 + taglib/CMakeLists.txt | 18 ++++++++++++++++-- 4 files changed, 22 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a27b2f6e..02eb9893 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -85,6 +85,11 @@ if(NOT WIN32 AND NOT BUILD_FRAMEWORK) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/taglib.pc DESTINATION ${LIB_INSTALL_DIR}/pkgconfig) endif() +if(NOT HAVE_ZLIB AND ZLIB_SOURCE) + set(HAVE_ZLIB 1) + set(HAVE_ZLIB_SOURCE 1) +endif() + include_directories(${CMAKE_CURRENT_BINARY_DIR}) configure_file(config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config.h) diff --git a/ConfigureChecks.cmake b/ConfigureChecks.cmake index b0f164a9..1df69f95 100644 --- a/ConfigureChecks.cmake +++ b/ConfigureChecks.cmake @@ -216,7 +216,6 @@ else() set(HAVE_ZLIB 0) endif() - set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules) find_package(CppUnit) diff --git a/INSTALL b/INSTALL index 5e36d30c..0f905006 100644 --- a/INSTALL +++ b/INSTALL @@ -72,6 +72,7 @@ Useful configuration options used with CMake (GUI and/or Command line): Assumes parent of: \include and \lib. ZLIB_INCLUDE_DIR= Where to find ZLib's Include directory. ZLIB_LIBRARY= Where to find ZLib's Library. + ZLIB_SOURCE= Where to find ZLib's source code. CMAKE_INSTALL_PREFIX= Where to install Taglib. CMAKE_BUILD_TYPE= Release, Debug, etc ... (Not available in MSVC) diff --git a/taglib/CMakeLists.txt b/taglib/CMakeLists.txt index 89cb8e12..0325ea94 100644 --- a/taglib/CMakeLists.txt +++ b/taglib/CMakeLists.txt @@ -28,6 +28,8 @@ include_directories( if(ZLIB_FOUND) include_directories(${ZLIB_INCLUDE_DIR}) +elseif(HAVE_ZLIB_SOURCE) + include_directories(${ZLIB_SOURCE}) endif() set(tag_HDRS @@ -300,6 +302,18 @@ set(toolkit_SRCS toolkit/unicode.cpp ) +if(HAVE_ZLIB_SOURCE) + set(zlib_SRCS + ${ZLIB_SOURCE}/adler32.c + ${ZLIB_SOURCE}/crc32.c + ${ZLIB_SOURCE}/inffast.c + ${ZLIB_SOURCE}/inflate.c + ${ZLIB_SOURCE}/inftrees.c + ${ZLIB_SOURCE}/uncompr.c + ${ZLIB_SOURCE}/zutil.c + ) +endif() + set(tag_LIB_SRCS ${mpeg_SRCS} ${id3v1_SRCS} ${id3v2_SRCS} ${frames_SRCS} ${ogg_SRCS} ${vorbis_SRCS} ${oggflacs_SRCS} ${mpc_SRCS} ${ape_SRCS} ${toolkit_SRCS} ${flacs_SRCS} @@ -311,10 +325,10 @@ set(tag_LIB_SRCS audioproperties.cpp ) -add_library(tag ${tag_LIB_SRCS} ${tag_HDRS}) +add_library(tag ${tag_LIB_SRCS} ${zlib_SRCS} ${tag_HDRS}) if(ZLIB_FOUND) - target_link_libraries(tag ${ZLIB_LIBRARIES}) + target_link_libraries(tag ${ZLIB_LIBRARIES}) endif() set_target_properties(tag PROPERTIES From 29a5129096c0bb0b762ad41138b862bfda3c2d08 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Thu, 19 Jun 2014 11:55:52 +0900 Subject: [PATCH 26/35] Modified some tests to work with MSVC. --- tests/test_flac.cpp | 12 ++- tests/test_id3v2.cpp | 201 ++++++++++++++++++++----------------- tests/test_mp4.cpp | 4 +- tests/test_mpeg.cpp | 71 +++++++------ tests/test_propertymap.cpp | 6 +- tests/test_string.cpp | 2 +- 6 files changed, 169 insertions(+), 127 deletions(-) diff --git a/tests/test_flac.cpp b/tests/test_flac.cpp index caec7153..22bbc854 100644 --- a/tests/test_flac.cpp +++ b/tests/test_flac.cpp @@ -91,6 +91,7 @@ public: newpic->setData("JPEG data"); f->addPicture(newpic); f->save(); + delete f; f = new FLAC::File(newname.c_str()); lst = f->pictureList(); @@ -115,6 +116,7 @@ public: CPPUNIT_ASSERT_EQUAL(String("image/jpeg"), pic->mimeType()); CPPUNIT_ASSERT_EQUAL(String("new image"), pic->description()); CPPUNIT_ASSERT_EQUAL(ByteVector("JPEG data"), pic->data()); + delete f; } void testReplacePicture() @@ -138,6 +140,7 @@ public: f->removePictures(); f->addPicture(newpic); f->save(); + delete f; f = new FLAC::File(newname.c_str()); lst = f->pictureList(); @@ -152,6 +155,7 @@ public: CPPUNIT_ASSERT_EQUAL(String("image/jpeg"), pic->mimeType()); CPPUNIT_ASSERT_EQUAL(String("new image"), pic->description()); CPPUNIT_ASSERT_EQUAL(ByteVector("JPEG data"), pic->data()); + delete f; } void testRemoveAllPictures() @@ -165,10 +169,12 @@ public: f->removePictures(); f->save(); + delete f; f = new FLAC::File(newname.c_str()); lst = f->pictureList(); CPPUNIT_ASSERT_EQUAL(TagLib::uint(0), lst.size()); + delete f; } void testRepeatedSave() @@ -185,10 +191,12 @@ public: tag->setTitle("NEW TITLE 2"); f->save(); CPPUNIT_ASSERT_EQUAL(String("NEW TITLE 2"), tag->title()); + delete f; f = new FLAC::File(newname.c_str()); tag = f->tag(); CPPUNIT_ASSERT_EQUAL(String("NEW TITLE 2"), tag->title()); + delete f; } void testSaveMultipleValues() @@ -209,6 +217,7 @@ public: CPPUNIT_ASSERT_EQUAL(TagLib::uint(2), m["ARTIST"].size()); CPPUNIT_ASSERT_EQUAL(String("artist 1"), m["ARTIST"][0]); CPPUNIT_ASSERT_EQUAL(String("artist 2"), m["ARTIST"][1]); + delete f; } void testDict() @@ -230,13 +239,14 @@ public: CPPUNIT_ASSERT_EQUAL(TagLib::uint(2), dict["ARTIST"].size()); CPPUNIT_ASSERT_EQUAL(String("artøst 1"), dict["ARTIST"][0]); CPPUNIT_ASSERT_EQUAL(String("artöst 2"), dict["ARTIST"][1]); + delete f; } void testInvalid() { ScopedFileCopy copy("silence-44-s", ".flac"); PropertyMap map; - map["HÄÖ"] = String("bla"); + map[L"H\x00c4\x00d6"] = String("bla"); FLAC::File f(copy.fileName().c_str()); PropertyMap invalid = f.setProperties(map); CPPUNIT_ASSERT_EQUAL(TagLib::uint(1), invalid.size()); diff --git a/tests/test_id3v2.cpp b/tests/test_id3v2.cpp index fadcae2f..b85d4a71 100644 --- a/tests/test_id3v2.cpp +++ b/tests/test_id3v2.cpp @@ -4,12 +4,9 @@ #include #include -// so evil :( -#define protected public #include #include #include -#undef protected #include #include #include @@ -101,13 +98,23 @@ public: void testDowngradeUTF8ForID3v23() { - ID3v2::TextIdentificationFrame f(ByteVector("TPE1"), String::UTF8); + ScopedFileCopy copy("xing", ".mp3"); + string newname = copy.fileName(); + + ID3v2::TextIdentificationFrame *f + = new ID3v2::TextIdentificationFrame(ByteVector("TPE1"), String::UTF8); StringList sl; sl.append("Foo"); - f.setText(sl); - f.header()->setVersion(3); - ByteVector data = f.render(); + f->setText(sl); + + MPEG::File file(newname.c_str()); + file.ID3v2Tag(true)->addFrame(f); + file.save(MPEG::File::ID3v2, true, 3); + CPPUNIT_ASSERT_EQUAL(true, file.hasID3v2Tag()); + + ByteVector data = f->render(); CPPUNIT_ASSERT_EQUAL((unsigned int)(4+4+2+1+6+2), data.size()); + ID3v2::TextIdentificationFrame f2(data); CPPUNIT_ASSERT_EQUAL(sl, f2.fieldList()); CPPUNIT_ASSERT_EQUAL(String::UTF16, f2.textEncoding()); @@ -293,13 +300,16 @@ public: f->setRating(200); f->setCounter(3); - MPEG::File foo(newname.c_str()); - foo.ID3v2Tag()->addFrame(f); - foo.save(); - - MPEG::File bar(newname.c_str()); - CPPUNIT_ASSERT_EQUAL(String("email@example.com"), dynamic_cast(bar.ID3v2Tag()->frameList("POPM").front())->email()); - CPPUNIT_ASSERT_EQUAL(200, dynamic_cast(bar.ID3v2Tag()->frameList("POPM").front())->rating()); + { + MPEG::File foo(newname.c_str()); + foo.ID3v2Tag()->addFrame(f); + foo.save(); + } + { + MPEG::File bar(newname.c_str()); + CPPUNIT_ASSERT_EQUAL(String("email@example.com"), dynamic_cast(bar.ID3v2Tag()->frameList("POPM").front())->email()); + CPPUNIT_ASSERT_EQUAL(200, dynamic_cast(bar.ID3v2Tag()->frameList("POPM").front())->rating()); + } } // http://bugs.kde.org/show_bug.cgi?id=150481 @@ -400,7 +410,7 @@ public: "http://example.com", 33), // URL f.render()); } - + void testParseOwnershipFrame() { ID3v2::OwnershipFrame f( @@ -545,13 +555,17 @@ public: ScopedFileCopy copy("xing", ".mp3"); string newname = copy.fileName(); ID3v2::FrameFactory::instance()->setDefaultTextEncoding(String::UTF16); - MPEG::File foo(newname.c_str()); - foo.strip(); - foo.tag()->setComment("Test comment!"); - foo.save(); - MPEG::File bar(newname.c_str()); - CPPUNIT_ASSERT_EQUAL(String("Test comment!"), bar.tag()->comment()); - ID3v2::FrameFactory::instance()->setDefaultTextEncoding(defaultEncoding); + { + MPEG::File foo(newname.c_str()); + foo.strip(); + foo.tag()->setComment("Test comment!"); + foo.save(); + } + { + MPEG::File bar(newname.c_str()); + CPPUNIT_ASSERT_EQUAL(String("Test comment!"), bar.tag()->comment()); + ID3v2::FrameFactory::instance()->setDefaultTextEncoding(defaultEncoding); + } } void testUpdateGenre23_1() @@ -632,57 +646,60 @@ public: string newname = copy.fileName(); ID3v2::TextIdentificationFrame *tf; - MPEG::File foo(newname.c_str()); - tf = new ID3v2::TextIdentificationFrame("TDOR", String::Latin1); - tf->setText("2011-03-16"); - foo.ID3v2Tag()->addFrame(tf); - tf = new ID3v2::TextIdentificationFrame("TDRC", String::Latin1); - tf->setText("2012-04-17T12:01"); - foo.ID3v2Tag()->addFrame(tf); - tf = new ID3v2::TextIdentificationFrame("TMCL", String::Latin1); - tf->setText(StringList().append("Guitar").append("Artist 1").append("Drums").append("Artist 2")); - foo.ID3v2Tag()->addFrame(tf); - tf = new ID3v2::TextIdentificationFrame("TIPL", String::Latin1); - tf->setText(StringList().append("Producer").append("Artist 3").append("Mastering").append("Artist 4")); - foo.ID3v2Tag()->addFrame(tf); - foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TDRL", String::Latin1)); - foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TDTG", String::Latin1)); - foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TMOO", String::Latin1)); - foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TPRO", String::Latin1)); - foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TSOA", String::Latin1)); - foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TSOT", String::Latin1)); - foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TSST", String::Latin1)); - foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TSOP", String::Latin1)); - foo.save(MPEG::File::AllTags, true, 3); - - MPEG::File bar(newname.c_str()); - tf = static_cast(bar.ID3v2Tag()->frameList("TDOR").front()); - CPPUNIT_ASSERT(tf); - CPPUNIT_ASSERT_EQUAL(TagLib::uint(1), tf->fieldList().size()); - CPPUNIT_ASSERT_EQUAL(String("2011"), tf->fieldList().front()); - tf = static_cast(bar.ID3v2Tag()->frameList("TDRC").front()); - CPPUNIT_ASSERT(tf); - CPPUNIT_ASSERT_EQUAL(TagLib::uint(1), tf->fieldList().size()); - CPPUNIT_ASSERT_EQUAL(String("2012"), tf->fieldList().front()); - tf = dynamic_cast(bar.ID3v2Tag()->frameList("TIPL").front()); - CPPUNIT_ASSERT(tf); - CPPUNIT_ASSERT_EQUAL(TagLib::uint(8), tf->fieldList().size()); - CPPUNIT_ASSERT_EQUAL(String("Guitar"), tf->fieldList()[0]); - CPPUNIT_ASSERT_EQUAL(String("Artist 1"), tf->fieldList()[1]); - CPPUNIT_ASSERT_EQUAL(String("Drums"), tf->fieldList()[2]); - CPPUNIT_ASSERT_EQUAL(String("Artist 2"), tf->fieldList()[3]); - CPPUNIT_ASSERT_EQUAL(String("Producer"), tf->fieldList()[4]); - CPPUNIT_ASSERT_EQUAL(String("Artist 3"), tf->fieldList()[5]); - CPPUNIT_ASSERT_EQUAL(String("Mastering"), tf->fieldList()[6]); - CPPUNIT_ASSERT_EQUAL(String("Artist 4"), tf->fieldList()[7]); - CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TDRL")); - CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TDTG")); - CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TMOO")); - CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TPRO")); - CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TSOA")); - CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TSOT")); - CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TSST")); - CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TSOP")); + { + MPEG::File foo(newname.c_str()); + tf = new ID3v2::TextIdentificationFrame("TDOR", String::Latin1); + tf->setText("2011-03-16"); + foo.ID3v2Tag()->addFrame(tf); + tf = new ID3v2::TextIdentificationFrame("TDRC", String::Latin1); + tf->setText("2012-04-17T12:01"); + foo.ID3v2Tag()->addFrame(tf); + tf = new ID3v2::TextIdentificationFrame("TMCL", String::Latin1); + tf->setText(StringList().append("Guitar").append("Artist 1").append("Drums").append("Artist 2")); + foo.ID3v2Tag()->addFrame(tf); + tf = new ID3v2::TextIdentificationFrame("TIPL", String::Latin1); + tf->setText(StringList().append("Producer").append("Artist 3").append("Mastering").append("Artist 4")); + foo.ID3v2Tag()->addFrame(tf); + foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TDRL", String::Latin1)); + foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TDTG", String::Latin1)); + foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TMOO", String::Latin1)); + foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TPRO", String::Latin1)); + foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TSOA", String::Latin1)); + foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TSOT", String::Latin1)); + foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TSST", String::Latin1)); + foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TSOP", String::Latin1)); + foo.save(MPEG::File::AllTags, true, 3); + } + { + MPEG::File bar(newname.c_str()); + tf = static_cast(bar.ID3v2Tag()->frameList("TDOR").front()); + CPPUNIT_ASSERT(tf); + CPPUNIT_ASSERT_EQUAL(TagLib::uint(1), tf->fieldList().size()); + CPPUNIT_ASSERT_EQUAL(String("2011"), tf->fieldList().front()); + tf = static_cast(bar.ID3v2Tag()->frameList("TDRC").front()); + CPPUNIT_ASSERT(tf); + CPPUNIT_ASSERT_EQUAL(TagLib::uint(1), tf->fieldList().size()); + CPPUNIT_ASSERT_EQUAL(String("2012"), tf->fieldList().front()); + tf = dynamic_cast(bar.ID3v2Tag()->frameList("TIPL").front()); + CPPUNIT_ASSERT(tf); + CPPUNIT_ASSERT_EQUAL(TagLib::uint(8), tf->fieldList().size()); + CPPUNIT_ASSERT_EQUAL(String("Guitar"), tf->fieldList()[0]); + CPPUNIT_ASSERT_EQUAL(String("Artist 1"), tf->fieldList()[1]); + CPPUNIT_ASSERT_EQUAL(String("Drums"), tf->fieldList()[2]); + CPPUNIT_ASSERT_EQUAL(String("Artist 2"), tf->fieldList()[3]); + CPPUNIT_ASSERT_EQUAL(String("Producer"), tf->fieldList()[4]); + CPPUNIT_ASSERT_EQUAL(String("Artist 3"), tf->fieldList()[5]); + CPPUNIT_ASSERT_EQUAL(String("Mastering"), tf->fieldList()[6]); + CPPUNIT_ASSERT_EQUAL(String("Artist 4"), tf->fieldList()[7]); + CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TDRL")); + CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TDTG")); + CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TMOO")); + CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TPRO")); + CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TSOA")); + CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TSOT")); + CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TSST")); + CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TSOP")); + } } void testCompressedFrameWithBrokenLength() @@ -692,7 +709,7 @@ public: #ifdef HAVE_ZLIB - ID3v2::AttachedPictureFrame *frame + ID3v2::AttachedPictureFrame *frame = dynamic_cast(f.ID3v2Tag()->frameListMap()["APIC"].front()); CPPUNIT_ASSERT(frame); CPPUNIT_ASSERT_EQUAL(String("image/bmp"), frame->mimeType()); @@ -705,13 +722,13 @@ public: // Skip the test if ZLIB is not installed. // The message "Compressed frames are currently not supported." will be displayed. - ID3v2::UnknownFrame *frame + ID3v2::UnknownFrame *frame = dynamic_cast(f.ID3v2Tag()->frameListMap()["APIC"].front()); CPPUNIT_ASSERT(frame); #endif } - + void testW000() { MPEG::File f(TEST_FILE_PATH_C("w000.mp3"), false); @@ -823,36 +840,40 @@ public: { ScopedFileCopy copy("rare_frames", ".mp3"); string newname = copy.fileName(); - MPEG::File f(newname.c_str()); - ID3v2::Tag *t = f.ID3v2Tag(); - ID3v2::Frame *frame = t->frameList("TCON")[0]; - CPPUNIT_ASSERT_EQUAL(1u, t->frameList("TCON").size()); - t->removeFrame(frame, true); - f.save(MPEG::File::ID3v2); - - MPEG::File f2(newname.c_str()); - t = f2.ID3v2Tag(); - CPPUNIT_ASSERT(t->frameList("TCON").isEmpty()); + + { + MPEG::File f(newname.c_str()); + ID3v2::Tag *t = f.ID3v2Tag(); + ID3v2::Frame *frame = t->frameList("TCON")[0]; + CPPUNIT_ASSERT_EQUAL(1u, t->frameList("TCON").size()); + t->removeFrame(frame, true); + f.save(MPEG::File::ID3v2); + } + { + MPEG::File f2(newname.c_str()); + ID3v2::Tag *t = f2.ID3v2Tag(); + CPPUNIT_ASSERT(t->frameList("TCON").isEmpty()); + } } - + void testSaveAndStripID3v1ShouldNotAddFrameFromID3v1ToId3v2() { ScopedFileCopy copy("xing", ".mp3"); string newname = copy.fileName(); - + { MPEG::File foo(newname.c_str()); foo.tag()->setArtist("Artist"); foo.save(MPEG::File::ID3v1 | MPEG::File::ID3v2); } - + { MPEG::File bar(newname.c_str()); bar.ID3v2Tag()->removeFrames("TPE1"); // Should strip ID3v1 here and not add old values to ID3v2 again bar.save(MPEG::File::ID3v2, true); } - + MPEG::File f(newname.c_str()); CPPUNIT_ASSERT(!f.ID3v2Tag()->frameListMap().contains("TPE1")); } diff --git a/tests/test_mp4.cpp b/tests/test_mp4.cpp index 56b60525..9b90eed3 100644 --- a/tests/test_mp4.cpp +++ b/tests/test_mp4.cpp @@ -158,6 +158,7 @@ public: f->tag()->itemListMap()["pgap"] = true; f->save(); + delete f; f = new MP4::File(filename.c_str()); CPPUNIT_ASSERT_EQUAL(true, f->tag()->itemListMap()["cpil"].toBool()); @@ -167,6 +168,7 @@ public: moov = atoms->atoms[0]; // original size + 'pgap' size + padding CPPUNIT_ASSERT_EQUAL(long(77 + 25 + 974), moov->length); + delete f; } void testGnre() @@ -231,7 +233,7 @@ public: void testProperties() { MP4::File f(TEST_FILE_PATH_C("has-tags.m4a")); - + PropertyMap tags = f.properties(); CPPUNIT_ASSERT_EQUAL(StringList("Test Artist"), tags["ARTIST"]); diff --git a/tests/test_mpeg.cpp b/tests/test_mpeg.cpp index ad6acc49..44925cac 100644 --- a/tests/test_mpeg.cpp +++ b/tests/test_mpeg.cpp @@ -32,18 +32,21 @@ public: string newname = copy.fileName(); String xxx = ByteVector(254, 'X'); - MPEG::File f(newname.c_str()); - CPPUNIT_ASSERT_EQUAL(false, f.hasID3v2Tag()); + { + MPEG::File f(newname.c_str()); + CPPUNIT_ASSERT_EQUAL(false, f.hasID3v2Tag()); - f.tag()->setTitle(xxx); - f.tag()->setArtist("Artist A"); - f.save(MPEG::File::AllTags, true, 4); - CPPUNIT_ASSERT_EQUAL(true, f.hasID3v2Tag()); - - MPEG::File f2(newname.c_str()); - CPPUNIT_ASSERT_EQUAL(TagLib::uint(4), f2.ID3v2Tag()->header()->majorVersion()); - CPPUNIT_ASSERT_EQUAL(String("Artist A"), f2.tag()->artist()); - CPPUNIT_ASSERT_EQUAL(xxx, f2.tag()->title()); + f.tag()->setTitle(xxx); + f.tag()->setArtist("Artist A"); + f.save(MPEG::File::AllTags, true, 4); + CPPUNIT_ASSERT_EQUAL(true, f.hasID3v2Tag()); + } + { + MPEG::File f2(newname.c_str()); + CPPUNIT_ASSERT_EQUAL(TagLib::uint(4), f2.ID3v2Tag()->header()->majorVersion()); + CPPUNIT_ASSERT_EQUAL(String("Artist A"), f2.tag()->artist()); + CPPUNIT_ASSERT_EQUAL(xxx, f2.tag()->title()); + } } void testSaveID3v24WrongParam() @@ -52,15 +55,18 @@ public: string newname = copy.fileName(); String xxx = ByteVector(254, 'X'); - MPEG::File f(newname.c_str()); - f.tag()->setTitle(xxx); - f.tag()->setArtist("Artist A"); - f.save(MPEG::File::AllTags, true, 8); - - MPEG::File f2(newname.c_str()); - CPPUNIT_ASSERT_EQUAL(TagLib::uint(4), f2.ID3v2Tag()->header()->majorVersion()); - CPPUNIT_ASSERT_EQUAL(String("Artist A"), f2.tag()->artist()); - CPPUNIT_ASSERT_EQUAL(xxx, f2.tag()->title()); + { + MPEG::File f(newname.c_str()); + f.tag()->setTitle(xxx); + f.tag()->setArtist("Artist A"); + f.save(MPEG::File::AllTags, true, 8); + } + { + MPEG::File f2(newname.c_str()); + CPPUNIT_ASSERT_EQUAL(TagLib::uint(4), f2.ID3v2Tag()->header()->majorVersion()); + CPPUNIT_ASSERT_EQUAL(String("Artist A"), f2.tag()->artist()); + CPPUNIT_ASSERT_EQUAL(xxx, f2.tag()->title()); + } } void testSaveID3v23() @@ -69,18 +75,21 @@ public: string newname = copy.fileName(); String xxx = ByteVector(254, 'X'); - MPEG::File f(newname.c_str()); - CPPUNIT_ASSERT_EQUAL(false, f.hasID3v2Tag()); + { + MPEG::File f(newname.c_str()); + CPPUNIT_ASSERT_EQUAL(false, f.hasID3v2Tag()); - f.tag()->setTitle(xxx); - f.tag()->setArtist("Artist A"); - f.save(MPEG::File::AllTags, true, 3); - CPPUNIT_ASSERT_EQUAL(true, f.hasID3v2Tag()); - - MPEG::File f2(newname.c_str()); - CPPUNIT_ASSERT_EQUAL(TagLib::uint(3), f2.ID3v2Tag()->header()->majorVersion()); - CPPUNIT_ASSERT_EQUAL(String("Artist A"), f2.tag()->artist()); - CPPUNIT_ASSERT_EQUAL(xxx, f2.tag()->title()); + f.tag()->setTitle(xxx); + f.tag()->setArtist("Artist A"); + f.save(MPEG::File::AllTags, true, 3); + CPPUNIT_ASSERT_EQUAL(true, f.hasID3v2Tag()); + } + { + MPEG::File f2(newname.c_str()); + CPPUNIT_ASSERT_EQUAL(TagLib::uint(3), f2.ID3v2Tag()->header()->majorVersion()); + CPPUNIT_ASSERT_EQUAL(String("Artist A"), f2.tag()->artist()); + CPPUNIT_ASSERT_EQUAL(xxx, f2.tag()->title()); + } } }; diff --git a/tests/test_propertymap.cpp b/tests/test_propertymap.cpp index d0b2fbb7..0ca7b723 100644 --- a/tests/test_propertymap.cpp +++ b/tests/test_propertymap.cpp @@ -11,11 +11,11 @@ public: { TagLib::PropertyMap map1; CPPUNIT_ASSERT(map1.isEmpty()); - map1["ÄÖÜ"].append("test"); + map1[L"\x00c4\x00d6\x00dc"].append("test"); CPPUNIT_ASSERT_EQUAL(map1.size(), 1u); TagLib::PropertyMap map2; - map2["ÄÖÜ"].append("test"); + map2[L"\x00c4\x00d6\x00dc"].append("test"); CPPUNIT_ASSERT(map1 == map2); CPPUNIT_ASSERT(map1.contains(map2)); @@ -23,7 +23,7 @@ public: CPPUNIT_ASSERT(map1 != map2); CPPUNIT_ASSERT(map2.contains(map1)); - map2["ÄÖÜ"].append("test 2"); + map2[L"\x00c4\x00d6\x00dc"].append("test 2"); CPPUNIT_ASSERT(!map2.contains(map1)); } diff --git a/tests/test_string.cpp b/tests/test_string.cpp index 1a20ed6f..40a21576 100644 --- a/tests/test_string.cpp +++ b/tests/test_string.cpp @@ -82,7 +82,7 @@ public: String unicode5(L"\u65e5\u672c\u8a9e", String::UTF16LE); CPPUNIT_ASSERT(unicode5[1] == L'\u2c67'); - wstring stduni = L"\u65e5\u672c\u8a9e"; + std::wstring stduni = L"\u65e5\u672c\u8a9e"; String unicode6(stduni, String::UTF16BE); CPPUNIT_ASSERT(unicode6[1] == L'\u672c'); From 5feabe0988d342857acb3e481d5ae1268521bc6a Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Fri, 20 Jun 2014 10:36:09 +0900 Subject: [PATCH 27/35] Updated INSTALL. --- INSTALL | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/INSTALL b/INSTALL index 0f905006..205b4d31 100644 --- a/INSTALL +++ b/INSTALL @@ -72,7 +72,8 @@ Useful configuration options used with CMake (GUI and/or Command line): Assumes parent of: \include and \lib. ZLIB_INCLUDE_DIR= Where to find ZLib's Include directory. ZLIB_LIBRARY= Where to find ZLib's Library. - ZLIB_SOURCE= Where to find ZLib's source code. + ZLIB_SOURCE= Where to find ZLib's Source Code. + Alternative to ZLIB_INCLUDE_DIR and ZLIB_LIBRARY. CMAKE_INSTALL_PREFIX= Where to install Taglib. CMAKE_BUILD_TYPE= Release, Debug, etc ... (Not available in MSVC) From 3e60e339a4bc46e2a1a7aea782502480561a8acf Mon Sep 17 00:00:00 2001 From: Urs Fleisch Date: Sat, 21 Jun 2014 12:34:42 +0200 Subject: [PATCH 28/35] Add genres 148 to 191 to list of ID3v1 genres. This genres were added for Winamp version 5.6, see . --- taglib/mpeg/id3v1/id3v1genres.cpp | 48 +++++++++++++++++++++++++++++-- taglib/mpeg/id3v1/id3v1genres.h | 2 +- 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/taglib/mpeg/id3v1/id3v1genres.cpp b/taglib/mpeg/id3v1/id3v1genres.cpp index eba4c526..074c8bff 100644 --- a/taglib/mpeg/id3v1/id3v1genres.cpp +++ b/taglib/mpeg/id3v1/id3v1genres.cpp @@ -30,7 +30,7 @@ using namespace TagLib; namespace TagLib { namespace ID3v1 { - static const int genresSize = 148; + static const int genresSize = 192; static const String genres[] = { "Blues", "Classic Rock", @@ -179,7 +179,51 @@ namespace TagLib { "Thrash Metal", "Anime", "Jpop", - "Synthpop" + "Synthpop", + "Abstract", + "Art Rock", + "Baroque", + "Bhangra", + "Big Beat", + "Breakbeat", + "Chillout", + "Downtempo", + "Dub", + "EBM", + "Eclectic", + "Electro", + "Electroclash", + "Emo", + "Experimental", + "Garage", + "Global", + "IDM", + "Illbient", + "Industro-Goth", + "Jam Band", + "Krautrock", + "Leftfield", + "Lounge", + "Math Rock", + "New Romantic", + "Nu-Breakz", + "Post-Punk", + "Post-Rock", + "Psytrance", + "Shoegaze", + "Space Rock", + "Trop Rock", + "World Music", + "Neoclassical", + "Audiobook", + "Audio Theatre", + "Neue Deutsche Welle", + "Podcast", + "Indie Rock", + "G-Funk", + "Dubstep", + "Garage Rock", + "Psybient" }; } } diff --git a/taglib/mpeg/id3v1/id3v1genres.h b/taglib/mpeg/id3v1/id3v1genres.h index 271f7259..0a0dd970 100644 --- a/taglib/mpeg/id3v1/id3v1genres.h +++ b/taglib/mpeg/id3v1/id3v1genres.h @@ -49,7 +49,7 @@ namespace TagLib { /*! * Returns the name of the genre at \a index in the ID3v1 genre list. If - * \a index is out of range -- less than zero or greater than 146 -- a null + * \a index is out of range -- less than zero or greater than 191 -- a null * string will be returned. */ String TAGLIB_EXPORT genre(int index); From d7995b807fc11129cc0a971ef5ee7a9a09a52bf2 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Tue, 24 Jun 2014 01:29:15 +0900 Subject: [PATCH 29/35] Fixed the tests to work with MSVC when UNICODE is set. --- tests/utils.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/utils.h b/tests/utils.h index 00cef628..5f2f9814 100644 --- a/tests/utils.h +++ b/tests/utils.h @@ -29,8 +29,8 @@ inline string copyFile(const string &filename, const string &ext) string newname = string(tempnam(NULL, NULL)) + ext; string oldname = testFilePath(filename) + ext; #ifdef _WIN32 - CopyFile(oldname.c_str(), newname.c_str(), FALSE); - SetFileAttributes(newname.c_str(), GetFileAttributes(newname.c_str()) & ~FILE_ATTRIBUTE_READONLY); + CopyFileA(oldname.c_str(), newname.c_str(), FALSE); + SetFileAttributesA(newname.c_str(), GetFileAttributesA(newname.c_str()) & ~FILE_ATTRIBUTE_READONLY); #else char buffer[4096]; int bytes; From 5f738a9819357bc17867cfe053e205d24bebc85a Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Wed, 9 Jul 2014 09:52:08 +0900 Subject: [PATCH 30/35] Changed to skip the ZLib check when ZLIB_SOURCE is set. --- ConfigureChecks.cmake | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/ConfigureChecks.cmake b/ConfigureChecks.cmake index 1df69f95..f2d4cae0 100644 --- a/ConfigureChecks.cmake +++ b/ConfigureChecks.cmake @@ -209,13 +209,14 @@ check_cxx_source_compiles(" # Check for libz using the cmake supplied FindZLIB.cmake -find_package(ZLIB) -if(ZLIB_FOUND) - set(HAVE_ZLIB 1) -else() - set(HAVE_ZLIB 0) +if(NOT ZLIB_SOURCE) + find_package(ZLIB) + if(ZLIB_FOUND) + set(HAVE_ZLIB 1) + else() + set(HAVE_ZLIB 0) + endif() endif() - set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules) find_package(CppUnit) From 47cd6fad4f3898108a2c330bf9e292da3eb49fdb Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Thu, 17 Jul 2014 01:43:56 +0900 Subject: [PATCH 31/35] Fixed some compilation error on MSVC in CHAP and CTOC frames support. --- taglib/mpeg/id3v2/frames/chapterframe.cpp | 8 ++++---- taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/taglib/mpeg/id3v2/frames/chapterframe.cpp b/taglib/mpeg/id3v2/frames/chapterframe.cpp index 8c5514e2..b1741cd2 100644 --- a/taglib/mpeg/id3v2/frames/chapterframe.cpp +++ b/taglib/mpeg/id3v2/frames/chapterframe.cpp @@ -183,11 +183,11 @@ PropertyMap ChapterFrame::asProperties() const PropertyMap map; map.unsupportedData().append(frameID() + String("/") + d->elementID); - + return map; } -ChapterFrame *ChapterFrame::findByElementID(const Tag *tag, const ByteVector &eID) // static +ChapterFrame *ChapterFrame::findByElementID(const ID3v2::Tag *tag, const ByteVector &eID) // static { ID3v2::FrameList comments = tag->frameList("CHAP"); @@ -238,7 +238,7 @@ void ChapterFrame::parseFields(const ByteVector &data) embPos += frame->size() + Frame::headerSize(4); addEmbeddedFrame(frame); - } + } } ByteVector ChapterFrame::renderFields() const @@ -253,7 +253,7 @@ ByteVector ChapterFrame::renderFields() const FrameList l = d->embeddedFrameList; for(FrameList::Iterator it = l.begin(); it != l.end(); ++it) data.append((*it)->render()); - + return data; } diff --git a/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp b/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp index f986eb9a..26f5a541 100644 --- a/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp +++ b/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp @@ -184,7 +184,7 @@ PropertyMap TableOfContentsFrame::asProperties() const PropertyMap map; map.unsupportedData().append(frameID() + String("/") + d->elementID); - + return map; } @@ -204,7 +204,7 @@ TableOfContentsFrame *TableOfContentsFrame::findByElementID(const ID3v2::Tag *ta return 0; } -TableOfContentsFrame *TableOfContentsFrame::findTopLevel(const Tag *tag) // static +TableOfContentsFrame *TableOfContentsFrame::findTopLevel(const ID3v2::Tag *tag) // static { ID3v2::FrameList tablesOfContents = tag->frameList("CTOC"); @@ -240,7 +240,7 @@ void TableOfContentsFrame::parseFields(const ByteVector &data) childElementID.append(char(0)); d->childElements.append(childElementID); } - + size -= pos; while((uint)embPos < size - Frame::headerSize(4)) { @@ -257,7 +257,7 @@ void TableOfContentsFrame::parseFields(const ByteVector &data) embPos += frame->size() + Frame::headerSize(4); addEmbeddedFrame(frame); - } + } } ByteVector TableOfContentsFrame::renderFields() const @@ -280,7 +280,7 @@ ByteVector TableOfContentsFrame::renderFields() const FrameList l = d->embeddedFrameList; for(FrameList::Iterator it = l.begin(); it != l.end(); ++it) data.append((*it)->render()); - + return data; } From e29f1d39e7695ebff50e490f4f3155f229121a32 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Thu, 17 Jul 2014 01:28:02 +0900 Subject: [PATCH 32/35] Added float conversion functions to ByteVector. Added CMake checks about IEEE754 compliance. --- CMakeLists.txt | 2 + ConfigureChecks.cmake | 25 ++++- cmake/TestFloatFormat.c | 13 +++ cmake/modules/TestFloatFormat.cmake | 60 ++++++++++++ config.h.cmake | 6 +- taglib/riff/aiff/aiffproperties.cpp | 50 +--------- taglib/toolkit/tbytevector.cpp | 141 ++++++++++++++++++++++++++++ taglib/toolkit/tbytevector.h | 78 ++++++++++++++- taglib/toolkit/tutils.h | 40 +++++++- tests/test_bytevector.cpp | 42 ++++++++- 10 files changed, 397 insertions(+), 60 deletions(-) create mode 100644 cmake/TestFloatFormat.c create mode 100644 cmake/modules/TestFloatFormat.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 02eb9893..d4469b5d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,6 +6,8 @@ if(NOT ${CMAKE_VERSION} VERSION_LESS 2.8.12) cmake_policy(SET CMP0022 OLD) endif() +list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules) + option(ENABLE_STATIC "Make static version of libtag" OFF) if(ENABLE_STATIC) add_definitions(-DTAGLIB_STATIC) diff --git a/ConfigureChecks.cmake b/ConfigureChecks.cmake index f2d4cae0..23830f93 100644 --- a/ConfigureChecks.cmake +++ b/ConfigureChecks.cmake @@ -6,8 +6,9 @@ include(CheckLibraryExists) include(CheckTypeSize) include(CheckCXXSourceCompiles) include(TestBigEndian) +include(TestFloatFormat) -# Check if the size of integral types are suitable. +# Check if the size of numeric types are suitable. check_type_size("short" SIZEOF_SHORT) if(NOT ${SIZEOF_SHORT} EQUAL 2) @@ -29,6 +30,16 @@ if(${SIZEOF_WCHAR_T} LESS 2) MESSAGE(FATAL_ERROR "TagLib requires that wchar_t is sufficient to store a UTF-16 char.") endif() +check_type_size("float" SIZEOF_FLOAT) +if(NOT ${SIZEOF_FLOAT} EQUAL 4) + MESSAGE(FATAL_ERROR "TagLib requires that float is 32-bit wide.") +endif() + +check_type_size("double" SIZEOF_DOUBLE) +if(NOT ${SIZEOF_DOUBLE} EQUAL 8) + MESSAGE(FATAL_ERROR "TagLib requires that double is 64-bit wide.") +endif() + # Determine the CPU byte order. test_big_endian(IS_BIG_ENDIAN) @@ -39,6 +50,18 @@ else() set(SYSTEM_BYTEORDER 2) endif() +# Check if the format of floating point types are suitable. + +test_float_format(FP_IEEE754) +if(${FP_IEEE754} EQUAL 1) + set(FLOAT_BYTEORDER 1) +elseif(${FP_IEEE754} EQUAL 2) + set(FLOAT_BYTEORDER 2) +else() + MESSAGE(FATAL_ERROR "TagLib requires that floating point types are IEEE754 compliant.") +endif() + + # Determine which kind of atomic operations your compiler supports. check_cxx_source_compiles(" diff --git a/cmake/TestFloatFormat.c b/cmake/TestFloatFormat.c new file mode 100644 index 00000000..5f18aafb --- /dev/null +++ b/cmake/TestFloatFormat.c @@ -0,0 +1,13 @@ +int main() +{ + double bin1[] = { + // "*TAGLIB*" encoded as a little-endian floating-point number + (double)3.9865557444897601e-105, (double)0.0 + }; + float bin2[] = { + // "*TL*" encoded as a little-endian floating-point number + (float)1.81480400e-013, (float)0.0 + }; + + return 0; +} diff --git a/cmake/modules/TestFloatFormat.cmake b/cmake/modules/TestFloatFormat.cmake new file mode 100644 index 00000000..0e90cde6 --- /dev/null +++ b/cmake/modules/TestFloatFormat.cmake @@ -0,0 +1,60 @@ +# Returns 1 if IEEE754 little-endian, 2 if IEEE754 big-endian, otherwise 0. + +MACRO(TEST_FLOAT_FORMAT FP_IEEE754) + IF(NOT FP_IEEE754) + TRY_COMPILE(HAVE_${FP_IEEE754} "${CMAKE_BINARY_DIR}" "${CMAKE_SOURCE_DIR}/cmake/TestFloatFormat.c" + COPY_FILE "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/TestFloatFormat.bin") + + SET(FP_IEEE754 0) + + IF(HAVE_${FP_IEEE754}) + + # dont match first/last letter because of string rounding errors :-) + FILE(STRINGS "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/TestFloatFormat.bin" + DOUBLE_IEEE754_LE LIMIT_COUNT 1 REGEX "TAGLIB") + FILE(STRINGS "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/TestFloatFormat.bin" + DOUBLE_IEEE754_BE LIMIT_COUNT 1 REGEX "BILGAT") + FILE(STRINGS "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/TestFloatFormat.bin" + FLOAT_IEEE754_LE LIMIT_COUNT 1 REGEX "TL") + FILE(STRINGS "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/TestFloatFormat.bin" + FLOAT_IEEE754_BE LIMIT_COUNT 1 REGEX "LT") + + IF(DOUBLE_IEEE754_LE AND FLOAT_IEEE754_LE) + SET(FP_IEEE754_LE 1) + ENDIF() + + IF(DOUBLE_IEEE754_BE AND FLOAT_IEEE754_BE) + SET(FP_IEEE754_BE 1) + ENDIF() + + # OS X Universal binaries will contain both strings, set it to the host + IF(FP_IEEE754_LE AND FP_IEEE754_BE) + IF(CMAKE_SYSTEM_PROCESSOR MATCHES powerpc) + SET(FP_IEEE754_LE FALSE) + SET(FP_IEEE754_BE TRUE) + ELSE() + SET(FP_IEEE754_LE TRUE) + SET(FP_IEEE754_BE FALSE) + ENDIF() + ENDIF() + + IF(FP_IEEE754_LE) + SET(FP_IEEE754 1) + ELSEIF(FP_IEEE754_BE) + SET(FP_IEEE754 2) + ENDIF() + ENDIF() + + # just some informational output for the user + IF(FP_IEEE754_LE) + MESSAGE(STATUS "Checking the floating point format - IEEE754 (LittleEndian)") + ELSEIF(FP_IEEE754_BE) + MESSAGE(STATUS "Checking the floating point format - IEEE754 (BigEndian)") + ELSE() + MESSAGE(STATUS "Checking the floating point format - Not IEEE754 or failed to detect.") + ENDIF() + + SET(FP_IEEE754 "${${FP_IEEE754}}" CACHE INTERNAL "Result of TEST_FLOAT_FORMAT" FORCE) + ENDIF() +ENDMACRO(TEST_FLOAT_FORMAT FP_IEEE754) + diff --git a/config.h.cmake b/config.h.cmake index f8dcbbde..07abffa6 100644 --- a/config.h.cmake +++ b/config.h.cmake @@ -1,9 +1,13 @@ /* config.h. Generated by cmake from config.h.cmake */ -/* Indicates the byte order of your target system */ +/* Integer byte order of your target system */ /* 1 if little-endian, 2 if big-endian. */ #cmakedefine SYSTEM_BYTEORDER ${SYSTEM_BYTEORDER} +/* IEEE754 byte order of your target system. */ +/* 1 if little-endian, 2 if big-endian. */ +#cmakedefine FLOAT_BYTEORDER ${FLOAT_BYTEORDER} + /* Defined if your compiler supports some byte swap functions */ #cmakedefine HAVE_GCC_BYTESWAP_16 1 #cmakedefine HAVE_GCC_BYTESWAP_32 1 diff --git a/taglib/riff/aiff/aiffproperties.cpp b/taglib/riff/aiff/aiffproperties.cpp index 18b973eb..1afb4a99 100644 --- a/taglib/riff/aiff/aiffproperties.cpp +++ b/taglib/riff/aiff/aiffproperties.cpp @@ -25,56 +25,8 @@ #include #include -#include -// ldexp is a c99 function, which might not be defined in -// so we pull in math.h too and hope it does the right (wrong) thing -// wrt. c99 functions in C++ -#include - #include "aiffproperties.h" -//////////////////////////////////////////////////////////////////////////////// -// nasty 80-bit float helpers -//////////////////////////////////////////////////////////////////////////////// - -#define UnsignedToFloat(u) (((double)((long)(u - 2147483647L - 1))) + 2147483648.0) - -static double ConvertFromIeeeExtended(const TagLib::uchar *bytes) -{ - double f; - int expon; - unsigned long hiMant, loMant; - - expon = ((bytes[0] & 0x7F) << 8) | (bytes[1] & 0xFF); - - hiMant = ((unsigned long)(bytes[2] & 0xFF) << 24) | - ((unsigned long)(bytes[3] & 0xFF) << 16) | - ((unsigned long)(bytes[4] & 0xFF) << 8) | - ((unsigned long)(bytes[5] & 0xFF)); - - loMant = ((unsigned long)(bytes[6] & 0xFF) << 24) | - ((unsigned long)(bytes[7] & 0xFF) << 16) | - ((unsigned long)(bytes[8] & 0xFF) << 8) | - ((unsigned long)(bytes[9] & 0xFF)); - - if (expon == 0 && hiMant == 0 && loMant == 0) - f = 0; - else { - if(expon == 0x7FFF) /* Infinity or NaN */ - f = HUGE_VAL; - else { - expon -= 16383; - f = ldexp(UnsignedToFloat(hiMant), expon -= 31); - f += ldexp(UnsignedToFloat(loMant), expon -= 32); - } - } - - if(bytes[0] & 0x80) - return -f; - else - return f; -} - using namespace TagLib; class RIFF::AIFF::Properties::PropertiesPrivate @@ -153,7 +105,7 @@ void RIFF::AIFF::Properties::read(const ByteVector &data) d->channels = data.toShort(0U); d->sampleFrames = data.toUInt(2U); d->sampleWidth = data.toShort(6U); - double sampleRate = ConvertFromIeeeExtended(reinterpret_cast(data.data() + 8)); + const long double sampleRate = data.toFloat80BE(8); d->sampleRate = (int)sampleRate; d->bitrate = (int)((sampleRate * d->sampleWidth * d->channels) / 1000.0); d->length = d->sampleRate > 0 ? d->sampleFrames / d->sampleRate : 0; diff --git a/taglib/toolkit/tbytevector.cpp b/taglib/toolkit/tbytevector.cpp index 1ecd93d4..3404ab39 100644 --- a/taglib/toolkit/tbytevector.cpp +++ b/taglib/toolkit/tbytevector.cpp @@ -29,6 +29,8 @@ #include #include +#include +#include #include #include #include @@ -238,6 +240,95 @@ ByteVector fromNumber(T value, bool mostSignificantByteFirst) return ByteVector(reinterpret_cast(&value), sizeof(T)); } +template +TFloat toFloat(const ByteVector &v, size_t offset) +{ + if (offset > v.size() - sizeof(TInt)) { + debug("toFloat() - offset is out of range. Returning 0."); + return 0.0; + } + + union { + TInt i; + TFloat f; + } tmp; + ::memcpy(&tmp, v.data() + offset, sizeof(TInt)); + + if(ENDIAN != Utils::FloatByteOrder) + tmp.i = Utils::byteSwap(tmp.i); + + return tmp.f; +} + +template +ByteVector fromFloat(TFloat value) +{ + union { + TInt i; + TFloat f; + } tmp; + tmp.f = value; + + if(ENDIAN != Utils::FloatByteOrder) + tmp.i = Utils::byteSwap(tmp.i); + + return ByteVector(reinterpret_cast(&tmp), sizeof(TInt)); +} + +template +long double toFloat80(const ByteVector &v, size_t offset) +{ + if(offset > v.size() - 10) { + debug("toFloat80() - offset is out of range. Returning 0."); + return 0.0; + } + + uchar bytes[10]; + ::memcpy(bytes, v.data() + offset, 10); + + if(ENDIAN == Utils::LittleEndian) { + std::swap(bytes[0], bytes[9]); + std::swap(bytes[1], bytes[8]); + std::swap(bytes[2], bytes[7]); + std::swap(bytes[3], bytes[6]); + std::swap(bytes[4], bytes[5]); + } + + // 1-bit sign + const bool negative = ((bytes[0] & 0x80) != 0); + + // 15-bit exponent + const int exponent = ((bytes[0] & 0x7F) << 8) | bytes[1]; + + // 64-bit fraction. Leading 1 is explicit. + const ulonglong fraction + = (static_cast(bytes[2]) << 56) + | (static_cast(bytes[3]) << 48) + | (static_cast(bytes[4]) << 40) + | (static_cast(bytes[5]) << 32) + | (static_cast(bytes[6]) << 24) + | (static_cast(bytes[7]) << 16) + | (static_cast(bytes[8]) << 8) + | (static_cast(bytes[9])); + + long double val; + if(exponent == 0 && fraction == 0) + val = 0; + else { + if(exponent == 0x7FFF) { + debug("toFloat80() - can't handle the infinity or NaN. Returning 0."); + return 0.0; + } + else + val = ::ldexp(static_cast(fraction), exponent - 16383 - 63); + } + + if(negative) + return -val; + else + return val; +} + class DataPrivate : public RefCounter { public: @@ -371,6 +462,26 @@ ByteVector ByteVector::fromLongLong(long long value, bool mostSignificantByteFir return fromNumber(value, mostSignificantByteFirst); } +ByteVector ByteVector::fromFloat32LE(float value) +{ + return fromFloat(value); +} + +ByteVector ByteVector::fromFloat32BE(float value) +{ + return fromFloat(value); +} + +ByteVector ByteVector::fromFloat64LE(double value) +{ + return fromFloat(value); +} + +ByteVector ByteVector::fromFloat64BE(double value) +{ + return fromFloat(value); +} + //////////////////////////////////////////////////////////////////////////////// // public members //////////////////////////////////////////////////////////////////////////////// @@ -708,6 +819,36 @@ long long ByteVector::toLongLong(uint offset, bool mostSignificantByteFirst) con return toNumber(*this, offset, mostSignificantByteFirst); } +float ByteVector::toFloat32LE(size_t offset) const +{ + return toFloat(*this, offset); +} + +float ByteVector::toFloat32BE(size_t offset) const +{ + return toFloat(*this, offset); +} + +double ByteVector::toFloat64LE(size_t offset) const +{ + return toFloat(*this, offset); +} + +double ByteVector::toFloat64BE(size_t offset) const +{ + return toFloat(*this, offset); +} + +long double ByteVector::toFloat80LE(size_t offset) const +{ + return toFloat80(*this, offset); +} + +long double ByteVector::toFloat80BE(size_t offset) const +{ + return toFloat80(*this, offset); +} + const char &ByteVector::operator[](int index) const { return d->data->data[d->offset + index]; diff --git a/taglib/toolkit/tbytevector.h b/taglib/toolkit/tbytevector.h index 538565b0..cbdb1d2c 100644 --- a/taglib/toolkit/tbytevector.h +++ b/taglib/toolkit/tbytevector.h @@ -291,7 +291,7 @@ namespace TagLib { uint toUInt(bool mostSignificantByteFirst = true) const; /*! - * Converts the 4 bytes at \a offset of the vector to an unsigned integer. + * Converts the 4 bytes at \a offset of the vector to an unsigned integer. * * If \a mostSignificantByteFirst is true this will operate left to right * evaluating the integer. For example if \a mostSignificantByteFirst is @@ -303,8 +303,8 @@ namespace TagLib { uint toUInt(uint offset, bool mostSignificantByteFirst = true) const; /*! - * Converts the \a length bytes at \a offset of the vector to an unsigned - * integer. If \a length is larger than 4, the excess is ignored. + * Converts the \a length bytes at \a offset of the vector to an unsigned + * integer. If \a length is larger than 4, the excess is ignored. * * If \a mostSignificantByteFirst is true this will operate left to right * evaluating the integer. For example if \a mostSignificantByteFirst is @@ -383,6 +383,46 @@ namespace TagLib { */ long long toLongLong(uint offset, bool mostSignificantByteFirst = true) const; + /* + * Converts the 4 bytes at \a offset of the vector to a float as an IEEE754 + * 32-bit little-endian floating point number. + */ + float toFloat32LE(size_t offset) const; + + /* + * Converts the 4 bytes at \a offset of the vector to a float as an IEEE754 + * 32-bit big-endian floating point number. + */ + float toFloat32BE(size_t offset) const; + + /* + * Converts the 8 bytes at \a offset of the vector to a double as an IEEE754 + * 64-bit little-endian floating point number. + */ + double toFloat64LE(size_t offset) const; + + /* + * Converts the 8 bytes at \a offset of the vector to a double as an IEEE754 + * 64-bit big-endian floating point number. + */ + double toFloat64BE(size_t offset) const; + + /* + * Converts the 10 bytes at \a offset of the vector to a long double as an + * IEEE754 80-bit little-endian floating point number. + * + * \note This may compromise the precision depends on the size of long double. + */ + long double toFloat80LE(size_t offset) const; + + /* + * Converts the 10 bytes at \a offset of the vector to a long double as an + * IEEE754 80-bit big-endian floating point number. + * + * \note This may compromise the precision depends on the size of long double. + */ + long double toFloat80BE(size_t offset) const; + /*! * Creates a 4 byte ByteVector based on \a value. If * \a mostSignificantByteFirst is true, then this will operate left to right @@ -415,6 +455,38 @@ namespace TagLib { */ static ByteVector fromLongLong(long long value, bool mostSignificantByteFirst = true); + /*! + * Creates a 4 byte ByteVector based on \a value as an IEEE754 32-bit + * little-endian floating point number. + * + * \see fromFloat32BE() + */ + static ByteVector fromFloat32LE(float value); + + /*! + * Creates a 4 byte ByteVector based on \a value as an IEEE754 32-bit + * big-endian floating point number. + * + * \see fromFloat32LE() + */ + static ByteVector fromFloat32BE(float value); + + /*! + * Creates a 8 byte ByteVector based on \a value as an IEEE754 64-bit + * little-endian floating point number. + * + * \see fromFloat64BE() + */ + static ByteVector fromFloat64LE(double value); + + /*! + * Creates a 8 byte ByteVector based on \a value as an IEEE754 64-bit + * big-endian floating point number. + * + * \see fromFloat64LE() + */ + static ByteVector fromFloat64BE(double value); + /*! * Returns a ByteVector based on the CString \a s. */ diff --git a/taglib/toolkit/tutils.h b/taglib/toolkit/tutils.h index 4a398d26..2ade4d89 100644 --- a/taglib/toolkit/tutils.h +++ b/taglib/toolkit/tutils.h @@ -155,11 +155,11 @@ namespace TagLib # if SYSTEM_BYTEORDER == 1 - const ByteOrder SystemByteOrder = LittleEndian; + const ByteOrder SystemByteOrder = LittleEndian; # else - const ByteOrder SystemByteOrder = BigEndian; + const ByteOrder SystemByteOrder = BigEndian; # endif @@ -178,8 +178,40 @@ namespace TagLib else return BigEndian; } - - const ByteOrder SystemByteOrder = systemByteOrder(); + + const ByteOrder SystemByteOrder = systemByteOrder(); + +#endif + +#ifdef FLOAT_BYTEORDER + +# if FLOAT_BYTEORDER == 1 + + const ByteOrder FloatByteOrder = LittleEndian; + +# else + + const ByteOrder FloatByteOrder = BigEndian; + +# endif + +#else + + inline ByteOrder floatByteOrder() + { + double bin[] = { + // "*TAGLIB*" encoded as a little-endian floating-point number + (double) 3.9865557444897601e-105, (double) 0.0 + }; + + char *str = (char*)&bin[0]; + if(strncmp(&str[1], "TAGLIB", 6) == 0) + return LittleEndian; + else + return BigEndian; + } + + const ByteOrder FloatByteOrder = floatByteOrder(); #endif } diff --git a/tests/test_bytevector.cpp b/tests/test_bytevector.cpp index c6a96198..5614f695 100644 --- a/tests/test_bytevector.cpp +++ b/tests/test_bytevector.cpp @@ -22,6 +22,7 @@ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#include #include #include #include @@ -38,7 +39,7 @@ class TestByteVector : public CppUnit::TestFixture CPPUNIT_TEST(testRfind1); CPPUNIT_TEST(testRfind2); CPPUNIT_TEST(testToHex); - CPPUNIT_TEST(testToUShort); + CPPUNIT_TEST(testNumericCoversion); CPPUNIT_TEST(testReplace); CPPUNIT_TEST_SUITE_END(); @@ -194,13 +195,50 @@ public: CPPUNIT_ASSERT_EQUAL(ByteVector("f0e1d2c3b4a5968778695a4b3c2d1e0f"), v.toHex()); } - void testToUShort() + void testNumericCoversion() { CPPUNIT_ASSERT_EQUAL((unsigned short)0xFFFF, ByteVector("\xff\xff", 2).toUShort()); CPPUNIT_ASSERT_EQUAL((unsigned short)0x0001, ByteVector("\x00\x01", 2).toUShort()); CPPUNIT_ASSERT_EQUAL((unsigned short)0x0100, ByteVector("\x00\x01", 2).toUShort(false)); CPPUNIT_ASSERT_EQUAL((unsigned short)0xFF01, ByteVector("\xFF\x01", 2).toUShort()); CPPUNIT_ASSERT_EQUAL((unsigned short)0x01FF, ByteVector("\xFF\x01", 2).toUShort(false)); + + const uchar PI32LE[] = { 0x00, 0xdb, 0x0f, 0x49, 0x40 }; + const uchar PI32BE[] = { 0x00, 0x40, 0x49, 0x0f, 0xdb }; + const uchar PI64LE[] = { 0x00, 0x18, 0x2d, 0x44, 0x54, 0xfb, 0x21, 0x09, 0x40 }; + const uchar PI64BE[] = { 0x00, 0x40, 0x09, 0x21, 0xfb, 0x54, 0x44, 0x2d, 0x18 }; + const uchar PI80LE[] = { 0x00, 0x00, 0xc0, 0x68, 0x21, 0xa2, 0xda, 0x0f, 0xc9, 0x00, 0x40 }; + const uchar PI80BE[] = { 0x00, 0x40, 0x00, 0xc9, 0x0f, 0xda, 0xa2, 0x21, 0x68, 0xc0, 0x00 }; + + ByteVector pi32le(reinterpret_cast(PI32LE), 5); + CPPUNIT_ASSERT_EQUAL(31415, static_cast(pi32le.toFloat32LE(1) * 10000)); + + ByteVector pi32be(reinterpret_cast(PI32BE), 5); + CPPUNIT_ASSERT_EQUAL(31415, static_cast(pi32be.toFloat32BE(1) * 10000)); + + ByteVector pi64le(reinterpret_cast(PI64LE), 9); + CPPUNIT_ASSERT_EQUAL(31415, static_cast(pi64le.toFloat64LE(1) * 10000)); + + ByteVector pi64be(reinterpret_cast(PI64BE), 9); + CPPUNIT_ASSERT_EQUAL(31415, static_cast(pi64be.toFloat64BE(1) * 10000)); + + ByteVector pi80le(reinterpret_cast(PI80LE), 11); + CPPUNIT_ASSERT_EQUAL(31415, static_cast(pi80le.toFloat80LE(1) * 10000)); + + ByteVector pi80be(reinterpret_cast(PI80BE), 11); + CPPUNIT_ASSERT_EQUAL(31415, static_cast(pi80be.toFloat80BE(1) * 10000)); + + ByteVector pi32le2 = ByteVector::fromFloat32LE(pi32le.toFloat32LE(1)); + CPPUNIT_ASSERT(memcmp(pi32le.data() + 1, pi32le2.data(), 4) == 0); + + ByteVector pi32be2 = ByteVector::fromFloat32BE(pi32be.toFloat32BE(1)); + CPPUNIT_ASSERT(memcmp(pi32be.data() + 1, pi32be2.data(), 4) == 0); + + ByteVector pi64le2 = ByteVector::fromFloat64LE(pi64le.toFloat64LE(1)); + CPPUNIT_ASSERT(memcmp(pi64le.data() + 1, pi64le2.data(), 8) == 0); + + ByteVector pi64be2 = ByteVector::fromFloat64BE(pi64be.toFloat64BE(1)); + CPPUNIT_ASSERT(memcmp(pi64be.data() + 1, pi64be2.data(), 8) == 0); } void testReplace() From c03b91aed3c8306d6776046dbc052e87b32e4792 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Wed, 23 Jul 2014 14:56:18 +0900 Subject: [PATCH 33/35] Added a missing #include. --- taglib/toolkit/tutils.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/taglib/toolkit/tutils.h b/taglib/toolkit/tutils.h index 2ade4d89..0874684a 100644 --- a/taglib/toolkit/tutils.h +++ b/taglib/toolkit/tutils.h @@ -44,6 +44,8 @@ # include #endif +#include + namespace TagLib { namespace Utils From 8e6e580c608eea7ba1c629a4cb1b451a1b08f0b4 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Wed, 23 Jul 2014 15:35:49 +0900 Subject: [PATCH 34/35] Fixed handling UTF-16 byte order. --- taglib/toolkit/tstring.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/taglib/toolkit/tstring.cpp b/taglib/toolkit/tstring.cpp index 603455a1..c43d70b6 100644 --- a/taglib/toolkit/tstring.cpp +++ b/taglib/toolkit/tstring.cpp @@ -48,11 +48,6 @@ namespace { - inline unsigned short combine(unsigned char c1, unsigned char c2) - { - return (c1 << 8) | c2; - } - void UTF16toUTF8(const wchar_t *src, size_t srcLength, char *dst, size_t dstLength) { #ifdef HAVE_STD_CODECVT @@ -844,7 +839,12 @@ void String::copyFromUTF16(const char *s, size_t length, Type t) d->data.resize(length / 2); for(size_t i = 0; i < length / 2; ++i) { - d->data[i] = swap ? combine(*s, *(s + 1)) : combine(*(s + 1), *s); + ushort c; + ::memcpy(&c, s, 2); + if(swap) + c = Utils::byteSwap(c); + + d->data[i] = static_cast(c); s += 2; } } From 590405d87824391e666b04020cb233dacfa6c4f6 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Wed, 23 Jul 2014 15:35:49 +0900 Subject: [PATCH 35/35] Fixed handling UTF-16 byte order. --- taglib/toolkit/tstring.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/taglib/toolkit/tstring.cpp b/taglib/toolkit/tstring.cpp index 603455a1..c43d70b6 100644 --- a/taglib/toolkit/tstring.cpp +++ b/taglib/toolkit/tstring.cpp @@ -48,11 +48,6 @@ namespace { - inline unsigned short combine(unsigned char c1, unsigned char c2) - { - return (c1 << 8) | c2; - } - void UTF16toUTF8(const wchar_t *src, size_t srcLength, char *dst, size_t dstLength) { #ifdef HAVE_STD_CODECVT @@ -844,7 +839,12 @@ void String::copyFromUTF16(const char *s, size_t length, Type t) d->data.resize(length / 2); for(size_t i = 0; i < length / 2; ++i) { - d->data[i] = swap ? combine(*s, *(s + 1)) : combine(*(s + 1), *s); + ushort c; + ::memcpy(&c, s, 2); + if(swap) + c = Utils::byteSwap(c); + + d->data[i] = static_cast(c); s += 2; } }