mirror of
https://github.com/taglib/taglib.git
synced 2025-06-04 01:28:21 -04:00
git-svn-id: svn://anonsvn.kde.org/home/kde/trunk/kdesupport/taglib@330300 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
366 lines
8.7 KiB
C++
366 lines
8.7 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;
|
|
|
|
Map<const String, String> items;
|
|
Map<const String, ByteVector> unknowns;
|
|
|
|
};
|
|
/*
|
|
struct APE::Tag::Item
|
|
{
|
|
Item(String key) : key(key), type(STRING), value.str(String::null), readOnly(false) {};
|
|
const String key;
|
|
enum Type{ STRING, BINARY, URL, RESERVED } type;
|
|
union value{
|
|
String str;
|
|
ByteVector bin;
|
|
}
|
|
bool readOnly;
|
|
}*/
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// 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 APEItem(String key, String value) {
|
|
ByteVector data;
|
|
uint flags = 0;
|
|
|
|
data.append(ByteVector::fromUInt(value.size(),false));
|
|
data.append(ByteVector::fromUInt(flags,false));
|
|
data.append(key.data(String::UTF8));
|
|
data.append(char(0));
|
|
data.append(value.data(String::UTF8));
|
|
|
|
return data;
|
|
}
|
|
|
|
static ByteVector APEFrame(bool isHeader, uint dataSize, uint itemCount) {
|
|
ByteVector header;
|
|
uint tagSize = 32 + dataSize;
|
|
// bit 31: Has a header
|
|
// bit 29: Is the header
|
|
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<String,String>::Iterator i = d->items.begin();
|
|
while (i != d->items.end()) {
|
|
if (!i->second.isEmpty()) {
|
|
data.append(APEItem(i->first, i->second));
|
|
itemCount++;
|
|
}
|
|
i++;
|
|
}
|
|
}
|
|
|
|
{ Map<String,ByteVector>::Iterator i = d->unknowns.begin();
|
|
while (i != d->unknowns.end()) {
|
|
if (!i->second.isEmpty()) {
|
|
data.append(i->second);
|
|
itemCount++;
|
|
}
|
|
i++;
|
|
}
|
|
}
|
|
|
|
ByteVector tag;
|
|
tag.append(APEFrame(true, data.size(), itemCount));
|
|
tag.append(data);
|
|
tag.append(APEFrame(false, data.size(), itemCount));
|
|
|
|
return tag;
|
|
}
|
|
|
|
ByteVector APE::Tag::fileIdentifier()
|
|
{
|
|
return ByteVector::fromCString("APETAGEX");
|
|
}
|
|
|
|
String APE::Tag::title() const
|
|
{
|
|
if (d->items.contains("Title"))
|
|
return d->items["Title"];
|
|
else
|
|
return String::null;
|
|
}
|
|
|
|
String APE::Tag::artist() const
|
|
{
|
|
if (d->items.contains("Artist"))
|
|
return d->items["Artist"];
|
|
else
|
|
return String::null;
|
|
}
|
|
|
|
String APE::Tag::album() const
|
|
{
|
|
if (d->items.contains("Album"))
|
|
return d->items["Album"];
|
|
else
|
|
return String::null;
|
|
}
|
|
|
|
String APE::Tag::comment() const
|
|
{
|
|
if (d->items.contains("Comment"))
|
|
return d->items["Comment"];
|
|
else
|
|
return String::null;
|
|
}
|
|
|
|
String APE::Tag::genre() const
|
|
{
|
|
if (d->items.contains("Genre"))
|
|
return d->items["Genre"];
|
|
else
|
|
return String::null;
|
|
}
|
|
|
|
TagLib::uint APE::Tag::year() const
|
|
{
|
|
if (d->items.contains("Year"))
|
|
return (d->items["Year"]).toInt();
|
|
return 0;
|
|
}
|
|
|
|
TagLib::uint APE::Tag::track() const
|
|
{
|
|
if (d->items.contains("Track"))
|
|
return (d->items["Track"]).toInt();
|
|
return 0;
|
|
}
|
|
|
|
void APE::Tag::setTitle(const String &s)
|
|
{
|
|
d->items["Title"] = s;
|
|
}
|
|
|
|
void APE::Tag::setArtist(const String &s)
|
|
{
|
|
d->items["Artist"] = s;
|
|
}
|
|
|
|
void APE::Tag::setAlbum(const String &s)
|
|
{
|
|
d->items["Album"] = s;
|
|
}
|
|
|
|
void APE::Tag::setComment(const String &s)
|
|
{
|
|
if(s.isEmpty() )
|
|
removeComment("Comment");
|
|
else
|
|
d->items["Comment"] = s;
|
|
}
|
|
|
|
void APE::Tag::setGenre(const String &s)
|
|
{
|
|
if(s.isEmpty())
|
|
removeComment("Genre");
|
|
else
|
|
d->items["Genre"] = s;
|
|
}
|
|
|
|
void APE::Tag::setYear(uint i)
|
|
{
|
|
if(i <=0 )
|
|
removeComment("Year");
|
|
else
|
|
d->items["Year"] = String::number(i);
|
|
}
|
|
|
|
void APE::Tag::setTrack(uint i)
|
|
{
|
|
if(i <=0 )
|
|
removeComment("Track");
|
|
else
|
|
d->items["Track"] = String::number(i);
|
|
}
|
|
|
|
void APE::Tag::removeComment(const String &key) {
|
|
Map<String,String>::Iterator it = d->items.find(key);
|
|
if (it != d->items.end())
|
|
d->items.erase(it);
|
|
}
|
|
|
|
void APE::Tag::addComment(const String &key, const String &value) {
|
|
if (value.isEmpty())
|
|
removeComment(key);
|
|
else
|
|
d->items[key] = value;
|
|
}
|
|
|
|
uint APE::Tag::tagSize(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::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.");
|
|
}
|
|
}
|
|
|
|
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);
|
|
|
|
if (flags == 0) {
|
|
value = String(data.mid(pos+8+key.size()+1, vallen), String::UTF8);
|
|
d->items.insert(key,value);
|
|
} else {
|
|
d->unknowns.insert(key,data.mid(pos, 8+key.size()+1+vallen));
|
|
}
|
|
|
|
pos += 8+key.size()+1+vallen;
|
|
count--;
|
|
}
|
|
}
|
|
/*
|
|
void APE::Tag::parse(const ByteVector &data, uint count)
|
|
{
|
|
uint pos = 0;
|
|
uint vallen, flags;
|
|
String key;
|
|
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);
|
|
Item item(key);
|
|
|
|
ByteVector value = data.mid(pos+8+key.size()+1, vallen);
|
|
|
|
switch ((flags >> 1) & 3) {
|
|
case 0:
|
|
item.value.str = String(value, String::UTF8);
|
|
item.type = Item::STRING;
|
|
break;
|
|
case 1:
|
|
item.value.bin = value;
|
|
item.type = Item::BINARY;
|
|
break;
|
|
case 2:
|
|
item.value.str = String(value, String::UTF8);
|
|
item.type = Item::URL;
|
|
break;
|
|
case 3:
|
|
item.value.bin = value;
|
|
item.type = Item::RESERVED;
|
|
break;
|
|
}
|
|
item.readOnly = (flags & 1);
|
|
|
|
d->items.insert(key,item);
|
|
|
|
pos += 8+key.size()+1+vallen;
|
|
count--;
|
|
}
|
|
}*/
|