mirror of
https://github.com/taglib/taglib.git
synced 2025-06-04 01:28:21 -04:00
Make mpc use the new APETag-structure
git-svn-id: svn://anonsvn.kde.org/home/kde/trunk/kdesupport/taglib@333012 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
This commit is contained in:
parent
73c2311010
commit
dbc0fe07c9
@ -1,4 +1,4 @@
|
||||
SUBDIRS = toolkit mpeg ogg flac mpc
|
||||
SUBDIRS = toolkit mpeg ogg flac ape mpc
|
||||
|
||||
INCLUDES = \
|
||||
-I$(top_srcdir)/taglib/toolkit \
|
||||
@ -16,7 +16,7 @@ taglib_include_HEADERS = tag.h fileref.h audioproperties.h
|
||||
taglib_includedir = $(includedir)/taglib
|
||||
|
||||
libtag_la_LDFLAGS = $(all_libraries) -no-undefined -version-info 2:1:0
|
||||
libtag_la_LIBADD = ./mpeg/libmpeg.la ./ogg/libogg.la ./flac/libflac.la ./mpc/libmpc.la ./toolkit/libtoolkit.la
|
||||
libtag_la_LIBADD = ./mpeg/libmpeg.la ./ogg/libogg.la ./flac/libflac.la ./mpc/libmpc.la ./ape/libape.la ./toolkit/libtoolkit.la
|
||||
|
||||
bin_SCRIPTS = taglib-config
|
||||
|
||||
|
@ -1,13 +1,14 @@
|
||||
INCLUDES = \
|
||||
-I$(top_srcdir)/taglib \
|
||||
-I$(top_srcdir)/taglib/toolkit \
|
||||
-I$(top_srcdir)/taglib/ape \
|
||||
-I$(top_srcdir)/taglib/mpeg/id3v1 \
|
||||
-I$(top_srcdir)/taglib/mpeg/id3v2 \
|
||||
$(all_includes)
|
||||
|
||||
noinst_LTLIBRARIES = libmpc.la
|
||||
|
||||
libmpc_la_SOURCES = mpcfile.cpp mpcproperties.cpp apetag.cpp
|
||||
libmpc_la_SOURCES = mpcfile.cpp mpcproperties.cpp
|
||||
|
||||
taglib_include_HEADERS = mpcfile.h mpcproperties.h
|
||||
taglib_includedir = $(includedir)/taglib
|
||||
|
368
mpc/apetag.cpp
368
mpc/apetag.cpp
@ -1,368 +0,0 @@
|
||||
/***************************************************************************
|
||||
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--;
|
||||
}
|
||||
}
|
156
mpc/apetag.h
156
mpc/apetag.h
@ -1,156 +0,0 @@
|
||||
/***************************************************************************
|
||||
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 {
|
||||
|
||||
/*!
|
||||
* 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> FieldListMap;
|
||||
|
||||
|
||||
//! 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();
|
||||
|
||||
/*!
|
||||
* Returns the size of the tag calculated based on the footer.
|
||||
*/
|
||||
static uint tagSize(const ByteVector &footer);
|
||||
|
||||
// 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);
|
||||
|
||||
const FieldListMap &fieldListMap() const;
|
||||
|
||||
/*!
|
||||
* Removes the \a key comment from the tag
|
||||
*/
|
||||
void removeField(const String &key);
|
||||
|
||||
/*!
|
||||
* Adds the \a key comment with \a value
|
||||
*/
|
||||
void addField(const String &key, const String &value, bool replace = true);
|
||||
|
||||
/*!
|
||||
* Adds the \a key comment with \a values
|
||||
*/
|
||||
void addField(const String &key, const StringList &values);
|
||||
|
||||
protected:
|
||||
/*!
|
||||
* Adds the \a key comment with \a item
|
||||
*/
|
||||
void addItem(const String &key, const Item &item);
|
||||
|
||||
/*!
|
||||
* 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
|
@ -27,6 +27,7 @@
|
||||
#include "id3v1tag.h"
|
||||
#include "id3v2header.h"
|
||||
#include "apetag.h"
|
||||
#include "apefooter.h"
|
||||
#include "mpctag.h"
|
||||
|
||||
using namespace TagLib;
|
||||
@ -36,7 +37,6 @@ class MPC::File::FilePrivate
|
||||
public:
|
||||
FilePrivate() :
|
||||
APETag(0),
|
||||
APEFooter(-1),
|
||||
APELocation(-1),
|
||||
APESize(0),
|
||||
ID3v1Tag(0),
|
||||
@ -197,8 +197,13 @@ void MPC::File::read(bool readProperties, Properties::ReadStyle /* propertiesSty
|
||||
|
||||
findAPE();
|
||||
|
||||
// Look for an APE tag
|
||||
|
||||
d->APELocation = findAPE();
|
||||
|
||||
if(d->APELocation >= 0) {
|
||||
d->APETag = new APE::Tag(this, d->APEFooter);
|
||||
d->APETag = new APE::Tag(this, d->APELocation);
|
||||
d->APESize = d->APETag->footer()->completeTagSize();
|
||||
d->hasAPE = true;
|
||||
}
|
||||
|
||||
@ -237,26 +242,20 @@ void MPC::File::read(bool readProperties, Properties::ReadStyle /* propertiesSty
|
||||
}
|
||||
}
|
||||
|
||||
bool MPC::File::findAPE()
|
||||
long MPC::File::findAPE()
|
||||
{
|
||||
if(!isValid())
|
||||
return false;
|
||||
if(isValid()) {
|
||||
if (d->hasID3v1)
|
||||
seek(-160, End);
|
||||
else
|
||||
seek(-32, End);
|
||||
|
||||
if (d->hasID3v1)
|
||||
seek(-160, End);
|
||||
else
|
||||
seek(-32, End);
|
||||
long p = tell();
|
||||
long p = tell();
|
||||
|
||||
ByteVector footer = readBlock(32);
|
||||
if(footer.mid(0,8) == APE::Tag::fileIdentifier()) {
|
||||
d->APEFooter = p;
|
||||
d->APESize = APE::Tag::tagSize(footer);
|
||||
d->APELocation = p + 32 - d->APESize;
|
||||
return true;
|
||||
if(readBlock(8) == APE::Tag::fileIdentifier())
|
||||
return p;
|
||||
}
|
||||
|
||||
return false;
|
||||
return -1;
|
||||
}
|
||||
|
||||
long MPC::File::findID3v1()
|
||||
|
@ -137,7 +137,7 @@ namespace TagLib {
|
||||
|
||||
void read(bool readProperties, Properties::ReadStyle propertiesStyle);
|
||||
void scan();
|
||||
bool findAPE();
|
||||
long findAPE();
|
||||
long findID3v1();
|
||||
long findID3v2();
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user