]> granicus.if.org Git - taglib/commitdiff
Large update:
authorAllan Sandfeld Jensen <kde@carewolf.com>
Mon, 26 Jul 2004 12:58:59 +0000 (12:58 +0000)
committerAllan Sandfeld Jensen <kde@carewolf.com>
Mon, 26 Jul 2004 12:58:59 +0000 (12:58 +0000)
* 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

mpc/apetag.cpp
mpc/apetag.h
mpc/mpcfile.cpp
mpc/mpcfile.h
mpc/mpctag.h [new file with mode: 0644]

index 83362a2b599340b94cb79f75a54b31d909306c5d..551540a1a560eb0d62070b119e7fac2f4e274c69 100644 (file)
@@ -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 <=)
+    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 <=)
+    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)
@@ -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()
   }
 }
 
-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--;
   }
 }
-*/
index bf87d53322845eafe83c7cf4b5c64fc0b6e52e46..37b102d08ff9722461352ac5bb62eb686d65dd8d 100644 (file)
 
 #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.
        */
index 4a9d8add78d285499a3bac0a602d7b12994b4294..f57301fb5b251de878f960ecf0dc85e6a65d5039 100644 (file)
@@ -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);
index 9b5f57a752d1398e4b3fe79a0c2686ac2840f01b..900cadb44a9bd850f2727251af8b1517308576b6 100644 (file)
@@ -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 &);
diff --git a/mpc/mpctag.h b/mpc/mpctag.h
new file mode 100644 (file)
index 0000000..2b567bb
--- /dev/null
@@ -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