mirror of
https://github.com/taglib/taglib.git
synced 2025-06-04 01:28:21 -04:00
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
This commit is contained in:
parent
62ca49decb
commit
2025c4e9e5
265
mpc/apetag.cpp
265
mpc/apetag.cpp
@ -38,24 +38,22 @@ public:
|
||||
long tagOffset;
|
||||
long tagLength;
|
||||
|
||||
Map<const String, String> items;
|
||||
Map<const String, ByteVector> unknowns;
|
||||
FieldListMap fieldListMap;
|
||||
Map<const String, ByteVector> binaries;
|
||||
};
|
||||
|
||||
/*
|
||||
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;
|
||||
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
|
||||
@ -80,22 +78,35 @@ APE::Tag::~Tag()
|
||||
delete d;
|
||||
}
|
||||
|
||||
static ByteVector APEItem(String key, String value)
|
||||
using TagLib::uint;
|
||||
|
||||
static ByteVector _render_APEItem(String key, Item item)
|
||||
{
|
||||
ByteVector data;
|
||||
TagLib::uint flags = 0;
|
||||
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(char(0));
|
||||
data.append(value.data(String::UTF8));
|
||||
data.append(ByteVector('\0'));
|
||||
data.append(value);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
static ByteVector APEFrame(bool isHeader, TagLib::uint dataSize, TagLib::uint itemCount)
|
||||
{
|
||||
static ByteVector _render_APEFrame(bool isHeader, uint dataSize, uint itemCount) {
|
||||
ByteVector header;
|
||||
TagLib::uint tagSize = 32 + dataSize;
|
||||
// bit 31: Has a header
|
||||
@ -117,32 +128,30 @@ 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));
|
||||
{ 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->unknowns.begin();
|
||||
while(i != d->unknowns.end()) {
|
||||
if(!i->second.isEmpty()) {
|
||||
data.append(i->second);
|
||||
itemCount++;
|
||||
{ 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(APEFrame(true, data.size(), itemCount));
|
||||
tag.append(_render_APEFrame(true, data.size(), itemCount));
|
||||
tag.append(data);
|
||||
tag.append(APEFrame(false, data.size(), itemCount));
|
||||
tag.append(_render_APEFrame(false, data.size(), itemCount));
|
||||
|
||||
return tag;
|
||||
}
|
||||
@ -154,117 +163,124 @@ ByteVector APE::Tag::fileIdentifier()
|
||||
|
||||
String APE::Tag::title() const
|
||||
{
|
||||
if(d->items.contains("Title"))
|
||||
return d->items["Title"];
|
||||
else
|
||||
if(d->fieldListMap["TITLE"].isEmpty())
|
||||
return String::null;
|
||||
return d->fieldListMap["TITLE"].value.front();
|
||||
}
|
||||
|
||||
String APE::Tag::artist() const
|
||||
{
|
||||
if(d->items.contains("Artist"))
|
||||
return d->items["Artist"];
|
||||
else
|
||||
if(d->fieldListMap["ARTIST"].isEmpty())
|
||||
return String::null;
|
||||
return d->fieldListMap["ARTIST"].value.front();
|
||||
}
|
||||
|
||||
String APE::Tag::album() const
|
||||
{
|
||||
if(d->items.contains("Album"))
|
||||
return d->items["Album"];
|
||||
else
|
||||
if(d->fieldListMap["ALBUM"].isEmpty())
|
||||
return String::null;
|
||||
return d->fieldListMap["ALBUM"].value.front();
|
||||
}
|
||||
|
||||
String APE::Tag::comment() const
|
||||
{
|
||||
if(d->items.contains("Comment"))
|
||||
return d->items["Comment"];
|
||||
else
|
||||
if(d->fieldListMap["COMMENT"].isEmpty())
|
||||
return String::null;
|
||||
return d->fieldListMap["COMMENT"].value.front();
|
||||
}
|
||||
|
||||
String APE::Tag::genre() const
|
||||
{
|
||||
if(d->items.contains("Genre"))
|
||||
return d->items["Genre"];
|
||||
else
|
||||
if(d->fieldListMap["GENRE"].isEmpty())
|
||||
return String::null;
|
||||
return d->fieldListMap["GENRE"].value.front();
|
||||
}
|
||||
|
||||
TagLib::uint APE::Tag::year() const
|
||||
{
|
||||
if(d->items.contains("Year"))
|
||||
return (d->items["Year"]).toInt();
|
||||
return 0;
|
||||
if(d->fieldListMap["YEAR"].isEmpty())
|
||||
return 0;
|
||||
return d->fieldListMap["YEAR"].value.front().toInt();
|
||||
}
|
||||
|
||||
TagLib::uint APE::Tag::track() const
|
||||
{
|
||||
if(d->items.contains("Track"))
|
||||
return (d->items["Track"]).toInt();
|
||||
return 0;
|
||||
if(d->fieldListMap["TRACK"].isEmpty())
|
||||
return 0;
|
||||
return d->fieldListMap["TRACK"].value.front().toInt();
|
||||
}
|
||||
|
||||
void APE::Tag::setTitle(const String &s)
|
||||
{
|
||||
d->items["Title"] = s;
|
||||
addField("TITLE", s, true);
|
||||
}
|
||||
|
||||
void APE::Tag::setArtist(const String &s)
|
||||
{
|
||||
d->items["Artist"] = s;
|
||||
addField("ARTIST", s, true);
|
||||
}
|
||||
|
||||
void APE::Tag::setAlbum(const String &s)
|
||||
{
|
||||
d->items["Album"] = s;
|
||||
addField("ALBUM", s, true);
|
||||
}
|
||||
|
||||
void APE::Tag::setComment(const String &s)
|
||||
{
|
||||
if(s.isEmpty())
|
||||
removeComment("Comment");
|
||||
else
|
||||
d->items["Comment"] = s;
|
||||
addField("COMMENT", s, true);
|
||||
}
|
||||
|
||||
void APE::Tag::setGenre(const String &s)
|
||||
{
|
||||
if(s.isEmpty())
|
||||
removeComment("Genre");
|
||||
else
|
||||
d->items["Genre"] = s;
|
||||
addField("GENRE", s, true);
|
||||
}
|
||||
|
||||
void APE::Tag::setYear(uint i)
|
||||
{
|
||||
if(i <= 0)
|
||||
removeComment("Year");
|
||||
if(i <=0 )
|
||||
removeField("YEAR");
|
||||
else
|
||||
d->items["Year"] = String::number(i);
|
||||
addField("YEAR", String::number(i), true);
|
||||
}
|
||||
|
||||
void APE::Tag::setTrack(uint i)
|
||||
{
|
||||
if(i <= 0)
|
||||
removeComment("Track");
|
||||
if(i <=0 )
|
||||
removeField("TRACK");
|
||||
else
|
||||
d->items["Track"] = String::number(i);
|
||||
addField("TRACK", String::number(i), true);
|
||||
}
|
||||
|
||||
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)
|
||||
const APE::FieldListMap& APE::Tag::fieldListMap() const
|
||||
{
|
||||
if(value.isEmpty())
|
||||
removeComment(key);
|
||||
else
|
||||
d->items[key] = value;
|
||||
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)
|
||||
@ -277,13 +293,20 @@ TagLib::uint APE::Tag::tagSize(const ByteVector &footer)
|
||||
|
||||
uint flags = footer.mid(20, 4).toUInt(false);
|
||||
|
||||
return length + (flags & (1U << 31) ? 32 : 0);
|
||||
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()) {
|
||||
@ -305,67 +328,41 @@ void APE::Tag::read()
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
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 == 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));
|
||||
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--;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
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--;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
44
mpc/apetag.h
44
mpc/apetag.h
@ -24,15 +24,39 @@
|
||||
|
||||
#include "tag.h"
|
||||
#include "tbytevector.h"
|
||||
#include "tmap.h"
|
||||
#include "tstring.h"
|
||||
#include "tstringlist.h"
|
||||
|
||||
namespace TagLib {
|
||||
|
||||
class File;
|
||||
|
||||
//! An APE tag implementation
|
||||
|
||||
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:
|
||||
@ -87,17 +111,29 @@ namespace TagLib {
|
||||
virtual void setYear(uint i);
|
||||
virtual void setTrack(uint i);
|
||||
|
||||
const FieldListMap &fieldListMap() const;
|
||||
|
||||
/*!
|
||||
* Removes the \a key comment from the tag
|
||||
*/
|
||||
void removeComment(const String &key);
|
||||
void removeField(const String &key);
|
||||
|
||||
/*!
|
||||
* Adds the \a key comment with \a value
|
||||
*/
|
||||
void addComment(const String &key, const String &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.
|
||||
*/
|
||||
|
119
mpc/mpcfile.cpp
119
mpc/mpcfile.cpp
@ -27,6 +27,7 @@
|
||||
#include "id3v1tag.h"
|
||||
#include "id3v2header.h"
|
||||
#include "apetag.h"
|
||||
#include "mpctag.h"
|
||||
|
||||
using namespace TagLib;
|
||||
|
||||
@ -43,6 +44,7 @@ public:
|
||||
ID3v2Header(0),
|
||||
ID3v2Location(-1),
|
||||
ID3v2Size(0),
|
||||
tag(0),
|
||||
properties(0),
|
||||
scanned(false),
|
||||
hasAPE(false),
|
||||
@ -95,16 +97,7 @@ MPC::File::~File()
|
||||
|
||||
TagLib::Tag *MPC::File::tag() const
|
||||
{
|
||||
if(d->APETag)
|
||||
return d->APETag;
|
||||
else {
|
||||
if(d->ID3v1Tag)
|
||||
return d->ID3v1Tag;
|
||||
else {
|
||||
d->APETag = new APE::Tag;
|
||||
return d->APETag;
|
||||
}
|
||||
}
|
||||
return d->tag;
|
||||
}
|
||||
|
||||
MPC::Properties *MPC::File::audioProperties() const
|
||||
@ -114,27 +107,92 @@ MPC::Properties *MPC::File::audioProperties() const
|
||||
|
||||
bool MPC::File::save()
|
||||
{
|
||||
// Update APE tag
|
||||
|
||||
if(d->hasAPE && d->APETag)
|
||||
insert(d->APETag->render(), d->APELocation, d->APESize);
|
||||
else {
|
||||
// Update ID3v1 tag
|
||||
// Update ID3v1 tag
|
||||
|
||||
if(d->hasID3v1 && d->ID3v1Tag) {
|
||||
seek(-128, End);
|
||||
writeBlock(d->ID3v1Tag->render());
|
||||
if(d->ID3v1Tag)
|
||||
{
|
||||
if(d->hasID3v1)
|
||||
{
|
||||
seek(-128, End);
|
||||
writeBlock(d->ID3v1Tag->render());
|
||||
}
|
||||
else
|
||||
{
|
||||
seek(0, End);
|
||||
writeBlock(d->ID3v1Tag->render());
|
||||
}
|
||||
}
|
||||
|
||||
// Update APE tag
|
||||
|
||||
if(d->APETag)
|
||||
{
|
||||
if(d->hasAPE)
|
||||
{
|
||||
insert(d->APETag->render(), d->APELocation, d->APESize);
|
||||
}
|
||||
else
|
||||
if (d->hasID3v1)
|
||||
{
|
||||
seek(-128, End);
|
||||
insert(d->APETag->render(), tell(), 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
seek(0, End);
|
||||
writeBlock(d->APETag->render());
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ID3v1::Tag *MPC::File::ID3v1Tag(bool create)
|
||||
{
|
||||
if(!create || d->ID3v1Tag)
|
||||
return d->ID3v1Tag;
|
||||
|
||||
// no ID3v1 tag exists and we've been asked to create one
|
||||
|
||||
d->ID3v1Tag = new ID3v1::Tag;
|
||||
if (d->APETag)
|
||||
d->tag = new CombinedTag(d->APETag, d->ID3v1Tag);
|
||||
else
|
||||
d->tag = d->ID3v1Tag;
|
||||
return d->ID3v1Tag;
|
||||
}
|
||||
|
||||
APE::Tag *MPC::File::APETag(bool create)
|
||||
{
|
||||
if(!create || d->APETag)
|
||||
return d->APETag;
|
||||
|
||||
// no APE tag exists and we've been asked to create one
|
||||
|
||||
d->APETag = new APE::Tag;
|
||||
if (d->ID3v1Tag)
|
||||
d->tag = new CombinedTag(d->APETag, d->ID3v1Tag);
|
||||
else
|
||||
d->tag = d->APETag;
|
||||
return d->APETag;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// private members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void MPC::File::read(bool readProperties, Properties::ReadStyle /* propertiesStyle */)
|
||||
{
|
||||
// Look for an ID3v1 tag
|
||||
|
||||
d->ID3v1Location = findID3v1();
|
||||
|
||||
if(d->ID3v1Location >= 0) {
|
||||
d->ID3v1Tag = new ID3v1::Tag(this, d->ID3v1Location);
|
||||
d->hasID3v1 = true;
|
||||
}
|
||||
|
||||
// Look for an APE tag
|
||||
|
||||
findAPE();
|
||||
@ -144,16 +202,16 @@ void MPC::File::read(bool readProperties, Properties::ReadStyle /* propertiesSty
|
||||
d->hasAPE = true;
|
||||
}
|
||||
|
||||
// Look for an ID3v1 tag
|
||||
|
||||
if(!d->hasAPE) {
|
||||
d->ID3v1Location = findID3v1();
|
||||
|
||||
if(d->ID3v1Location >= 0) {
|
||||
d->ID3v1Tag = new ID3v1::Tag(this, d->ID3v1Location);
|
||||
d->hasID3v1 = true;
|
||||
}
|
||||
}
|
||||
if (d->hasID3v1 && d->hasAPE)
|
||||
d->tag = new CombinedTag(d->APETag, d->ID3v1Tag);
|
||||
else
|
||||
if (d->hasID3v1)
|
||||
d->tag = d->ID3v1Tag;
|
||||
else
|
||||
if (d->hasAPE)
|
||||
d->tag = d->APETag;
|
||||
else
|
||||
d->tag = d->APETag = new APE::Tag();
|
||||
|
||||
// Look for and skip an ID3v2 tag
|
||||
|
||||
@ -184,7 +242,10 @@ bool MPC::File::findAPE()
|
||||
if(!isValid())
|
||||
return false;
|
||||
|
||||
seek(-32, End);
|
||||
if (d->hasID3v1)
|
||||
seek(-160, End);
|
||||
else
|
||||
seek(-32, End);
|
||||
long p = tell();
|
||||
|
||||
ByteVector footer = readBlock(32);
|
||||
|
@ -30,13 +30,17 @@ namespace TagLib {
|
||||
|
||||
class Tag;
|
||||
|
||||
namespace ID3v1 { class Tag; }
|
||||
namespace APE { class Tag; }
|
||||
|
||||
//! An implementation of MPC metadata
|
||||
|
||||
/*!
|
||||
* This is implementation of MPC metadata.
|
||||
*
|
||||
* This supports ID3v1 and APE (v1 and v2) style comments as well as reading stream
|
||||
* properties from the file. ID3v2 tags will be skipped and ignored.
|
||||
* properties from the file. ID3v2 tags are invalid in MPC-files, but will be skipped
|
||||
* and ignored.
|
||||
*/
|
||||
|
||||
namespace MPC {
|
||||
@ -48,11 +52,27 @@ namespace TagLib {
|
||||
* TagLib::Tag and TagLib::AudioProperties interfaces by way of implementing
|
||||
* the abstract TagLib::File API as well as providing some additional
|
||||
* information specific to MPC files.
|
||||
* The only invalid tag combination supported is an ID3v1 tag after an APE tag.
|
||||
*/
|
||||
|
||||
class File : public TagLib::File
|
||||
{
|
||||
public:
|
||||
/*!
|
||||
* This set of flags is used for various operations and is suitable for
|
||||
* being OR-ed together.
|
||||
*/
|
||||
enum TagTypes {
|
||||
//! Empty set. Matches no tag types.
|
||||
NoTags = 0x0000,
|
||||
//! Matches ID3v1 tags.
|
||||
ID3v1 = 0x0001,
|
||||
//! Matches APE tags.
|
||||
APE = 0x0004,
|
||||
//! Matches APE behind ID3v1
|
||||
APEID3 = 0x0005
|
||||
};
|
||||
|
||||
/*!
|
||||
* Contructs an MPC file from \a file. If \a readProperties is true the
|
||||
* file's audio properties will also be read using \a propertiesStyle. If
|
||||
@ -67,8 +87,8 @@ namespace TagLib {
|
||||
virtual ~File();
|
||||
|
||||
/*!
|
||||
* Returns the Tag for this file. This will be either an APE or
|
||||
* ID3v1 tag.
|
||||
* Returns the Tag for this file. This will be an APE tag, an ID3v1 tag
|
||||
* or a combination of the two.
|
||||
*/
|
||||
virtual TagLib::Tag *tag() const;
|
||||
|
||||
@ -83,6 +103,34 @@ namespace TagLib {
|
||||
*/
|
||||
virtual bool save();
|
||||
|
||||
/*!
|
||||
* Returns a pointer to the ID3v1 tag of the file.
|
||||
*
|
||||
* If \a create is false (the default) this will return a null pointer
|
||||
* if there is no valid ID3v1 tag. If \a create is true it will create
|
||||
* an ID3v1 tag if one does not exist. If there is already an APE tag, the
|
||||
* new ID3v1 tag will be placed after it.
|
||||
*
|
||||
* \note The Tag <b>is still</b> owned by the APE::File and should not be
|
||||
* deleted by the user. It will be deleted when the file (object) is
|
||||
* destroyed.
|
||||
*/
|
||||
ID3v1::Tag *ID3v1Tag(bool create = false);
|
||||
|
||||
/*!
|
||||
* Returns a pointer to the APE tag of the file.
|
||||
*
|
||||
* If \a create is false (the default) this will return a null pointer
|
||||
* if there is no valid APE tag. If \a create is true it will create
|
||||
* a APE tag if one does not exist. If there is already an ID3v1 tag, thes
|
||||
* new APE tag will be placed before it.
|
||||
*
|
||||
* \note The Tag <b>is still</b> owned by the APE::File and should not be
|
||||
* deleted by the user. It will be deleted when the file (object) is
|
||||
* destroyed.
|
||||
*/
|
||||
APE::Tag *APETag(bool create = false);
|
||||
|
||||
private:
|
||||
File(const File &);
|
||||
File &operator=(const File &);
|
||||
|
175
mpc/mpctag.h
Normal file
175
mpc/mpctag.h
Normal file
@ -0,0 +1,175 @@
|
||||
/***************************************************************************
|
||||
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 DO_NOT_DOCUMENT // Tell Doxygen not to document this header
|
||||
|
||||
#ifndef TAGLIB_MPCTAG_H
|
||||
#define TAGLIB_MPCTAG_H
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Note that this header is not installed.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <apetag.h>
|
||||
#include <id3v1tag.h>
|
||||
|
||||
namespace TagLib {
|
||||
|
||||
namespace MPC {
|
||||
|
||||
/*!
|
||||
* A union of APE and ID3v1 tags.
|
||||
*/
|
||||
class CombinedTag : public TagLib::Tag
|
||||
{
|
||||
public:
|
||||
CombinedTag(APE::Tag *ape = 0, ID3v1::Tag *id3v1 = 0) :
|
||||
TagLib::Tag(),
|
||||
ape(ape), id3v1(id3v1) {}
|
||||
|
||||
virtual String title() const {
|
||||
if(ape && !ape->title().isEmpty())
|
||||
return ape->title();
|
||||
|
||||
if(id3v1)
|
||||
return id3v1->title();
|
||||
|
||||
return String::null;
|
||||
}
|
||||
|
||||
virtual String artist() const {
|
||||
if(ape && !ape->artist().isEmpty())
|
||||
return ape->artist();
|
||||
|
||||
if(id3v1)
|
||||
return id3v1->artist();
|
||||
|
||||
return String::null;
|
||||
}
|
||||
|
||||
virtual String album() const {
|
||||
if(ape && !ape->album().isEmpty())
|
||||
return ape->album();
|
||||
|
||||
if(id3v1)
|
||||
return id3v1->album();
|
||||
|
||||
return String::null;
|
||||
}
|
||||
|
||||
virtual String comment() const {
|
||||
if(ape && !ape->comment().isEmpty())
|
||||
return ape->comment();
|
||||
|
||||
if(id3v1)
|
||||
return id3v1->comment();
|
||||
|
||||
return String::null;
|
||||
}
|
||||
|
||||
virtual String genre() const {
|
||||
if(ape && !ape->genre().isEmpty())
|
||||
return ape->genre();
|
||||
|
||||
if(id3v1)
|
||||
return id3v1->genre();
|
||||
|
||||
return String::null;
|
||||
}
|
||||
|
||||
virtual uint year() const {
|
||||
if(ape && ape->year() > 0)
|
||||
return ape->year();
|
||||
|
||||
if(id3v1)
|
||||
return id3v1->year();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
virtual uint track() const {
|
||||
if(ape && ape->track() > 0)
|
||||
return ape->track();
|
||||
|
||||
if(id3v1)
|
||||
return id3v1->track();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
virtual void setTitle(const String &s) {
|
||||
if(ape)
|
||||
ape->setTitle(s);
|
||||
if(id3v1)
|
||||
id3v1->setTitle(s);
|
||||
}
|
||||
|
||||
virtual void setArtist(const String &s) {
|
||||
if(ape)
|
||||
ape->setArtist(s);
|
||||
if(id3v1)
|
||||
id3v1->setArtist(s);
|
||||
}
|
||||
|
||||
virtual void setAlbum(const String &s) {
|
||||
if(ape)
|
||||
ape->setAlbum(s);
|
||||
if(id3v1)
|
||||
id3v1->setAlbum(s);
|
||||
}
|
||||
|
||||
virtual void setComment(const String &s) {
|
||||
if(ape)
|
||||
ape->setComment(s);
|
||||
if(id3v1)
|
||||
id3v1->setComment(s);
|
||||
}
|
||||
|
||||
virtual void setGenre(const String &s) {
|
||||
if(ape)
|
||||
ape->setGenre(s);
|
||||
if(id3v1)
|
||||
id3v1->setGenre(s);
|
||||
}
|
||||
|
||||
virtual void setYear(uint i) {
|
||||
if(ape)
|
||||
ape->setYear(i);
|
||||
if(id3v1)
|
||||
id3v1->setYear(i);
|
||||
}
|
||||
|
||||
virtual void setTrack(uint i) {
|
||||
if(ape)
|
||||
ape->setTrack(i);
|
||||
if(id3v1)
|
||||
id3v1->setTrack(i);
|
||||
}
|
||||
|
||||
private:
|
||||
APE::Tag *ape;
|
||||
ID3v1::Tag *id3v1;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
Loading…
x
Reference in New Issue
Block a user