--- /dev/null
+INCLUDES = \
+ -I$(top_srcdir)/taglib \
+ -I$(top_srcdir)/taglib/toolkit \
+ $(all_includes)
+
+noinst_LTLIBRARIES = libape.la
+
+libape_la_SOURCES = apetag.cpp apefooter.cpp
+
+taglib_include_HEADERS = apetag.h apefooter.h
+taglib_includedir = $(includedir)/taglib
+
+EXTRA_DIST = $(libape_la_SOURCES) $(taglib_include_HEADERS)
--- /dev/null
+/***************************************************************************
+ copyright : (C) 2004 by Allan Sandfeld Jensen
+ (C) 2002, 2003 by Scott Wheeler (id3v2header.cpp)
+ email : kde@carewolf.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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
+ * USA *
+ ***************************************************************************/
+
+#include <iostream>
+#include <bitset>
+
+#include <tstring.h>
+#include <tdebug.h>
+
+#include "apefooter.h"
+
+using namespace TagLib;
+using namespace APE;
+
+class Footer::FooterPrivate
+{
+public:
+ FooterPrivate() : version(0),
+ footerPresent(true),
+ headerPresent(false),
+ isHeader(false),
+ itemCount(0),
+ tagSize(0) {}
+
+ ~FooterPrivate() {}
+
+ uint version;
+
+ bool footerPresent;
+ bool headerPresent;
+
+ bool isHeader;
+
+ uint itemCount;
+ uint tagSize;
+
+ static const uint size = 32;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+// static members
+////////////////////////////////////////////////////////////////////////////////
+
+TagLib::uint Footer::size()
+{
+ return FooterPrivate::size;
+}
+
+ByteVector Footer::fileIdentifier()
+{
+ return ByteVector::fromCString("APETAGEX");
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// public members
+////////////////////////////////////////////////////////////////////////////////
+
+Footer::Footer()
+{
+ d = new FooterPrivate;
+}
+
+Footer::Footer(const ByteVector &data)
+{
+ d = new FooterPrivate;
+ parse(data);
+}
+
+Footer::~Footer()
+{
+ delete d;
+}
+
+TagLib::uint Footer::version() const
+{
+ return d->version;
+}
+
+bool Footer::headerPresent() const
+{
+ return d->headerPresent;
+}
+
+bool Footer::footerPresent() const
+{
+ return d->footerPresent;
+}
+
+bool Footer::isHeader() const
+{
+ return d->isHeader;
+}
+
+void Footer::setHeaderPresent(bool b) const
+{
+ d->headerPresent = b;
+}
+
+TagLib::uint Footer::itemCount() const
+{
+ return d->itemCount;
+}
+
+void Footer::setItemCount(uint s)
+{
+ d->itemCount = s;
+}
+
+TagLib::uint Footer::tagSize() const
+{
+ return d->tagSize;
+}
+
+TagLib::uint Footer::completeTagSize() const
+{
+ if(d->headerPresent)
+ return d->tagSize + d->size;
+ else
+ return d->tagSize;
+}
+
+void Footer::setTagSize(uint s)
+{
+ d->tagSize = s;
+}
+
+void Footer::setData(const ByteVector &data)
+{
+ parse(data);
+}
+
+ByteVector Footer::renderFooter() const
+{
+ return render(false);
+}
+
+ByteVector Footer::renderHeader() const
+{
+ if (!d->headerPresent) return ByteVector();
+
+ return render(true);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// protected members
+////////////////////////////////////////////////////////////////////////////////
+
+void Footer::parse(const ByteVector &data)
+{
+ if(data.size() < size())
+ return;
+
+ // The first eight bytes, data[0..7], are the File Identifier, "APETAGEX".
+
+ // Read the version number
+ d->version = data.mid(8, 4).toUInt(false);
+
+ // Read the tag size
+ d->tagSize = data.mid(12, 4).toUInt(false);
+
+ // Read the item count
+ d->itemCount = data.mid(16, 4).toUInt(false);
+
+ // Read the flags
+ std::bitset<32> flags(data.mid(8, 4).toUInt(false));
+
+ d->headerPresent = flags[31];
+ d->footerPresent = !flags[30];
+ d->isHeader = flags[29];
+
+}
+
+ByteVector Footer::render(bool isHeader) const
+{
+ ByteVector v;
+
+ // add the file identifier -- "APETAGEX"
+ v.append(fileIdentifier());
+
+ // add the version number -- we always render a 2.000 tag regardless of what
+ // the tag originally was.
+
+ v.append(ByteVector::fromUInt(2000, false));
+
+ // add the tag size
+ v.append(ByteVector::fromUInt(d->tagSize, false));
+
+ // add the item count
+ v.append(ByteVector::fromUInt(d->itemCount, false));
+
+ // render and add the flags
+ std::bitset<32> flags;
+
+ flags[31] = d->headerPresent;
+ flags[30] = false; // footer is always present
+ flags[29] = isHeader;
+
+ v.append(ByteVector::fromUInt(flags.to_ulong(), false));
+
+ // add the reserved 64bit
+ v.append(ByteVector::fromLongLong(0));
+
+ return v;
+}
--- /dev/null
+/***************************************************************************
+ copyright : (C) 2004 by Allan Sandfeld Jensen
+ email : kde@carewolf.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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
+ * USA *
+ ***************************************************************************/
+
+#ifndef TAGLIB_APEFOOTER_H
+#define TAGLIB_APEFOOTER_H
+
+#include <tbytevector.h>
+
+namespace TagLib {
+
+ namespace APE {
+
+ //! An implementation of APE footers
+
+ /*!
+ * This class implements APE footers (and headers). It attempts to follow, both
+ * semantically and programatically, the structure specified in
+ * the APE v2.0 standard. The API is based on the properties of APE footer and
+ * headers specified there.
+ */
+
+ class Footer
+ {
+ public:
+ /*!
+ * Constructs an empty APE footer.
+ */
+ Footer();
+
+ /*!
+ * Constructs an APE footer based on \a data. parse() is called
+ * immediately.
+ */
+ Footer(const ByteVector &data);
+
+ /*!
+ * Destroys the footer.
+ */
+ virtual ~Footer();
+
+ /*!
+ * Returns the version number. (Note: This is the 1000 or 2000.)
+ */
+ uint version() const;
+
+ /*!
+ * Returns true if a header is present in the tag.
+ */
+ bool headerPresent() const;
+
+ /*!
+ * Returns true if a footer is present in the tag.
+ */
+ bool footerPresent() const;
+
+ /*!
+ * Returns true this is actually the header.
+ */
+ bool isHeader() const;
+
+ /*!
+ * Sets whether the header should be rendered or not
+ */
+ void setHeaderPresent(bool b) const;
+
+ /*!
+ * Returns the number of items in the tag.
+ */
+ uint itemCount() const;
+
+ /*!
+ * Set the item count to \a s.
+ * \see itemCount()
+ */
+ void setItemCount(uint s);
+
+ /*!
+ * Returns the tag size in bytes. This is the size of the frame content and footer.
+ * The size of the \e entire tag will be this plus the header size, if present.
+ *
+ * \see completeTagSize()
+ */
+ uint tagSize() const;
+
+ /*!
+ * Returns the tag size, including if present, the header
+ * size.
+ *
+ * \see tagSize()
+ */
+ uint completeTagSize() const;
+
+ /*!
+ * Set the tag size to \a s.
+ * \see tagSize()
+ */
+ void setTagSize(uint s);
+
+ /*!
+ * Returns the size of the footer. Presently this is always 32 bytes.
+ */
+ static uint size();
+
+ /*!
+ * Returns the string used to identify an APE tag inside of a file.
+ * Presently this is always "APETAGEX".
+ */
+ static ByteVector fileIdentifier();
+
+ /*!
+ * Sets the data that will be used as the footer. 32 bytes,
+ * starting from \a data will be used.
+ */
+ void setData(const ByteVector &data);
+
+ /*!
+ * Renders the footer back to binary format.
+ */
+ ByteVector renderFooter() const;
+
+ /*!
+ * Renders the header corresponding to the footer. If headerPresent is
+ * set to false, it returns an empty ByteVector.
+ */
+ ByteVector renderHeader() const;
+
+ protected:
+ /*!
+ * Called by setData() to parse the footer data. It makes this information
+ * available through the public API.
+ */
+ void parse(const ByteVector &data);
+
+ /*!
+ * Called by renderFooter and renderHeader
+ */
+ ByteVector render(bool isHeader) const;
+
+ private:
+ Footer(const Footer &);
+ Footer &operator=(const Footer &);
+
+ class FooterPrivate;
+ FooterPrivate *d;
+ };
+
+ }
+}
+
+#endif
--- /dev/null
+/***************************************************************************
+ copyright : (C) 2004 by Allan Sandfeld Jensen
+ email : kde@carewolf.com
+ ***************************************************************************/
+
+/***************************************************************************
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
+ * USA *
+ ***************************************************************************/
+
+#include <tdebug.h>
+#include <tfile.h>
+#include <tstring.h>
+#include <tmap.h>
+
+#include "apetag.h"
+#include "apefooter.h"
+
+using namespace TagLib;
+using namespace APE;
+
+class APE::Tag::TagPrivate
+{
+public:
+ TagPrivate() : file(0), tagOffset(-1), tagLength(0) {}
+
+ File *file;
+ long tagOffset;
+ long tagLength;
+
+ Footer footer;
+
+ ItemListMap itemListMap;
+ Map<const String, ByteVector> binaries;
+};
+
+APE::Item::Item(const String& str) : readOnly(false), locator(false) {
+ value.append(str);
+}
+
+APE::Item::Item(const StringList& values) : readOnly(false), locator(false) {
+ value.append(values);
+}
+
+bool APE::Item::isEmpty() const
+{
+ return value.isEmpty();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// public methods
+////////////////////////////////////////////////////////////////////////////////
+
+APE::Tag::Tag() : TagLib::Tag()
+{
+ d = new TagPrivate;
+}
+
+APE::Tag::Tag(File *file, long tagOffset) : TagLib::Tag()
+{
+ d = new TagPrivate;
+ d->file = file;
+ d->tagOffset = tagOffset;
+
+ read();
+}
+
+APE::Tag::~Tag()
+{
+ delete d;
+}
+
+using TagLib::uint;
+
+static ByteVector _render_APEItem(String key, Item item)
+{
+ ByteVector data;
+ uint flags = ((item.readOnly) ? 1 : 0) | ((item.locator) ? 2 : 0);
+ ByteVector value;
+
+ if (item.value.isEmpty()) return data;
+
+ StringList::Iterator it = item.value.begin();
+ value.append(it->data(String::UTF8));
+ it++;
+ while (it != item.value.end()) {
+ value.append('\0');
+ value.append(it->data(String::UTF8));
+ it++;
+ }
+
+ data.append(ByteVector::fromUInt(value.size(), false));
+ data.append(ByteVector::fromUInt(flags, false));
+ data.append(key.data(String::UTF8));
+ data.append(ByteVector('\0'));
+ data.append(value);
+
+ return data;
+}
+
+ByteVector APE::Tag::render() const
+{
+ ByteVector data;
+ uint itemCount = 0;
+
+ { Map<const String,Item>::Iterator i = d->itemListMap.begin();
+ while (i != d->itemListMap.end()) {
+ if (!i->second.value.isEmpty()) {
+ data.append(_render_APEItem(i->first, i->second));
+ itemCount++;
+ }
+ i++;
+ }
+ }
+
+ { Map<String,ByteVector>::Iterator i = d->binaries.begin();
+ while (i != d->binaries.end()) {
+ if (!i->second.isEmpty()) {
+ data.append(i->second);
+ itemCount++;
+ }
+ i++;
+ }
+ }
+
+ d->footer.setItemCount(itemCount);
+ d->footer.setTagSize(data.size()+Footer::size());
+
+ return d->footer.renderHeader() + data + d->footer.renderFooter();
+}
+
+ByteVector APE::Tag::fileIdentifier()
+{
+ return ByteVector::fromCString("APETAGEX");
+}
+
+String APE::Tag::title() const
+{
+ if(d->itemListMap["TITLE"].isEmpty())
+ return String::null;
+ return d->itemListMap["TITLE"].value.front();
+}
+
+String APE::Tag::artist() const
+{
+ if(d->itemListMap["ARTIST"].isEmpty())
+ return String::null;
+ return d->itemListMap["ARTIST"].value.front();
+}
+
+String APE::Tag::album() const
+{
+ if(d->itemListMap["ALBUM"].isEmpty())
+ return String::null;
+ return d->itemListMap["ALBUM"].value.front();
+}
+
+String APE::Tag::comment() const
+{
+ if(d->itemListMap["COMMENT"].isEmpty())
+ return String::null;
+ return d->itemListMap["COMMENT"].value.front();
+}
+
+String APE::Tag::genre() const
+{
+ if(d->itemListMap["GENRE"].isEmpty())
+ return String::null;
+ return d->itemListMap["GENRE"].value.front();
+}
+
+TagLib::uint APE::Tag::year() const
+{
+ if(d->itemListMap["YEAR"].isEmpty())
+ return 0;
+ return d->itemListMap["YEAR"].value.front().toInt();
+}
+
+TagLib::uint APE::Tag::track() const
+{
+ if(d->itemListMap["TRACK"].isEmpty())
+ return 0;
+ return d->itemListMap["TRACK"].value.front().toInt();
+}
+
+void APE::Tag::setTitle(const String &s)
+{
+ addValue("TITLE", s, true);
+}
+
+void APE::Tag::setArtist(const String &s)
+{
+ addValue("ARTIST", s, true);
+}
+
+void APE::Tag::setAlbum(const String &s)
+{
+ addValue("ALBUM", s, true);
+}
+
+void APE::Tag::setComment(const String &s)
+{
+ addValue("COMMENT", s, true);
+}
+
+void APE::Tag::setGenre(const String &s)
+{
+ addValue("GENRE", s, true);
+}
+
+void APE::Tag::setYear(uint i)
+{
+ if(i <=0 )
+ removeItem("YEAR");
+ else
+ addValue("YEAR", String::number(i), true);
+}
+
+void APE::Tag::setTrack(uint i)
+{
+ if(i <=0 )
+ removeItem("TRACK");
+ else
+ addValue("TRACK", String::number(i), true);
+}
+
+APE::Footer* APE::Tag::footer() const
+{
+ return &d->footer;
+}
+
+const APE::ItemListMap& APE::Tag::itemListMap() const
+{
+ return d->itemListMap;
+}
+
+void APE::Tag::removeItem(const String &key) {
+ Map<const String, Item>::Iterator it = d->itemListMap.find(key.upper());
+ if(it != d->itemListMap.end())
+ d->itemListMap.erase(it);
+}
+
+void APE::Tag::addValue(const String &key, const String &value, bool replace)
+{
+ if(replace)
+ removeItem(key);
+ if(!value.isEmpty()) {
+ Map<const String, Item>::Iterator it = d->itemListMap.find(key.upper());
+ if (it != d->itemListMap.end())
+ d->itemListMap[key].value.append(value);
+ else
+ setItem(key, Item(value));
+ }
+}
+
+void APE::Tag::setItem(const String &key, const Item &item)
+{
+ d->itemListMap.insert(key, item);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// protected methods
+////////////////////////////////////////////////////////////////////////////////
+
+void APE::Tag::read()
+{
+ if(d->file && d->file->isValid()) {
+
+ d->file->seek(d->tagOffset);
+ d->footer.setData(d->file->readBlock(Footer::size()));
+
+ if(d->footer.tagSize() == 0 || d->footer.tagSize() > d->file->length())
+ return;
+
+ d->file->seek(d->tagOffset + Footer::size() - d->footer.tagSize());
+ parse(d->file->readBlock(d->footer.tagSize() - Footer::size()), d->footer.itemCount());
+ }
+}
+
+static StringList _parse_APEString(ByteVector val)
+{
+ StringList value;
+ int pold = 0;
+ int p = val.find('\0');
+ while (p != -1) {
+ value.append(String(val.mid(pold, p), String::UTF8));
+ pold = p+1;
+ p = val.find('\0', pold);
+ };
+ value.append(String(val.mid(pold), String::UTF8));
+
+ return value;
+}
+
+void APE::Tag::parse(const ByteVector &data, uint count)
+{
+ uint pos = 0;
+ uint vallen, flags;
+ String key, value;
+ while(count > 0) {
+ vallen = data.mid(pos+0,4).toUInt(false);
+ flags = data.mid(pos+4,4).toUInt(false);
+ key = String(data.mid(pos+8), String::UTF8);
+ key = key.upper();
+ APE::Item item;
+
+ if (flags < 4 ) {
+ ByteVector val = data.mid(pos+8+key.size()+1, vallen);
+ d->itemListMap.insert(key, Item(_parse_APEString(val)));
+ } else {
+ d->binaries.insert(key,data.mid(pos, 8+key.size()+1+vallen));
+ }
+
+ pos += 8 + key.size() + 1 + vallen;
+ count--;
+ }
+}
--- /dev/null
+/***************************************************************************
+ copyright : (C) 2004 by Allan Sandfeld Jensen
+ email : kde@carewolf.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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
+ * USA *
+ ***************************************************************************/
+
+#ifndef TAGLIB_APETAG_H
+#define TAGLIB_APETAG_H
+
+#include "tag.h"
+#include "tbytevector.h"
+#include "tmap.h"
+#include "tstring.h"
+#include "tstringlist.h"
+
+namespace TagLib {
+
+ class File;
+
+ namespace APE {
+
+ class Footer;
+
+ /*!
+ * A non-binary APE-item.
+ */
+ struct Item {
+ Item() {};
+ explicit Item(const String&);
+ explicit Item(const StringList&);
+ bool readOnly;
+ bool locator; // URL to external data
+ StringList value;
+ bool isEmpty() const;
+ };
+
+ /*!
+ * A mapping between a list of item names, or keys, and the associated item.
+ *
+ * \see APE::Tag::itemListMap()
+ */
+ typedef Map<const String, Item> ItemListMap;
+
+
+ //! An APE tag implementation
+
+ class Tag : public TagLib::Tag
+ {
+ public:
+ /*!
+ * Create an APE tag with default values.
+ */
+ Tag();
+
+ /*!
+ * Create an APE tag and parse the data in \a file with APE footer at
+ * \a tagOffset.
+ */
+ Tag(File *file, long tagOffset);
+
+ /*!
+ * Destroys this Tag instance.
+ */
+ virtual ~Tag();
+
+ /*!
+ * Renders the in memory values to a ByteVector suitable for writing to
+ * the file.
+ */
+ ByteVector render() const;
+
+ /*!
+ * Returns the string "APETAGEX" suitable for usage in locating the tag in a
+ * file.
+ */
+ static ByteVector fileIdentifier();
+
+ // Reimplementations.
+
+ virtual String title() const;
+ virtual String artist() const;
+ virtual String album() const;
+ virtual String comment() const;
+ virtual String genre() const;
+ virtual uint year() const;
+ virtual uint track() const;
+
+ virtual void setTitle(const String &s);
+ virtual void setArtist(const String &s);
+ virtual void setAlbum(const String &s);
+ virtual void setComment(const String &s);
+ virtual void setGenre(const String &s);
+ virtual void setYear(uint i);
+ virtual void setTrack(uint i);
+
+ /*!
+ * Returns a pointer to the tag's footer.
+ */
+ Footer *footer() const;
+
+ const ItemListMap &itemListMap() const;
+
+ /*!
+ * Removes the \a key comment from the tag
+ */
+ void removeItem(const String &key);
+
+ /*!
+ * Adds to the item specified by \a key the data \a value. If \a replace
+ * is true, then all of the other values on the same key will be removed
+ * first.
+ */
+ void addValue(const String &key, const String &value, bool replace = true);
+
+ /*!
+ * Sets the \a key comment to \a item.
+ */
+ void setItem(const String &key, const Item &item);
+
+ protected:
+
+ /*!
+ * Reads from the file specified in the constructor.
+ */
+ void read();
+ /*!
+ * Parses the body of the tag in \a data with \a count items.
+ */
+ void parse(const ByteVector &data, uint count);
+
+ private:
+ Tag(const Tag &);
+ Tag &operator=(const Tag &);
+
+ class TagPrivate;
+ TagPrivate *d;
+ };
+ }
+}
+
+#endif