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
This commit is contained in:
Allan Sandfeld Jensen 2004-07-26 19:03:28 +00:00
parent 1ad5582391
commit 73c2311010
5 changed files with 888 additions and 0 deletions

13
ape/Makefile.am Normal file
View File

@ -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)

223
ape/apefooter.cpp Normal file
View File

@ -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 <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;
}

168
ape/apefooter.h Normal file
View File

@ -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 <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

328
ape/apetag.cpp Normal file
View File

@ -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 <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--;
}
}

156
ape/apetag.h Normal file
View File

@ -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<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