]> granicus.if.org Git - taglib/commitdiff
Support INFO tags of RIFF wave files.
authorTsuda Kageyu <tsuda.kageyu@gmail.com>
Sun, 26 Aug 2012 01:12:40 +0000 (10:12 +0900)
committerTsuda Kageyu <tsuda.kageyu@gmail.com>
Thu, 6 Sep 2012 23:16:26 +0000 (08:16 +0900)
taglib/riff/rifffile.cpp
taglib/riff/rifffile.h
taglib/riff/wav/infotag.cpp [new file with mode: 0644]
taglib/riff/wav/infotag.h [new file with mode: 0644]
taglib/riff/wav/wavfile.cpp
taglib/riff/wav/wavfile.h
tests/test_fileref.cpp
tests/test_info.cpp [new file with mode: 0644]

index df3b36635adfeae375d2728a5020b139457e1fdf..22fd318440c0f31f274b37cdda1abe8f16720cff 100644 (file)
@@ -138,34 +138,44 @@ ByteVector RIFF::File::chunkData(uint i)
   return readBlock(d->chunks[i].size);
 }
 
-void RIFF::File::setChunkData(const ByteVector &name, const ByteVector &data)
+void RIFF::File::setChunkData(uint i, const ByteVector &data)
 {
-  if(d->chunks.size() == 0) {
-    debug("RIFF::File::setChunkData - No valid chunks found.");
-    return;
-  }
+  // First we update the global size
 
-  for(uint i = 0; i < d->chunks.size(); i++) {
-    if(d->chunks[i].name == name) {
+  d->size += ((data.size() + 1) & ~1) - (d->chunks[i].size + d->chunks[i].padding);
+  insert(ByteVector::fromUInt(d->size, d->endianness == BigEndian), 4, 4);
 
-      // First we update the global size
+  // Now update the specific chunk
 
-      d->size += ((data.size() + 1) & ~1) - (d->chunks[i].size + d->chunks[i].padding);
-      insert(ByteVector::fromUInt(d->size, d->endianness == BigEndian), 4, 4);
+  writeChunk(chunkName(i), data, d->chunks[i].offset - 8, d->chunks[i].size + d->chunks[i].padding + 8);
 
-      // Now update the specific chunk
+  d->chunks[i].size = data.size();
+  d->chunks[i].padding = (data.size() & 0x01) ? 1 : 0;
 
-      writeChunk(name, data, d->chunks[i].offset - 8, d->chunks[i].size + d->chunks[i].padding + 8);
+  // Now update the internal offsets
 
-      d->chunks[i].size = data.size();
-      d->chunks[i].padding = (data.size() & 0x01) ? 1 : 0;
+  for(i++; i < d->chunks.size(); i++)
+    d->chunks[i].offset = d->chunks[i-1].offset + 8 + d->chunks[i-1].size + d->chunks[i-1].padding;
+}
 
-      // Now update the internal offsets
+void RIFF::File::setChunkData(const ByteVector &name, const ByteVector &data, bool alwaysCreate)
+{
+  if(d->chunks.size() == 0) {
+    debug("RIFF::File::setChunkData - No valid chunks found.");
+    return;
+  }
 
-      for(i++; i < d->chunks.size(); i++)
-        d->chunks[i].offset = d->chunks[i-1].offset + 8 + d->chunks[i-1].size + d->chunks[i-1].padding;
+  if(alwaysCreate && name != "LIST") {
+    debug("RIFF::File::setChunkData - alwaysCreate should be used for only \"LIST\" chunks.");
+    return;
+  }
 
-      return;
+  if(!alwaysCreate) {
+    for(uint i = 0; i < d->chunks.size(); i++) {
+      if(d->chunks[i].name == name) {
+        setChunkData(i, data);
+        return;
+      }
     }
   }
 
@@ -181,7 +191,8 @@ void RIFF::File::setChunkData(const ByteVector &name, const ByteVector &data)
 
   // Now add the chunk to the file
 
-  writeChunk(name, data, offset, std::max(ulong(0), length() - offset), (offset & 1) ? 1 : 0);
+  long len = length();
+  writeChunk(name, data, offset, std::max<long>(0, length() - offset), (offset & 1) ? 1 : 0);
 
   // And update our internal structure
 
@@ -199,6 +210,28 @@ void RIFF::File::setChunkData(const ByteVector &name, const ByteVector &data)
   d->chunks.push_back(chunk);
 }
 
+void RIFF::File::removeChunk(uint i)
+{
+  if(i >= d->chunks.size())
+    return;
+  
+  removeBlock(d->chunks[i].offset - 8, d->chunks[i].size + 8);
+  d->chunks.erase(d->chunks.begin() + i);
+}
+
+void RIFF::File::removeChunk(const ByteVector &name)
+{
+  std::vector<Chunk> newChunks;
+  for(size_t i = 0; i < d->chunks.size(); ++i) {
+    if(d->chunks[i].name == name)
+      removeBlock(d->chunks[i].offset - 8, d->chunks[i].size + 8);
+    else
+      newChunks.push_back(d->chunks[i]);
+  }
+
+  d->chunks.swap(newChunks);
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // private members
 ////////////////////////////////////////////////////////////////////////////////
index e418dbb6751f8c362b9078952b480c35cdb563b2..274549ae0246eb755f93221b8a82569440a74baf 100644 (file)
@@ -95,14 +95,39 @@ namespace TagLib {
        */
       ByteVector chunkData(uint i);
 
+      /*!
+       * Sets the data for the the specified chunk to \a data. 
+       *
+       * \warning This will update the file immediately.
+       */
+      void setChunkData(uint i, const ByteVector &data);
+
       /*!
        * Sets the data for the chunk \a name to \a data.  If a chunk with the
        * given name already exists it will be overwritten, otherwise it will be
        * created after the existing chunks.
        *
+       * \note If \a alwaysCreate is true, a new chunk is created regardless of 
+       * existence of chunk \a name. It should be used for only "LIST" chunks. 
+       *
+       * \warning This will update the file immediately.
+       */
+      void setChunkData(const ByteVector &name, const ByteVector &data, bool alwaysCreate = false);
+
+      /*!
+       * Removes the specified chunk.
+       *
+       * \warning This will update the file immediately.
+       */
+      void removeChunk(uint i);
+
+      /*!
+       * Removes the chunk \a name.
+       *
        * \warning This will update the file immediately.
+       * \warning This removes all the chunks with the given name.
        */
-      void setChunkData(const ByteVector &name, const ByteVector &data);
+      void removeChunk(const ByteVector &name);
 
     private:
       File(const File &);
diff --git a/taglib/riff/wav/infotag.cpp b/taglib/riff/wav/infotag.cpp
new file mode 100644 (file)
index 0000000..e358777
--- /dev/null
@@ -0,0 +1,250 @@
+/***************************************************************************
+    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., 51 Franklin Street, Fifth Floor, Boston, MA         *
+ *   02110-1301  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 <tdebug.h>
+#include <tfile.h>
+
+#include "infotag.h"
+
+using namespace TagLib;
+using namespace RIFF::Info;
+
+namespace {
+  static bool isValidChunkID(const ByteVector &name)
+  {
+    if(name.size() != 4)
+      return false;
+
+    for(int i = 0; i < 4; i++) {
+      if(name[i] < 32 || name[i] > 127)
+        return false;
+    }
+
+    return true;
+  }
+}
+
+class RIFF::Info::Tag::TagPrivate
+{
+public:
+  TagPrivate() 
+  {}
+
+  FieldListMap fieldListMap;
+
+  static const StringHandler *stringHandler;
+};
+
+
+
+StringHandler::StringHandler()
+{
+}
+
+StringHandler::~StringHandler()
+{
+}
+
+String RIFF::Info::StringHandler::parse(const ByteVector &data) const
+{
+  return String(data, String::UTF8);
+}
+
+ByteVector RIFF::Info::StringHandler::render(const String &s) const
+{
+  return s.data(String::UTF8);
+}
+
+
+
+static const StringHandler defaultStringHandler;
+const RIFF::Info::StringHandler *RIFF::Info::Tag::TagPrivate::stringHandler = &defaultStringHandler;
+
+RIFF::Info::Tag::Tag(const ByteVector &data) : TagLib::Tag()
+{
+  d = new TagPrivate;
+  parse(data);
+}
+
+RIFF::Info::Tag::Tag() : TagLib::Tag()
+{
+  d = new TagPrivate;
+}
+
+RIFF::Info::Tag::~Tag()
+{
+}
+
+String RIFF::Info::Tag::title() const
+{
+  return fieldText("INAM");
+}
+
+String RIFF::Info::Tag::artist() const
+{
+  return fieldText("IART");
+}
+
+String RIFF::Info::Tag::album() const
+{
+  return fieldText("IPRD");
+}
+
+String RIFF::Info::Tag::comment() const
+{
+  return fieldText("ICMT");
+}
+
+String RIFF::Info::Tag::genre() const
+{
+  return fieldText("IGNR");
+}
+
+TagLib::uint RIFF::Info::Tag::year() const
+{
+  return fieldText("ICRD").substr(0, 4).toInt();
+}
+
+TagLib::uint RIFF::Info::Tag::track() const
+{
+  return fieldText("IPRT").toInt();
+}
+
+void RIFF::Info::Tag::setTitle(const String &s)
+{
+  setFieldText("INAM", s);
+}
+
+void RIFF::Info::Tag::setArtist(const String &s)
+{
+  setFieldText("IART", s);
+}
+
+void RIFF::Info::Tag::setAlbum(const String &s)
+{
+  setFieldText("IPRD", s);
+}
+
+void RIFF::Info::Tag::setComment(const String &s)
+{
+  setFieldText("ICMT", s);
+}
+
+void RIFF::Info::Tag::setGenre(const String &s)
+{
+  setFieldText("IGNR", s);
+}
+
+void RIFF::Info::Tag::setYear(uint i)
+{
+  if(i != 0)
+    setFieldText("ICRD", String::number(i));
+  else
+    d->fieldListMap.erase("ICRD");
+}
+
+void RIFF::Info::Tag::setTrack(uint i)
+{
+  if(i != 0)
+    setFieldText("IPRT", String::number(i));
+  else
+    d->fieldListMap.erase("IPRT");
+}
+
+bool RIFF::Info::Tag::isEmpty() const
+{
+  return d->fieldListMap.isEmpty();
+}
+
+String RIFF::Info::Tag::fieldText(const ByteVector &id) const
+{
+  if(d->fieldListMap.contains(id))
+    return String(d->fieldListMap[id]);
+  else
+    return String();
+}
+
+void RIFF::Info::Tag::setFieldText(const ByteVector &id, const String &s)
+{
+  // id must be four-byte long pure ascii string.
+  if(!isValidChunkID(id))
+    return;
+
+  if(!s.isEmpty())
+    d->fieldListMap[id] = s;
+  else
+    removeField(id);
+}
+
+void RIFF::Info::Tag::removeField(const ByteVector &id)
+{
+  if(d->fieldListMap.contains(id))
+    d->fieldListMap.erase(id);
+}
+
+ByteVector RIFF::Info::Tag::render() const
+{
+  ByteVector data("INFO");
+
+  FieldListMap::ConstIterator it = d->fieldListMap.begin();
+  for(; it != d->fieldListMap.end(); ++it) {
+    ByteVector text = TagPrivate::stringHandler->render(it->second);
+    if(text.isEmpty())
+      continue;
+
+    data.append(it->first);
+    data.append(ByteVector::fromUInt(text.size() + 1, false));
+    data.append(text);
+    
+    do {
+      data.append('\0');
+    } while(data.size() & 1);
+  }
+
+  if(data.size() == 4)
+    return ByteVector();
+  else
+    return data;
+}
+
+void RIFF::Info::Tag::setStringHandler(const StringHandler *handler)
+{
+  if(handler)
+    TagPrivate::stringHandler = handler;
+  else
+    TagPrivate::stringHandler = &defaultStringHandler;
+}
+
+void RIFF::Info::Tag::parse(const ByteVector &data)
+{
+  uint p = 4;
+  while(p < data.size()) {
+    uint size = data.mid(p + 4, 4).toUInt(false);
+    d->fieldListMap[data.mid(p, 4)] = TagPrivate::stringHandler->parse(data.mid(p + 8, size));
+
+    p += ((size + 1) & ~1) + 8;
+  }
+}
+
diff --git a/taglib/riff/wav/infotag.h b/taglib/riff/wav/infotag.h
new file mode 100644 (file)
index 0000000..7241484
--- /dev/null
@@ -0,0 +1,179 @@
+/***************************************************************************
+    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., 51 Franklin Street, Fifth Floor, Boston, MA         *
+ *   02110-1301  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/                                           *
+ ***************************************************************************/
+
+#ifndef TAGLIB_INFOTAG_H
+#define TAGLIB_INFOTAG_H
+
+#include "tag.h"
+#include "tmap.h"
+#include "tstring.h"
+#include "tstringlist.h"
+#include "tbytevector.h"
+#include "taglib_export.h"
+
+namespace TagLib {
+
+  class File;
+
+  //! A RIFF Info tag implementation. 
+  namespace RIFF {
+  namespace Info {
+
+    typedef Map<ByteVector, String> FieldListMap;
+
+    //! A abstraction for the string to data encoding in Info tags.
+
+    /*!
+     * RIFF Info tag has no clear definitions about character encodings.
+     * In practice, local encoding of each system is largely used and UTF-8 is
+     * popular too.
+     *
+     * Here is an option to read and write tags in your preferrd encoding 
+     * by subclassing this class, reimplementing parse() and render() and setting 
+     * your reimplementation as the default with Info::Tag::setStringHandler().
+     *
+     * \see ID3v1::Tag::setStringHandler()
+     */
+
+    class TAGLIB_EXPORT StringHandler
+    {
+    public:
+      StringHandler();
+      ~StringHandler();
+
+      /*!
+       * Decode a string from \a data.  The default implementation assumes that
+       * \a data is an UTF-8 character array.
+       */
+      virtual String parse(const ByteVector &data) const;
+
+      /*!
+       * Encode a ByteVector with the data from \a s.  The default implementation
+       * assumes that \a s is an UTF-8 string. 
+       */
+      virtual ByteVector render(const String &s) const;
+    };
+
+    //! The main class in the ID3v2 implementation
+
+    /*!
+     * This is the main class in the INFO tag implementation.  RIFF INFO tag is a 
+     * metadata format found in WAV audio and AVI video files.  Though it is a part 
+     * of Microsoft/IBM's RIFF specification, the author could not find the official 
+     * documents about it.  So, this implementation is refering to unofficial documents 
+     * online and some applications' behaviors especially Windows Explorer.
+     */
+    class TAGLIB_EXPORT Tag : public TagLib::Tag
+    {
+    public:
+      /*!
+       * Constructs an empty Info tag.
+       */
+      Tag();
+
+      /*!
+       * Constructs an Info tag read from \a data which is contents of "LIST" chunk.
+       */
+      Tag(const ByteVector &data);
+
+      virtual ~Tag();
+
+      // 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);
+
+      virtual bool isEmpty() const;
+      /*
+       * Gets the value of the field with the ID \a id.
+       */
+      String fieldText(const ByteVector &id) const;
+        
+      /*
+        * Sets the value of the field with the ID \a id to \a s.
+        * If the field does not exist, it is created.
+        * If \s is empty, the field is removed.
+        *
+        * \note fieldId must be four-byte long pure ascii string.  This function 
+        * performs nothing if fieldId is invalid.
+        */
+      void setFieldText(const ByteVector &id, const String &s);
+
+      /*
+       * Removes the field with the ID \a id.
+       */
+      void removeField(const ByteVector &id);
+
+      /*!
+       * Render the tag back to binary data, suitable to be written to disk.
+       *
+       * \note Returns empty ByteVector is the tag contains no fields. 
+       */
+      ByteVector render() const;
+
+      /*!
+       * Sets the string handler that decides how the text data will be
+       * converted to and from binary data.
+       * If the parameter \a handler is null, the previous handler is
+       * released and default UTF-8 handler is restored.
+       *
+       * \note The caller is responsible for deleting the previous handler
+       * as needed after it is released.
+       *
+       * \see StringHandler
+       */
+      static void setStringHandler(const StringHandler *handler);
+    
+    protected:
+      /*!
+       * Pareses the body of the tag in \a data.
+       */
+      void parse(const ByteVector &data);
+
+
+    private:
+      Tag(const Tag &);
+      Tag &operator=(const Tag &);
+
+      class TagPrivate;
+      TagPrivate *d;
+    };
+  }}
+}
+
+#endif
index 613db4ef9de4ffbe2b2849ec6f0ba8e43e6c397f..dd7da2c86956158a57557354bfeb975ca6078973 100644 (file)
  *   http://www.mozilla.org/MPL/                                           *
  ***************************************************************************/
 
-#include <tbytevector.h>
-#include <tdebug.h>
-#include <id3v2tag.h>
-#include <tstringlist.h>
-#include <tpropertymap.h>
+#include "tbytevector.h"
+#include "tdebug.h"
+#include "tstringlist.h"
+#include "tpropertymap.h"
 
 #include "wavfile.h"
+#include "id3v2tag.h"
+#include "infotag.h"
+#include "tagunion.h"
 
 using namespace TagLib;
 
+namespace
+{
+  enum { ID3v2Index = 0, InfoIndex = 1 };
+}
+
 class RIFF::WAV::File::FilePrivate
 {
 public:
   FilePrivate() :
     properties(0),
-    tag(0),
     tagChunkID("ID3 ")
   {
-
   }
 
   ~FilePrivate()
   {
     delete properties;
-    delete tag;
   }
 
   Properties *properties;
-  ID3v2::Tag *tag;
+  
   ByteVector tagChunkID;
+
+  TagUnion tag;
 };
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -80,28 +86,42 @@ RIFF::WAV::File::~File()
   delete d;
 }
 
-ID3v2::Tag *RIFF::WAV::File::tag() const
+Tag *RIFF::WAV::File::tag() const
 {
-  return d->tag;
+  return &d->tag;
+}
+
+ID3v2::Tag *RIFF::WAV::File::ID3v2Tag() const
+{
+  return d->tag.access<ID3v2::Tag>(ID3v2Index, false);
+}
+
+RIFF::Info::Tag *RIFF::WAV::File::InfoTag() const
+{
+  return d->tag.access<RIFF::Info::Tag>(InfoIndex, false);
 }
 
 PropertyMap RIFF::WAV::File::properties() const
 {
-  return d->tag->properties();
+  return d->tag.properties();
 }
 
 PropertyMap RIFF::WAV::File::setProperties(const PropertyMap &properties)
 {
-  return d->tag->setProperties(properties);
+  return d->tag.setProperties(properties);
 }
 
-
 RIFF::WAV::Properties *RIFF::WAV::File::audioProperties() const
 {
   return d->properties;
 }
 
 bool RIFF::WAV::File::save()
+{
+  return RIFF::WAV::File::save(AllTags);
+}
+
+bool RIFF::WAV::File::save(TagTypes tags, bool stripOthers, int id3v2Version)
 {
   if(readOnly()) {
     debug("RIFF::WAV::File::save() -- File is read only.");
@@ -113,7 +133,25 @@ bool RIFF::WAV::File::save()
     return false;
   }
 
-  setChunkData(d->tagChunkID, d->tag->render());
+  if(stripOthers)
+    strip(static_cast<TagTypes>(AllTags & ~tags));
+
+  ID3v2::Tag *id3v2tag = d->tag.access<ID3v2::Tag>(ID3v2Index, false);
+  if(!id3v2tag->isEmpty()) {
+    if(tags & ID3v2)
+      setChunkData(d->tagChunkID, id3v2tag->render(id3v2Version));
+  }
+
+  Info::Tag *infotag = d->tag.access<Info::Tag>(InfoIndex, false);
+  if(!infotag->isEmpty()) {
+    if(tags & Info) {
+      int chunkId = findInfoTagChunk();
+      if(chunkId != -1)
+        setChunkData(chunkId, infotag->render());
+      else
+        setChunkData("LIST", infotag->render(), true);
+    }
+  }
 
   return true;
 }
@@ -127,19 +165,53 @@ void RIFF::WAV::File::read(bool readProperties, Properties::ReadStyle properties
   ByteVector formatData;
   uint streamLength = 0;
   for(uint i = 0; i < chunkCount(); i++) {
-    if(chunkName(i) == "ID3 " || chunkName(i) == "id3 ") {
+    String name = chunkName(i);
+    if(name == "ID3 " || name == "id3 ") {
       d->tagChunkID = chunkName(i);
-      d->tag = new ID3v2::Tag(this, chunkOffset(i));
+      d->tag.set(ID3v2Index, new ID3v2::Tag(this, chunkOffset(i)));
     }
-    else if(chunkName(i) == "fmt " && readProperties)
+    else if(name == "fmt " && readProperties)
       formatData = chunkData(i);
-    else if(chunkName(i) == "data" && readProperties)
+    else if(name == "data" && readProperties)
       streamLength = chunkDataSize(i);
+    else if(name == "LIST") {
+      ByteVector data = chunkData(i);
+      ByteVector type = data.mid(0, 4);
+
+      if(type == "INFO")
+        d->tag.set(InfoIndex, new RIFF::Info::Tag(data));
+    }
   }
 
+  if (!d->tag[ID3v2Index])
+    d->tag.set(ID3v2Index, new ID3v2::Tag);
+
+  if (!d->tag[InfoIndex])
+    d->tag.set(InfoIndex, new RIFF::Info::Tag);
+
   if(!formatData.isEmpty())
     d->properties = new Properties(formatData, streamLength, propertiesStyle);
+}
 
-  if(!d->tag)
-    d->tag = new ID3v2::Tag;
+void RIFF::WAV::File::strip(TagTypes tags)
+{
+  if(tags & ID3v2)
+    removeChunk(d->tagChunkID);
+
+  if(tags & Info){
+    uint chunkId = findInfoTagChunk();
+    if(chunkId != -1)
+      removeChunk(chunkId);
+  }
+}
+
+uint RIFF::WAV::File::findInfoTagChunk()
+{
+  for(uint i = 0; i < chunkCount(); ++i) {
+    if(chunkName(i) == "LIST" && chunkData(i).mid(0, 4) == "INFO") {
+      return i;
+    }
+  }
+  
+  return -1;
 }
index 1c470870769ab5186c4fd37b27aaa04301a98809..c0fa122ef43573c83b38d78cd05a3bfc03f6e4cb 100644 (file)
@@ -28,6 +28,7 @@
 
 #include "rifffile.h"
 #include "id3v2tag.h"
+#include "infotag.h"
 #include "wavproperties.h"
 
 namespace TagLib {
@@ -57,6 +58,17 @@ namespace TagLib {
       class TAGLIB_EXPORT File : public TagLib::RIFF::File
       {
       public:
+        enum TagTypes {
+          //! Empty set.  Matches no tag types.
+          NoTags  = 0x0000,
+          //! Matches ID3v2 tags.
+          ID3v2   = 0x0001,
+          //! Matches Info tags.
+          Info    = 0x0002,
+          //! Matches all tag types.
+          AllTags = 0xffff
+        };
+
         /*!
          * Contructs an WAV file from \a file.  If \a readProperties is true the
          * file's audio properties will also be read using \a propertiesStyle.  If
@@ -84,7 +96,11 @@ namespace TagLib {
         /*!
          * Returns the Tag for this file.
          */
-        virtual ID3v2::Tag *tag() const;
+        virtual Tag *tag() const;
+
+        ID3v2::Tag *ID3v2Tag() const;
+
+        Info::Tag *InfoTag() const;
 
         /*!
          * Implements the unified property interface -- export function.
@@ -109,12 +125,21 @@ namespace TagLib {
          */
         virtual bool save();
 
+        bool save(TagTypes tags, bool stripOthers = true, int id3v2Version = 4);
+
       private:
         File(const File &);
         File &operator=(const File &);
 
         void read(bool readProperties, Properties::ReadStyle propertiesStyle);
 
+        void strip(TagTypes tags);
+
+        /*!
+         * Returns the index of the chunk that its name is "LIST" and list type is "INFO".
+         */
+        uint findInfoTagChunk();
+
         class FilePrivate;
         FilePrivate *d;
       };
index 3268fdb43851b85ef2ccfb081c401513022c860f..d0c8a3a507847c7aeefe4220b9bd86bd6d3cd322 100644 (file)
@@ -29,6 +29,7 @@ class TestFileRef : public CppUnit::TestFixture
   CPPUNIT_TEST(testMP4_3);
   CPPUNIT_TEST(testTrueAudio);
   CPPUNIT_TEST(testAPE);
+  CPPUNIT_TEST(testWav);
   CPPUNIT_TEST_SUITE_END();
 
 public:
@@ -127,6 +128,11 @@ public:
     fileRefSave("no-tags", ".3g2");
   }
 
+  void testWav()
+  {
+    fileRefSave("empty", ".wav");
+  }
+
   void testOGA_FLAC()
   {
       FileRef *f = new FileRef(TEST_FILE_PATH_C("empty_flac.oga"));
@@ -143,7 +149,7 @@ public:
 
   void testAPE()
   {
-    fileRefSave("mac-399.ape", ".ape");
+    fileRefSave("mac-399", ".ape");
   }
 };
 
diff --git a/tests/test_info.cpp b/tests/test_info.cpp
new file mode 100644 (file)
index 0000000..f76fd67
--- /dev/null
@@ -0,0 +1,45 @@
+#include <cppunit/extensions/HelperMacros.h>
+#include <string>
+#include <stdio.h>
+#include <infotag.h>
+#include <tpropertymap.h>
+#include <tdebug.h>
+#include "utils.h"
+
+using namespace std;
+using namespace TagLib;
+
+class TestInfoTag : public CppUnit::TestFixture
+{
+  CPPUNIT_TEST_SUITE(TestInfoTag);
+  CPPUNIT_TEST(testTitle);
+  CPPUNIT_TEST(testNumericFields);
+  CPPUNIT_TEST_SUITE_END();
+
+public:
+  void testTitle()
+  {
+    RIFF::Info::Tag tag;
+
+    CPPUNIT_ASSERT_EQUAL(String(""), tag.title());
+    tag.setTitle("Test title 1");
+    CPPUNIT_ASSERT_EQUAL(String("Test title 1"), tag.title());
+  }
+
+  void testNumericFields()
+  {
+    RIFF::Info::Tag tag;
+
+    CPPUNIT_ASSERT_EQUAL((uint)0, tag.track());
+    tag.setTrack(1234);
+    CPPUNIT_ASSERT_EQUAL((uint)1234, tag.track());
+    CPPUNIT_ASSERT_EQUAL(String("1234"), tag.fieldText("IPRT"));
+
+    CPPUNIT_ASSERT_EQUAL((uint)0, tag.year());
+    tag.setYear(1234);
+    CPPUNIT_ASSERT_EQUAL((uint)1234, tag.year());
+    CPPUNIT_ASSERT_EQUAL(String("1234"), tag.fieldText("ICRD"));
+  }
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(TestInfoTag);