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
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
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;
}
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);
+const APE::FieldListMap& APE::Tag::fieldListMap() const
+{
+ return d->fieldListMap;
}
-void APE::Tag::addComment(const String &key, const String &value)
-{
- if(value.isEmpty())
- removeComment(key);
- else
- d->items[key] = value;
+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)
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()) {
}
}
-void APE::Tag::parse(const ByteVector &data, uint count)
+static StringList _parse_APEString(ByteVector val)
{
- 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--;
- }
+ 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;
+ 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);
- 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;
+ 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));
}
- item.readOnly = (flags & 1);
-
- d->items.insert(key,item);
pos += 8 + key.size() + 1 + vallen;
count--;
}
}
-*/
#include "id3v1tag.h"
#include "id3v2header.h"
#include "apetag.h"
+#include "mpctag.h"
using namespace TagLib;
ID3v2Header(0),
ID3v2Location(-1),
ID3v2Size(0),
+ tag(0),
properties(0),
scanned(false),
hasAPE(false),
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
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();
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
if(!isValid())
return false;
- seek(-32, End);
+ if (d->hasID3v1)
+ seek(-160, End);
+ else
+ seek(-32, End);
long p = tell();
ByteVector footer = readBlock(32);
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 {
* 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
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;
*/
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 &);
--- /dev/null
+/***************************************************************************
+ 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