taglib/mpc/apetag.cpp
Allan Sandfeld Jensen 2025c4e9e5 Large update:
* Give access to APEv2 features like lists and read-only fields (not enforced by TagLib).
* Support one common mistake: ID3v1 tags placed after an APE-tag. In this case both tags are now read and maintained. APE-tags after an ID3v1 tag is not supported.


git-svn-id: svn://anonsvn.kde.org/home/kde/trunk/kdesupport/taglib@332903 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
2004-07-26 12:58:59 +00:00

369 lines
9.2 KiB
C++

/***************************************************************************
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"
using namespace TagLib;
using namespace APE;
class APE::Tag::TagPrivate
{
public:
TagPrivate() : file(0), tagOffset(-1), tagLength(0) {}
File *file;
long tagOffset;
long tagLength;
FieldListMap fieldListMap;
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;
}
static ByteVector _render_APEFrame(bool isHeader, uint dataSize, uint itemCount) {
ByteVector header;
TagLib::uint tagSize = 32 + dataSize;
// bit 31: Has a header
// bit 29: Is the header
TagLib::uint flags = (1U << 31) | ((isHeader) ? (1U << 29) : 0);
header.append(APE::Tag::fileIdentifier());
header.append(ByteVector::fromUInt(2, false));
header.append(ByteVector::fromUInt(tagSize, false));
header.append(ByteVector::fromUInt(itemCount, false));
header.append(ByteVector::fromUInt(flags, false));
header.append(ByteVector::fromLongLong(0, false));
return header;
}
ByteVector APE::Tag::render() const
{
ByteVector data;
uint itemCount = 0;
{ Map<const String,Item>::Iterator i = d->fieldListMap.begin();
while (i != d->fieldListMap.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++;
}
}
ByteVector tag;
tag.append(_render_APEFrame(true, data.size(), itemCount));
tag.append(data);
tag.append(_render_APEFrame(false, data.size(), itemCount));
return tag;
}
ByteVector APE::Tag::fileIdentifier()
{
return ByteVector::fromCString("APETAGEX");
}
String APE::Tag::title() const
{
if(d->fieldListMap["TITLE"].isEmpty())
return String::null;
return d->fieldListMap["TITLE"].value.front();
}
String APE::Tag::artist() const
{
if(d->fieldListMap["ARTIST"].isEmpty())
return String::null;
return d->fieldListMap["ARTIST"].value.front();
}
String APE::Tag::album() const
{
if(d->fieldListMap["ALBUM"].isEmpty())
return String::null;
return d->fieldListMap["ALBUM"].value.front();
}
String APE::Tag::comment() const
{
if(d->fieldListMap["COMMENT"].isEmpty())
return String::null;
return d->fieldListMap["COMMENT"].value.front();
}
String APE::Tag::genre() const
{
if(d->fieldListMap["GENRE"].isEmpty())
return String::null;
return d->fieldListMap["GENRE"].value.front();
}
TagLib::uint APE::Tag::year() const
{
if(d->fieldListMap["YEAR"].isEmpty())
return 0;
return d->fieldListMap["YEAR"].value.front().toInt();
}
TagLib::uint APE::Tag::track() const
{
if(d->fieldListMap["TRACK"].isEmpty())
return 0;
return d->fieldListMap["TRACK"].value.front().toInt();
}
void APE::Tag::setTitle(const String &s)
{
addField("TITLE", s, true);
}
void APE::Tag::setArtist(const String &s)
{
addField("ARTIST", s, true);
}
void APE::Tag::setAlbum(const String &s)
{
addField("ALBUM", s, true);
}
void APE::Tag::setComment(const String &s)
{
addField("COMMENT", s, true);
}
void APE::Tag::setGenre(const String &s)
{
addField("GENRE", s, true);
}
void APE::Tag::setYear(uint i)
{
if(i <=0 )
removeField("YEAR");
else
addField("YEAR", String::number(i), true);
}
void APE::Tag::setTrack(uint i)
{
if(i <=0 )
removeField("TRACK");
else
addField("TRACK", String::number(i), true);
}
const APE::FieldListMap& APE::Tag::fieldListMap() const
{
return d->fieldListMap;
}
void APE::Tag::removeField(const String &key) {
Map<const String, Item>::Iterator it = d->fieldListMap.find(key.upper());
if(it != d->fieldListMap.end())
d->fieldListMap.erase(it);
}
void APE::Tag::addField(const String &key, const String &value, bool replace) {
if(replace)
removeField(key);
if(!value.isEmpty()) {
Map<const String, Item>::Iterator it = d->fieldListMap.find(key.upper());
if (it != d->fieldListMap.end())
d->fieldListMap[key].value.append(value);
else
addItem(key, Item(value));
}
}
void APE::Tag::addField(const String &key, const StringList &values) {
removeField(key);
if(values.isEmpty()) return;
else {
addItem(key, Item(values));
}
}
TagLib::uint APE::Tag::tagSize(const ByteVector &footer)
{
// The reported length (excl. header)
uint length = footer.mid(12, 4).toUInt(false);
// Flags (bit 31: tag contains a header)
uint flags = footer.mid(20, 4).toUInt(false);
return length + ((flags & (1U << 31)) ? 32 : 0);
}
////////////////////////////////////////////////////////////////////////////////
// protected methods
////////////////////////////////////////////////////////////////////////////////
void APE::Tag::addItem(const String &key, const Item &item) {
removeField(key);
Map<const String, Item>::Iterator it = d->fieldListMap.find(key.upper());
d->fieldListMap.insert(key, item);
}
void APE::Tag::read()
{
if(d->file && d->file->isValid()) {
d->file->seek(d->tagOffset);
// read the footer -- always 32 bytes
ByteVector footer = d->file->readBlock(32);
// parse footer and some initial sanity checking
if(footer.size() == 32 && footer.mid(0, 8) == "APETAGEX") {
uint length = footer.mid(12, 4).toUInt(false);
uint count = footer.mid(16, 4).toUInt(false);
d->tagLength = length;
d->file->seek(d->tagOffset + 32 - length);
ByteVector data = d->file->readBlock(length - 32);
parse(data, count);
}
else
debug("APE tag is not valid or could not be read at the specified offset.");
}
}
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->fieldListMap.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--;
}
}