From 73c2311010932208f635b0318668e80ef6cbb969 Mon Sep 17 00:00:00 2001 From: Allan Sandfeld Jensen Date: Mon, 26 Jul 2004 19:03:28 +0000 Subject: [PATCH] Refactoring. APE-tags are not native to musepack, so I am moving it out. Seperated footer-decoding the same way header-decoding is seperated in ID3v2. Fixed a bug with the version in the footer. Should be 2000 (read 2.000) not 2. git-svn-id: svn://anonsvn.kde.org/home/kde/trunk/kdesupport/taglib@333011 283d02a7-25f6-0310-bc7c-ecb5cbfe19da --- ape/Makefile.am | 13 ++ ape/apefooter.cpp | 223 +++++++++++++++++++++++++++++++ ape/apefooter.h | 168 ++++++++++++++++++++++++ ape/apetag.cpp | 328 ++++++++++++++++++++++++++++++++++++++++++++++ ape/apetag.h | 156 ++++++++++++++++++++++ 5 files changed, 888 insertions(+) create mode 100644 ape/Makefile.am create mode 100644 ape/apefooter.cpp create mode 100644 ape/apefooter.h create mode 100644 ape/apetag.cpp create mode 100644 ape/apetag.h diff --git a/ape/Makefile.am b/ape/Makefile.am new file mode 100644 index 00000000..25637340 --- /dev/null +++ b/ape/Makefile.am @@ -0,0 +1,13 @@ +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) diff --git a/ape/apefooter.cpp b/ape/apefooter.cpp new file mode 100644 index 00000000..7d709dec --- /dev/null +++ b/ape/apefooter.cpp @@ -0,0 +1,223 @@ +/*************************************************************************** + 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 +#include + +#include +#include + +#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; +} diff --git a/ape/apefooter.h b/ape/apefooter.h new file mode 100644 index 00000000..b3359d13 --- /dev/null +++ b/ape/apefooter.h @@ -0,0 +1,168 @@ +/*************************************************************************** + 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 + +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 diff --git a/ape/apetag.cpp b/ape/apetag.cpp new file mode 100644 index 00000000..d18c387a --- /dev/null +++ b/ape/apetag.cpp @@ -0,0 +1,328 @@ +/*************************************************************************** + 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 +#include +#include +#include + +#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 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::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::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::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::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--; + } +} diff --git a/ape/apetag.h b/ape/apetag.h new file mode 100644 index 00000000..7c161fd9 --- /dev/null +++ b/ape/apetag.h @@ -0,0 +1,156 @@ +/*************************************************************************** + 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 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