Added TagLib (with AUTORS and COPYING files)
[someplayer] / src / taglib / ogg / xiphcomment.cpp
diff --git a/src/taglib/ogg/xiphcomment.cpp b/src/taglib/ogg/xiphcomment.cpp
new file mode 100644 (file)
index 0000000..406137a
--- /dev/null
@@ -0,0 +1,319 @@
+/***************************************************************************
+    copyright            : (C) 2002 - 2008 by Scott Wheeler
+    email                : wheeler@kde.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                                                                   *
+ *                                                                         *
+ *   Alternatively, this file is available under the Mozilla Public        *
+ *   License Version 1.1.  You may obtain a copy of the License at         *
+ *   http://www.mozilla.org/MPL/                                           *
+ ***************************************************************************/
+
+#include <tbytevector.h>
+#include <tdebug.h>
+
+#include <xiphcomment.h>
+
+using namespace TagLib;
+
+class Ogg::XiphComment::XiphCommentPrivate
+{
+public:
+  FieldListMap fieldListMap;
+  String vendorID;
+  String commentField;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+// public members
+////////////////////////////////////////////////////////////////////////////////
+
+Ogg::XiphComment::XiphComment() : TagLib::Tag()
+{
+  d = new XiphCommentPrivate;
+}
+
+Ogg::XiphComment::XiphComment(const ByteVector &data) : TagLib::Tag()
+{
+  d = new XiphCommentPrivate;
+  parse(data);
+}
+
+Ogg::XiphComment::~XiphComment()
+{
+  delete d;
+}
+
+String Ogg::XiphComment::title() const
+{
+  if(d->fieldListMap["TITLE"].isEmpty())
+    return String::null;
+  return d->fieldListMap["TITLE"].front();
+}
+
+String Ogg::XiphComment::artist() const
+{
+  if(d->fieldListMap["ARTIST"].isEmpty())
+    return String::null;
+  return d->fieldListMap["ARTIST"].front();
+}
+
+String Ogg::XiphComment::album() const
+{
+  if(d->fieldListMap["ALBUM"].isEmpty())
+    return String::null;
+  return d->fieldListMap["ALBUM"].front();
+}
+
+String Ogg::XiphComment::comment() const
+{
+  if(!d->fieldListMap["DESCRIPTION"].isEmpty()) {
+    d->commentField = "DESCRIPTION";
+    return d->fieldListMap["DESCRIPTION"].front();
+  }
+
+  if(!d->fieldListMap["COMMENT"].isEmpty()) {
+    d->commentField = "COMMENT";
+    return d->fieldListMap["COMMENT"].front();
+  }
+
+  return String::null;
+}
+
+String Ogg::XiphComment::genre() const
+{
+  if(d->fieldListMap["GENRE"].isEmpty())
+    return String::null;
+  return d->fieldListMap["GENRE"].front();
+}
+
+TagLib::uint Ogg::XiphComment::year() const
+{
+  if(!d->fieldListMap["DATE"].isEmpty())
+    return d->fieldListMap["DATE"].front().toInt();
+  if(!d->fieldListMap["YEAR"].isEmpty())
+    return d->fieldListMap["YEAR"].front().toInt();
+  return 0;
+}
+
+TagLib::uint Ogg::XiphComment::track() const
+{
+  if(!d->fieldListMap["TRACKNUMBER"].isEmpty())
+    return d->fieldListMap["TRACKNUMBER"].front().toInt();
+  if(!d->fieldListMap["TRACKNUM"].isEmpty())
+    return d->fieldListMap["TRACKNUM"].front().toInt();
+  return 0;
+}
+
+void Ogg::XiphComment::setTitle(const String &s)
+{
+  addField("TITLE", s);
+}
+
+void Ogg::XiphComment::setArtist(const String &s)
+{
+  addField("ARTIST", s);
+}
+
+void Ogg::XiphComment::setAlbum(const String &s)
+{
+  addField("ALBUM", s);
+}
+
+void Ogg::XiphComment::setComment(const String &s)
+{
+  addField(d->commentField.isEmpty() ? "DESCRIPTION" : d->commentField, s);
+}
+
+void Ogg::XiphComment::setGenre(const String &s)
+{
+  addField("GENRE", s);
+}
+
+void Ogg::XiphComment::setYear(uint i)
+{
+  removeField("YEAR");
+  if(i == 0)
+    removeField("DATE");
+  else
+    addField("DATE", String::number(i));
+}
+
+void Ogg::XiphComment::setTrack(uint i)
+{
+  removeField("TRACKNUM");
+  if(i == 0)
+    removeField("TRACKNUMBER");
+  else
+    addField("TRACKNUMBER", String::number(i));
+}
+
+bool Ogg::XiphComment::isEmpty() const
+{
+  FieldListMap::ConstIterator it = d->fieldListMap.begin();
+  for(; it != d->fieldListMap.end(); ++it)
+    if(!(*it).second.isEmpty())
+      return false;
+
+  return true;
+}
+
+TagLib::uint Ogg::XiphComment::fieldCount() const
+{
+  uint count = 0;
+
+  FieldListMap::ConstIterator it = d->fieldListMap.begin();
+  for(; it != d->fieldListMap.end(); ++it)
+    count += (*it).second.size();
+
+  return count;
+}
+
+const Ogg::FieldListMap &Ogg::XiphComment::fieldListMap() const
+{
+  return d->fieldListMap;
+}
+
+String Ogg::XiphComment::vendorID() const
+{
+  return d->vendorID;
+}
+
+void Ogg::XiphComment::addField(const String &key, const String &value, bool replace)
+{
+  if(replace)
+    removeField(key.upper());
+
+  if(!key.isEmpty() && !value.isEmpty())
+    d->fieldListMap[key.upper()].append(value);
+}
+
+void Ogg::XiphComment::removeField(const String &key, const String &value)
+{
+  if(!value.isNull()) {
+    StringList::Iterator it = d->fieldListMap[key].begin();
+    while(it != d->fieldListMap[key].end()) {
+      if(value == *it)
+        it = d->fieldListMap[key].erase(it);
+      else
+        it++;
+    }
+  }
+  else
+    d->fieldListMap.erase(key);
+}
+
+bool Ogg::XiphComment::contains(const String &key) const
+{
+  return d->fieldListMap.contains(key) && !d->fieldListMap[key].isEmpty();
+}
+
+ByteVector Ogg::XiphComment::render() const
+{
+  return render(true);
+}
+
+ByteVector Ogg::XiphComment::render(bool addFramingBit) const
+{
+  ByteVector data;
+
+  // Add the vendor ID length and the vendor ID.  It's important to use the
+  // length of the data(String::UTF8) rather than the length of the the string
+  // since this is UTF8 text and there may be more characters in the data than
+  // in the UTF16 string.
+
+  ByteVector vendorData = d->vendorID.data(String::UTF8);
+
+  data.append(ByteVector::fromUInt(vendorData.size(), false));
+  data.append(vendorData);
+
+  // Add the number of fields.
+
+  data.append(ByteVector::fromUInt(fieldCount(), false));
+
+  // Iterate over the the field lists.  Our iterator returns a
+  // std::pair<String, StringList> where the first String is the field name and
+  // the StringList is the values associated with that field.
+
+  FieldListMap::ConstIterator it = d->fieldListMap.begin();
+  for(; it != d->fieldListMap.end(); ++it) {
+
+    // And now iterate over the values of the current list.
+
+    String fieldName = (*it).first;
+    StringList values = (*it).second;
+
+    StringList::ConstIterator valuesIt = values.begin();
+    for(; valuesIt != values.end(); ++valuesIt) {
+      ByteVector fieldData = fieldName.data(String::UTF8);
+      fieldData.append('=');
+      fieldData.append((*valuesIt).data(String::UTF8));
+
+      data.append(ByteVector::fromUInt(fieldData.size(), false));
+      data.append(fieldData);
+    }
+  }
+
+  // Append the "framing bit".
+
+  if(addFramingBit)
+    data.append(char(1));
+
+  return data;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// protected members
+////////////////////////////////////////////////////////////////////////////////
+
+void Ogg::XiphComment::parse(const ByteVector &data)
+{
+  // The first thing in the comment data is the vendor ID length, followed by a
+  // UTF8 string with the vendor ID.
+
+  int pos = 0;
+
+  int vendorLength = data.mid(0, 4).toUInt(false);
+  pos += 4;
+
+  d->vendorID = String(data.mid(pos, vendorLength), String::UTF8);
+  pos += vendorLength;
+
+  // Next the number of fields in the comment vector.
+
+  int commentFields = data.mid(pos, 4).toUInt(false);
+  pos += 4;
+
+  for(int i = 0; i < commentFields; i++) {
+
+    // Each comment field is in the format "KEY=value" in a UTF8 string and has
+    // 4 bytes before the text starts that gives the length.
+
+    int commentLength = data.mid(pos, 4).toUInt(false);
+    pos += 4;
+
+    String comment = String(data.mid(pos, commentLength), String::UTF8);
+    pos += commentLength;
+
+    int commentSeparatorPosition = comment.find("=");
+
+    String key = comment.substr(0, commentSeparatorPosition);
+    String value = comment.substr(commentSeparatorPosition + 1);
+
+    addField(key, value, false);
+  }
+}