Add DSF and DSDIFF file types management (#878)
authorJonas Kvinge <jonas@jkvinge.net>
Sat, 27 Oct 2018 00:45:49 +0000 (02:45 +0200)
committerStephen F. Booth <me@sbooth.org>
Sat, 27 Oct 2018 00:45:49 +0000 (19:45 -0500)
* Add DSF and DSDIFF file types management

* Fixes for some build chains

* unit64_t replaced by unsigned long long, warning fixes

* Remove C++11 extension incompatible with some build chains (enumeration in a nested name specifier)

* Change typedef types (uint, ulong, ...) to standard types
remove BUILD_FRAMEWORK changes from this pull request

* Replace deprecated String::null and ByteVector::null by String() and ByteVector()
Styling update, thanks to FestusHagen

* Restyling

* Restyling to reduce length of excessively long lines

* Add to detectByExtension

* Added `isSupported(IOStream *stream)` to `DSF::File` and `DSDIFF::File`

20 files changed:
taglib/CMakeLists.txt
taglib/audioproperties.cpp
taglib/dsdiff/dsdiffdiintag.cpp [new file with mode: 0644]
taglib/dsdiff/dsdiffdiintag.h [new file with mode: 0644]
taglib/dsdiff/dsdifffile.cpp [new file with mode: 0644]
taglib/dsdiff/dsdifffile.h [new file with mode: 0644]
taglib/dsdiff/dsdiffproperties.cpp [new file with mode: 0644]
taglib/dsdiff/dsdiffproperties.h [new file with mode: 0644]
taglib/dsf/dsffile.cpp [new file with mode: 0644]
taglib/dsf/dsffile.h [new file with mode: 0644]
taglib/dsf/dsfproperties.cpp [new file with mode: 0644]
taglib/dsf/dsfproperties.h [new file with mode: 0644]
taglib/fileref.cpp
taglib/toolkit/tfile.cpp
tests/CMakeLists.txt
tests/data/empty10ms.dff [new file with mode: 0644]
tests/data/empty10ms.dsf [new file with mode: 0644]
tests/test_dsdiff.cpp [new file with mode: 0644]
tests/test_dsf.cpp [new file with mode: 0644]
tests/test_fileref.cpp

index 649390714ddd1526447c7e9c8f2861f8148f8a16..a5cb46266f540b1f19ef78be02dd2ee53692eed2 100644 (file)
@@ -24,6 +24,8 @@ include_directories(
   ${CMAKE_CURRENT_SOURCE_DIR}/s3m
   ${CMAKE_CURRENT_SOURCE_DIR}/it
   ${CMAKE_CURRENT_SOURCE_DIR}/xm
+  ${CMAKE_CURRENT_SOURCE_DIR}/dsf
+  ${CMAKE_CURRENT_SOURCE_DIR}/dsdiff
   ${CMAKE_SOURCE_DIR}/3rdparty
 )
 
@@ -138,6 +140,11 @@ set(tag_HDRS
   s3m/s3mproperties.h
   xm/xmfile.h
   xm/xmproperties.h
+  dsf/dsffile.h
+  dsf/dsfproperties.h
+  dsdiff/dsdifffile.h
+  dsdiff/dsdiffproperties.h
+  dsdiff/dsdiffdiintag.h
 )
 
 set(mpeg_SRCS
@@ -293,6 +300,17 @@ set(xm_SRCS
   xm/xmproperties.cpp
 )
 
+set(dsf_SRCS
+  dsf/dsffile.cpp
+  dsf/dsfproperties.cpp
+)
+
+set(dsdiff_SRCS
+  dsdiff/dsdifffile.cpp
+  dsdiff/dsdiffproperties.cpp
+  dsdiff/dsdiffdiintag.cpp
+)
+
 set(toolkit_SRCS
   toolkit/tstring.cpp
   toolkit/tstringlist.cpp
@@ -324,7 +342,7 @@ set(tag_LIB_SRCS
   ${mpeg_SRCS} ${id3v1_SRCS} ${id3v2_SRCS} ${frames_SRCS} ${ogg_SRCS}
   ${vorbis_SRCS} ${oggflacs_SRCS} ${mpc_SRCS} ${ape_SRCS} ${toolkit_SRCS} ${flacs_SRCS}
   ${wavpack_SRCS} ${speex_SRCS} ${trueaudio_SRCS} ${riff_SRCS} ${aiff_SRCS} ${wav_SRCS}
-  ${asf_SRCS} ${mp4_SRCS} ${mod_SRCS} ${s3m_SRCS} ${it_SRCS} ${xm_SRCS} ${opus_SRCS}
+  ${asf_SRCS} ${mp4_SRCS} ${mod_SRCS} ${s3m_SRCS} ${it_SRCS} ${xm_SRCS} ${opus_SRCS} ${dsf_SRCS} ${dsdiff_SRCS}
   ${zlib_SRCS}
   tag.cpp
   tagunion.cpp
index c29051a648d5fbaa76c70fd770b5f3b198b28e7f..94cddc72b241d8ed45bf6bbcc2e42db5c9649727 100644 (file)
@@ -38,6 +38,8 @@
 #include "vorbisproperties.h"
 #include "wavproperties.h"
 #include "wavpackproperties.h"
+#include "dsfproperties.h"
+#include "dsdiffproperties.h"
 
 #include "audioproperties.h"
 
@@ -73,6 +75,10 @@ using namespace TagLib;
     return dynamic_cast<const Vorbis::Properties*>(this)->function_name();      \
   else if(dynamic_cast<const WavPack::Properties*>(this))                       \
     return dynamic_cast<const WavPack::Properties*>(this)->function_name();     \
+  else if(dynamic_cast<const DSF::Properties*>(this))                           \
+    return dynamic_cast<const DSF::Properties*>(this)->function_name();         \
+  else if(dynamic_cast<const DSDIFF::Properties*>(this))                        \
+    return dynamic_cast<const DSDIFF::Properties*>(this)->function_name();      \
   else                                                                          \
     return (default_value);
 
diff --git a/taglib/dsdiff/dsdiffdiintag.cpp b/taglib/dsdiff/dsdiffdiintag.cpp
new file mode 100644 (file)
index 0000000..c027410
--- /dev/null
@@ -0,0 +1,162 @@
+/***************************************************************************
+ copyright            : (C) 2016 by Damien Plisson, Audirvana
+ email                : damien78@audirvana.com
+ ***************************************************************************/
+
+/***************************************************************************
+ *   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 "dsdiffdiintag.h"
+#include "tstringlist.h"
+#include "tpropertymap.h"
+
+using namespace TagLib;
+using namespace DSDIFF::DIIN;
+
+class DSDIFF::DIIN::Tag::TagPrivate
+{
+public:
+  TagPrivate()
+  {
+  }
+
+  String title;
+  String artist;
+};
+
+DSDIFF::DIIN::Tag::Tag() : TagLib::Tag()
+{
+  d = new TagPrivate;
+}
+
+DSDIFF::DIIN::Tag::~Tag()
+{
+  delete d;
+}
+
+String DSDIFF::DIIN::Tag::title() const
+{
+  return d->title;
+}
+
+String DSDIFF::DIIN::Tag::artist() const
+{
+  return d->artist;
+}
+
+String DSDIFF::DIIN::Tag::album() const
+{
+  return String();
+}
+
+String DSDIFF::DIIN::Tag::comment() const
+{
+  return String();
+}
+
+String DSDIFF::DIIN::Tag::genre() const
+{
+  return String();
+}
+
+unsigned int DSDIFF::DIIN::Tag::year() const
+{
+  return 0;
+}
+
+unsigned int DSDIFF::DIIN::Tag::track() const
+{
+  return 0;
+}
+
+void DSDIFF::DIIN::Tag::setTitle(const String &title)
+{
+  if(title.isNull() || title.isEmpty())
+    d->title = String();
+  else
+    d->title = title;
+}
+
+void DSDIFF::DIIN::Tag::setArtist(const String &artist)
+{
+  if(artist.isNull() || artist.isEmpty())
+    d->artist = String();
+  else
+    d->artist = artist;
+}
+
+void DSDIFF::DIIN::Tag::setAlbum(const String &)
+{
+}
+
+void DSDIFF::DIIN::Tag::setComment(const String &)
+{
+}
+
+void DSDIFF::DIIN::Tag::setGenre(const String &)
+{
+}
+
+void DSDIFF::DIIN::Tag::setYear(unsigned int)
+{
+}
+
+void DSDIFF::DIIN::Tag::setTrack(unsigned int)
+{
+}
+
+PropertyMap DSDIFF::DIIN::Tag::properties() const
+{
+  PropertyMap properties;
+  properties["TITLE"] = d->title;
+  properties["ARTIST"] = d->artist;
+  return properties;
+}
+
+PropertyMap DSDIFF::DIIN::Tag::setProperties(const PropertyMap &origProps)
+{
+  PropertyMap properties(origProps);
+  properties.removeEmpty();
+  StringList oneValueSet;
+
+  if(properties.contains("TITLE")) {
+    d->title = properties["TITLE"].front();
+    oneValueSet.append("TITLE");
+  } else
+    d->title = String();
+
+  if(properties.contains("ARTIST")) {
+    d->artist = properties["ARTIST"].front();
+    oneValueSet.append("ARTIST");
+  } else
+    d->artist = String();
+
+  // for each tag that has been set above, remove the first entry in the corresponding
+  // value list. The others will be returned as unsupported by this format.
+  for(StringList::Iterator it = oneValueSet.begin(); it != oneValueSet.end(); ++it) {
+    if(properties[*it].size() == 1)
+      properties.erase(*it);
+    else
+      properties[*it].erase(properties[*it].begin());
+  }
+
+  return properties;
+}
+
diff --git a/taglib/dsdiff/dsdiffdiintag.h b/taglib/dsdiff/dsdiffdiintag.h
new file mode 100644 (file)
index 0000000..0eff545
--- /dev/null
@@ -0,0 +1,150 @@
+/***************************************************************************
+ copyright            : (C) 2016 by Damien Plisson, Audirvana
+ email                : damien78@audirvana.com
+ ***************************************************************************/
+
+/***************************************************************************
+ *   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_DSDIFFDIINTAG_H
+#define TAGLIB_DSDIFFDIINTAG_H
+
+#include "tag.h"
+
+namespace TagLib {
+
+  namespace DSDIFF {
+
+    namespace DIIN {
+
+      /*!
+       * Tags from the Edited Master Chunk Info
+       *
+       * Only Title and Artist tags are supported
+       */
+      class TAGLIB_EXPORT Tag : public TagLib::Tag
+      {
+      public:
+        Tag();
+        virtual ~Tag();
+
+        /*!
+         * Returns the track name; if no track name is present in the tag
+         * String() will be returned.
+         */
+        String title() const;
+
+        /*!
+         * Returns the artist name; if no artist name is present in the tag
+         * String() will be returned.
+         */
+        String artist() const;
+
+        /*!
+         * Not supported.  Therefore always returns String().
+         */
+        String album() const;
+
+        /*!
+         * Not supported.  Therefore always returns String().
+         */
+        String comment() const;
+
+        /*!
+         * Not supported.  Therefore always returns String().
+         */
+        String genre() const;
+
+        /*!
+         * Not supported.  Therefore always returns 0.
+         */
+        unsigned int year() const;
+
+        /*!
+         * Not supported.  Therefore always returns 0.
+         */
+        unsigned int track() const;
+
+        /*!
+         * Sets the title to \a title.  If \a title is String() then this
+         * value will be cleared.
+         */
+        void setTitle(const String &title);
+
+        /*!
+         * Sets the artist to \a artist.  If \a artist is String() then this
+         * value will be cleared.
+         */
+        void setArtist(const String &artist);
+
+        /*!
+         * Not supported and therefore ignored.
+         */
+        void setAlbum(const String &album);
+
+        /*!
+         * Not supported and therefore ignored.
+         */
+        void setComment(const String &comment);
+
+        /*!
+         * Not supported and therefore ignored.
+         */
+        void setGenre(const String &genre);
+
+        /*!
+         * Not supported and therefore ignored.
+         */
+        void setYear(unsigned int year);
+
+        /*!
+         * Not supported and therefore ignored.
+         */
+        void setTrack(unsigned int track);
+
+        /*!
+         * Implements the unified property interface -- export function.
+         * Since the DIIN tag is very limited, the exported map is as well.
+         */
+        PropertyMap properties() const;
+
+        /*!
+         * Implements the unified property interface -- import function.
+         * Because of the limitations of the DIIN file tag, any tags besides
+         * TITLE and ARTIST, will be
+         * returned. Additionally, if the map contains tags with multiple values,
+         * all but the first will be contained in the returned map of unsupported
+         * properties.
+         */
+        PropertyMap setProperties(const PropertyMap &);
+
+      private:
+        Tag(const Tag &);
+        Tag &operator=(const Tag &);
+
+        class TagPrivate;
+        TagPrivate *d;
+      };
+    }
+  }
+}
+
+#endif
+
diff --git a/taglib/dsdiff/dsdifffile.cpp b/taglib/dsdiff/dsdifffile.cpp
new file mode 100644 (file)
index 0000000..73b2dbb
--- /dev/null
@@ -0,0 +1,812 @@
+/***************************************************************************
+ copyright            : (C) 2016 by Damien Plisson, Audirvana
+ email                : damien78@audirvana.com
+ ***************************************************************************/
+
+/***************************************************************************
+ *   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 <tbytevector.h>
+#include <tdebug.h>
+#include <id3v2tag.h>
+#include <tstringlist.h>
+#include <tpropertymap.h>
+#include <tagutils.h>
+
+#include "tagunion.h"
+#include "dsdifffile.h"
+
+using namespace TagLib;
+
+struct Chunk64
+{
+  ByteVector name;
+  unsigned long long offset;
+  unsigned long long size;
+  char padding;
+};
+
+namespace
+{
+  enum {
+    ID3v2Index = 0,
+    DIINIndex = 1
+  };
+  enum {
+    PROPChunk = 0,
+    DIINChunk = 1
+  };
+}
+
+class DSDIFF::File::FilePrivate
+{
+public:
+  FilePrivate() :
+  endianness(BigEndian),
+  size(0),
+  isID3InPropChunk(false),
+  duplicateID3V2chunkIndex(-1),
+  properties(0),
+  id3v2TagChunkID("ID3 "),
+  hasID3v2(false),
+  hasDiin(false)
+  {
+    childChunkIndex[ID3v2Index] = -1;
+    childChunkIndex[DIINIndex] = -1;
+  }
+
+  ~FilePrivate()
+  {
+    delete properties;
+  }
+
+  Endianness endianness;
+  ByteVector type;
+  unsigned long long size;
+  ByteVector format;
+  std::vector<Chunk64> chunks;
+  std::vector<Chunk64> childChunks[2];
+  int childChunkIndex[2];
+  bool isID3InPropChunk; // Two possibilities can be found: ID3V2 chunk inside PROP chunk or at root level
+  int duplicateID3V2chunkIndex; // 2 ID3 chunks are present. This is then the index of the one in
+                                // PROP chunk that will be removed upon next save to remove duplicates.
+
+  Properties *properties;
+
+  TagUnion tag;
+
+  ByteVector id3v2TagChunkID;
+
+  bool hasID3v2;
+  bool hasDiin;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+// static members
+////////////////////////////////////////////////////////////////////////////////
+
+bool DSDIFF::File::isSupported(IOStream *stream)
+{
+    // A DSDIFF file has to start with "FRM8????????DSD ".
+
+    const ByteVector id = Utils::readHeader(stream, 16, false);
+    return (id.startsWith("FRM8") && id.containsAt("DSD ", 12));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// public members
+////////////////////////////////////////////////////////////////////////////////
+
+DSDIFF::File::File(FileName file, bool readProperties,
+                   Properties::ReadStyle propertiesStyle) : TagLib::File(file)
+{
+  d = new FilePrivate;
+  d->endianness = BigEndian;
+  if(isOpen())
+    read(readProperties, propertiesStyle);
+}
+
+DSDIFF::File::File(IOStream *stream, bool readProperties,
+                   Properties::ReadStyle propertiesStyle) : TagLib::File(stream)
+{
+  d = new FilePrivate;
+  d->endianness = BigEndian;
+  if(isOpen())
+    read(readProperties, propertiesStyle);
+}
+
+DSDIFF::File::~File()
+{
+  delete d;
+}
+
+TagLib::Tag *DSDIFF::File::tag() const
+{
+  return &d->tag;
+}
+
+ID3v2::Tag *DSDIFF::File::ID3v2Tag() const
+{
+  return d->tag.access<ID3v2::Tag>(ID3v2Index, false);
+}
+
+bool DSDIFF::File::hasID3v2Tag() const
+{
+  return d->hasID3v2;
+}
+
+DSDIFF::DIIN::Tag *DSDIFF::File::DIINTag() const
+{
+  return d->tag.access<DSDIFF::DIIN::Tag>(DIINIndex, false);
+}
+
+bool DSDIFF::File::hasDIINTag() const
+{
+  return d->hasDiin;
+}
+
+PropertyMap DSDIFF::File::properties() const
+{
+  if(d->hasID3v2)
+    return d->tag.access<ID3v2::Tag>(ID3v2Index, false)->properties();
+
+  return PropertyMap();
+}
+
+void DSDIFF::File::removeUnsupportedProperties(const StringList &unsupported)
+{
+  if(d->hasID3v2)
+    d->tag.access<ID3v2::Tag>(ID3v2Index, false)->removeUnsupportedProperties(unsupported);
+
+  if(d->hasDiin)
+    d->tag.access<DSDIFF::DIIN::Tag>(DIINIndex, false)->removeUnsupportedProperties(unsupported);
+}
+
+PropertyMap DSDIFF::File::setProperties(const PropertyMap &properties)
+{
+  return d->tag.access<ID3v2::Tag>(ID3v2Index, true)->setProperties(properties);
+}
+
+DSDIFF::Properties *DSDIFF::File::audioProperties() const
+{
+  return d->properties;
+}
+
+bool DSDIFF::File::save()
+{
+  if(readOnly()) {
+    debug("DSDIFF::File::save() -- File is read only.");
+    return false;
+  }
+
+  if(!isValid()) {
+    debug("DSDIFF::File::save() -- Trying to save invalid file.");
+    return false;
+  }
+
+  // First: save ID3V2 chunk
+  ID3v2::Tag *id3v2Tag = d->tag.access<ID3v2::Tag>(ID3v2Index, false);
+  if(d->isID3InPropChunk) {
+    if(id3v2Tag != NULL && !id3v2Tag->isEmpty()) {
+      setChildChunkData(d->id3v2TagChunkID, id3v2Tag->render(), PROPChunk);
+      d->hasID3v2 = true;
+    }
+    else {
+      // Empty tag: remove it
+      setChildChunkData(d->id3v2TagChunkID, ByteVector(), PROPChunk);
+      d->hasID3v2 = false;
+    }
+  }
+  else {
+    if(id3v2Tag != NULL && !id3v2Tag->isEmpty()) {
+      setRootChunkData(d->id3v2TagChunkID, id3v2Tag->render());
+      d->hasID3v2 = true;
+    }
+    else {
+      // Empty tag: remove it
+      setRootChunkData(d->id3v2TagChunkID, ByteVector());
+      d->hasID3v2 = false;
+    }
+  }
+
+  // Second: save the DIIN chunk
+  if(d->hasDiin) {
+    DSDIFF::DIIN::Tag *diinTag = d->tag.access<DSDIFF::DIIN::Tag>(DIINIndex, false);
+
+    if(!diinTag->title().isNull() && !diinTag->title().isEmpty()) {
+      ByteVector diinTitle;
+      diinTitle.append(ByteVector::fromUInt(diinTag->title().size(), d->endianness == BigEndian));
+      diinTitle.append(ByteVector::fromCString(diinTag->title().toCString()));
+      setChildChunkData("DITI", diinTitle, DIINChunk);
+    }
+    else
+      setChildChunkData("DITI", ByteVector(), DIINChunk);
+
+    if(!diinTag->artist().isNull() && !diinTag->artist().isEmpty()) {
+      ByteVector diinArtist;
+      diinArtist.append(ByteVector::fromUInt(diinTag->artist().size(), d->endianness == BigEndian));
+      diinArtist.append(ByteVector::fromCString(diinTag->artist().toCString()));
+      setChildChunkData("DIAR", diinArtist, DIINChunk);
+    }
+    else
+      setChildChunkData("DIAR", ByteVector(), DIINChunk);
+  }
+
+  // Third: remove the duplicate ID3V2 chunk (inside PROP chunk) if any
+  if(d->duplicateID3V2chunkIndex>=0) {
+    setChildChunkData(d->duplicateID3V2chunkIndex, ByteVector(), PROPChunk);
+    d->duplicateID3V2chunkIndex = -1;
+  }
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// private members
+////////////////////////////////////////////////////////////////////////////////
+
+void DSDIFF::File::setRootChunkData(unsigned int i, const ByteVector &data)
+{
+  if(data.isNull() || data.isEmpty()) {
+    // Null data: remove chunk
+    // Update global size
+    unsigned long long removedChunkTotalSize = d->chunks[i].size + d->chunks[i].padding + 12;
+    d->size -= removedChunkTotalSize;
+    insert(ByteVector::fromLongLong(d->size, d->endianness == BigEndian), 4, 8);
+
+    removeBlock(d->chunks[i].offset - 12, removedChunkTotalSize);
+
+    // Update the internal offsets
+    for(unsigned long r = i + 1; r < d->chunks.size(); r++)
+      d->chunks[r].offset = d->chunks[r - 1].offset + 12
+        + d->chunks[r - 1].size + d->chunks[r - 1].padding;
+
+    d->chunks.erase(d->chunks.begin() + i);
+  }
+  else {
+    // Non null data: update chunk
+    // First we update the global size
+    d->size += ((data.size() + 1) & ~1) - (d->chunks[i].size + d->chunks[i].padding);
+    insert(ByteVector::fromLongLong(d->size, d->endianness == BigEndian), 4, 8);
+
+    // Now update the specific chunk
+    writeChunk(d->chunks[i].name,
+               data,
+               d->chunks[i].offset - 12,
+               d->chunks[i].size + d->chunks[i].padding + 12);
+
+    d->chunks[i].size = data.size();
+    d->chunks[i].padding = (data.size() & 0x01) ? 1 : 0;
+
+    // Finally update the internal offsets
+    updateRootChunksStructure(i + 1);
+  }
+}
+
+void DSDIFF::File::setRootChunkData(const ByteVector &name, const ByteVector &data)
+{
+  if(d->chunks.size() == 0) {
+    debug("DSDIFF::File::setPropChunkData - No valid chunks found.");
+    return;
+  }
+
+  for(unsigned int i = 0; i < d->chunks.size(); i++) {
+    if(d->chunks[i].name == name) {
+      setRootChunkData(i, data);
+      return;
+    }
+  }
+
+  // Couldn't find an existing chunk, so let's create a new one.
+  unsigned int i = d->chunks.size() - 1;
+  unsigned long offset = d->chunks[i].offset + d->chunks[i].size + d->chunks[i].padding;
+
+  // First we update the global size
+  d->size += (offset & 1) + ((data.size() + 1) & ~1) + 12;
+  insert(ByteVector::fromLongLong(d->size, d->endianness == BigEndian), 4, 8);
+
+  // Now add the chunk to the file
+  writeChunk(name,
+             data,
+             offset,
+             std::max<unsigned long long>(0, length() - offset),
+             (offset & 1) ? 1 : 0);
+
+  Chunk64 chunk;
+  chunk.name = name;
+  chunk.size = data.size();
+  chunk.offset = offset + 12;
+  chunk.padding = (data.size() & 0x01) ? 1 : 0;
+
+  d->chunks.push_back(chunk);
+}
+
+void DSDIFF::File::setChildChunkData(unsigned int i,
+                                     const ByteVector &data,
+                                     unsigned int childChunkNum)
+{
+  std::vector<Chunk64> &childChunks = d->childChunks[childChunkNum];
+
+  if(data.isNull() || data.isEmpty()) {
+    // Null data: remove chunk
+    // Update global size
+    unsigned long long removedChunkTotalSize = childChunks[i].size + childChunks[i].padding + 12;
+    d->size -= removedChunkTotalSize;
+    insert(ByteVector::fromLongLong(d->size, d->endianness == BigEndian), 4, 8);
+    // Update child chunk size
+    d->chunks[d->childChunkIndex[childChunkNum]].size -= removedChunkTotalSize;
+    insert(ByteVector::fromLongLong(d->chunks[d->childChunkIndex[childChunkNum]].size,
+                                    d->endianness == BigEndian),
+           d->chunks[d->childChunkIndex[childChunkNum]].offset - 8, 8);
+    // Remove the chunk
+    removeBlock(childChunks[i].offset - 12, removedChunkTotalSize);
+
+    // Update the internal offsets
+    // For child chunks
+    if((i + 1) < childChunks.size()) {
+      childChunks[i + 1].offset = childChunks[i].offset;
+      i++;
+      for(i++; i < childChunks.size(); i++)
+        childChunks[i].offset = childChunks[i - 1].offset + 12
+          + childChunks[i - 1].size + childChunks[i - 1].padding;
+    }
+
+    // And for root chunks
+    for(i = d->childChunkIndex[childChunkNum] + 1; i < d->chunks.size(); i++)
+      d->chunks[i].offset = d->chunks[i - 1].offset + 12
+        + d->chunks[i - 1].size + d->chunks[i - 1].padding;
+
+    childChunks.erase(childChunks.begin() + i);
+  }
+  else {
+    // Non null data: update chunk
+    // First we update the global size
+    d->size += ((data.size() + 1) & ~1) - (childChunks[i].size + childChunks[i].padding);
+    insert(ByteVector::fromLongLong(d->size, d->endianness == BigEndian), 4, 8);
+    // And the PROP chunk size
+    d->chunks[d->childChunkIndex[childChunkNum]].size += ((data.size() + 1) & ~1)
+      - (childChunks[i].size + childChunks[i].padding);
+    insert(ByteVector::fromLongLong(d->chunks[d->childChunkIndex[childChunkNum]].size,
+                                    d->endianness == BigEndian),
+           d->chunks[d->childChunkIndex[childChunkNum]].offset - 8, 8);
+
+    // Now update the specific chunk
+    writeChunk(childChunks[i].name,
+               data,
+               childChunks[i].offset - 12,
+               childChunks[i].size + childChunks[i].padding + 12);
+
+    childChunks[i].size = data.size();
+    childChunks[i].padding = (data.size() & 0x01) ? 1 : 0;
+
+    // Now update the internal offsets
+    // For child Chunks
+    for(i++; i < childChunks.size(); i++)
+      childChunks[i].offset = childChunks[i - 1].offset + 12
+      + childChunks[i - 1].size + childChunks[i - 1].padding;
+
+    // And for root chunks
+    updateRootChunksStructure(d->childChunkIndex[childChunkNum] + 1);
+  }
+}
+
+void DSDIFF::File::setChildChunkData(const ByteVector &name,
+                                     const ByteVector &data,
+                                     unsigned int childChunkNum)
+{
+  std::vector<Chunk64> &childChunks = d->childChunks[childChunkNum];
+
+  if(childChunks.size() == 0) {
+    debug("DSDIFF::File::setPropChunkData - No valid chunks found.");
+    return;
+  }
+
+  for(unsigned int i = 0; i < childChunks.size(); i++) {
+    if(childChunks[i].name == name) {
+      setChildChunkData(i, data, childChunkNum);
+      return;
+    }
+  }
+
+  // Do not attempt to remove a non existing chunk
+  if(data.isNull() || data.isEmpty())
+    return;
+
+  // Couldn't find an existing chunk, so let's create a new one.
+  unsigned int i = childChunks.size() - 1;
+  unsigned long offset = childChunks[i].offset + childChunks[i].size + childChunks[i].padding;
+
+  // First we update the global size
+  d->size += (offset & 1) + ((data.size() + 1) & ~1) + 12;
+  insert(ByteVector::fromLongLong(d->size, d->endianness == BigEndian), 4, 8);
+  // And the child chunk size
+  d->chunks[d->childChunkIndex[childChunkNum]].size += (offset & 1)
+    + ((data.size() + 1) & ~1) + 12;
+  insert(ByteVector::fromLongLong(d->chunks[d->childChunkIndex[childChunkNum]].size,
+                                  d->endianness == BigEndian),
+         d->chunks[d->childChunkIndex[childChunkNum]].offset - 8, 8);
+
+  // Now add the chunk to the file
+  unsigned long long nextRootChunkIdx = length();
+  if((d->childChunkIndex[childChunkNum] + 1) < static_cast<int>(d->chunks.size()))
+    nextRootChunkIdx = d->chunks[d->childChunkIndex[childChunkNum] + 1].offset - 12;
+
+  writeChunk(name, data, offset,
+             std::max<unsigned long long>(0, nextRootChunkIdx - offset),
+             (offset & 1) ? 1 : 0);
+
+  // For root chunks
+  updateRootChunksStructure(d->childChunkIndex[childChunkNum] + 1);
+
+  Chunk64 chunk;
+  chunk.name = name;
+  chunk.size = data.size();
+  chunk.offset = offset + 12;
+  chunk.padding = (data.size() & 0x01) ? 1 : 0;
+
+  childChunks.push_back(chunk);
+}
+
+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;
+}
+
+void DSDIFF::File::updateRootChunksStructure(unsigned int startingChunk)
+{
+  for(unsigned int i = startingChunk; i < d->chunks.size(); i++)
+    d->chunks[i].offset = d->chunks[i - 1].offset + 12
+      + d->chunks[i - 1].size + d->chunks[i - 1].padding;
+
+  // Update childchunks structure as well
+  if(d->childChunkIndex[PROPChunk] >= static_cast<int>(startingChunk)) {
+    std::vector<Chunk64> &childChunksToUpdate = d->childChunks[PROPChunk];
+    if(childChunksToUpdate.size() > 0) {
+      childChunksToUpdate[0].offset = d->chunks[d->childChunkIndex[PROPChunk]].offset + 12;
+      for(unsigned int i = 1; i < childChunksToUpdate.size(); i++)
+        childChunksToUpdate[i].offset = childChunksToUpdate[i - 1].offset + 12
+          + childChunksToUpdate[i - 1].size + childChunksToUpdate[i - 1].padding;
+    }
+  }
+  if(d->childChunkIndex[DIINChunk] >= static_cast<int>(startingChunk)) {
+    std::vector<Chunk64> &childChunksToUpdate = d->childChunks[DIINChunk];
+    if(childChunksToUpdate.size() > 0) {
+      childChunksToUpdate[0].offset = d->chunks[d->childChunkIndex[DIINChunk]].offset + 12;
+      for(unsigned int i = 1; i < childChunksToUpdate.size(); i++)
+        childChunksToUpdate[i].offset = childChunksToUpdate[i - 1].offset + 12
+          + childChunksToUpdate[i - 1].size + childChunksToUpdate[i - 1].padding;
+    }
+  }
+}
+
+void DSDIFF::File::read(bool readProperties, Properties::ReadStyle propertiesStyle)
+{
+  bool bigEndian = (d->endianness == BigEndian);
+
+  d->type = readBlock(4);
+  d->size = readBlock(8).toLongLong(bigEndian);
+  d->format = readBlock(4);
+
+  // + 12: chunk header at least, fix for additional junk bytes
+  while(tell() + 12 <= length()) {
+    ByteVector chunkName = readBlock(4);
+    unsigned long long chunkSize = readBlock(8).toLongLong(bigEndian);
+
+    if(!isValidChunkID(chunkName)) {
+      debug("DSDIFF::File::read() -- Chunk '" + chunkName + "' has invalid ID");
+      setValid(false);
+      break;
+    }
+
+    if(static_cast<unsigned long long>(tell()) + chunkSize > static_cast<unsigned long long>(length())) {
+      debug("DSDIFF::File::read() -- Chunk '" + chunkName
+            + "' has invalid size (larger than the file size)");
+      setValid(false);
+      break;
+    }
+
+    Chunk64 chunk;
+    chunk.name = chunkName;
+    chunk.size = chunkSize;
+    chunk.offset = tell();
+
+    seek(chunk.size, Current);
+
+    // Check padding
+    chunk.padding = 0;
+    long uPosNotPadded = tell();
+    if((uPosNotPadded & 0x01) != 0) {
+      ByteVector iByte = readBlock(1);
+      if((iByte.size() != 1) || (iByte[0] != 0))
+        // Not well formed, re-seek
+        seek(uPosNotPadded, Beginning);
+      else
+        chunk.padding = 1;
+    }
+    d->chunks.push_back(chunk);
+  }
+
+  unsigned long long lengthDSDSamplesTimeChannels = 0; // For DSD uncompressed
+  unsigned long long audioDataSizeinBytes = 0; // For computing bitrate
+  unsigned long dstNumFrames = 0; // For DST compressed frames
+  unsigned short dstFrameRate = 0; // For DST compressed frames
+
+  for(unsigned int i = 0; i < d->chunks.size(); i++) {
+    if(d->chunks[i].name == "DSD ") {
+      lengthDSDSamplesTimeChannels = d->chunks[i].size * 8;
+      audioDataSizeinBytes = d->chunks[i].size;
+    }
+    else if(d->chunks[i].name == "DST ") {
+      // Now decode the chunks inside the DST chunk to read the DST Frame Information one
+      long long dstChunkEnd = d->chunks[i].offset + d->chunks[i].size;
+      seek(d->chunks[i].offset);
+
+      audioDataSizeinBytes = d->chunks[i].size;
+
+      while(tell() + 12 <= dstChunkEnd) {
+        ByteVector dstChunkName = readBlock(4);
+        long long dstChunkSize = readBlock(8).toLongLong(bigEndian);
+
+        if(!isValidChunkID(dstChunkName)) {
+          debug("DSDIFF::File::read() -- DST Chunk '" + dstChunkName + "' has invalid ID");
+          setValid(false);
+          break;
+        }
+
+        if(static_cast<long long>(tell()) + dstChunkSize > dstChunkEnd) {
+          debug("DSDIFF::File::read() -- DST Chunk '" + dstChunkName
+                + "' has invalid size (larger than the DST chunk)");
+          setValid(false);
+          break;
+        }
+
+        if(dstChunkName == "FRTE") {
+          // Found the DST frame information chunk
+          dstNumFrames = readBlock(4).toUInt(bigEndian);
+          dstFrameRate = readBlock(2).toUShort(bigEndian);
+          break; // Found the wanted one, no need to look at the others
+        }
+
+        seek(dstChunkSize, Current);
+
+        // Check padding
+        long uPosNotPadded = tell();
+        if((uPosNotPadded & 0x01) != 0) {
+          ByteVector iByte = readBlock(1);
+          if((iByte.size() != 1) || (iByte[0] != 0))
+            // Not well formed, re-seek
+            seek(uPosNotPadded, Beginning);
+        }
+      }
+    }
+    else if(d->chunks[i].name == "PROP") {
+      d->childChunkIndex[PROPChunk] = i;
+      // Now decodes the chunks inside the PROP chunk
+      long long propChunkEnd = d->chunks[i].offset + d->chunks[i].size;
+      seek(d->chunks[i].offset + 4); // +4 to remove the 'SND ' marker at beginning of 'PROP' chunk
+      while(tell() + 12 <= propChunkEnd) {
+        ByteVector propChunkName = readBlock(4);
+        long long propChunkSize = readBlock(8).toLongLong(bigEndian);
+
+        if(!isValidChunkID(propChunkName)) {
+          debug("DSDIFF::File::read() -- PROP Chunk '" + propChunkName + "' has invalid ID");
+          setValid(false);
+          break;
+        }
+
+        if(static_cast<long long>(tell()) + propChunkSize > propChunkEnd) {
+          debug("DSDIFF::File::read() -- PROP Chunk '" + propChunkName
+                + "' has invalid size (larger than the PROP chunk)");
+          setValid(false);
+          break;
+        }
+
+        Chunk64 chunk;
+        chunk.name = propChunkName;
+        chunk.size = propChunkSize;
+        chunk.offset = tell();
+
+        seek(chunk.size, Current);
+
+        // Check padding
+        chunk.padding = 0;
+        long uPosNotPadded = tell();
+        if((uPosNotPadded & 0x01) != 0) {
+          ByteVector iByte = readBlock(1);
+          if((iByte.size() != 1) || (iByte[0] != 0))
+            // Not well formed, re-seek
+            seek(uPosNotPadded, Beginning);
+          else
+            chunk.padding = 1;
+        }
+        d->childChunks[PROPChunk].push_back(chunk);
+      }
+    }
+    else if(d->chunks[i].name == "DIIN") {
+      d->childChunkIndex[DIINChunk] = i;
+      d->hasDiin = true;
+      // Now decode the chunks inside the DIIN chunk
+      long long diinChunkEnd = d->chunks[i].offset + d->chunks[i].size;
+      seek(d->chunks[i].offset);
+
+      while(tell() + 12 <= diinChunkEnd) {
+        ByteVector diinChunkName = readBlock(4);
+        long long diinChunkSize = readBlock(8).toLongLong(bigEndian);
+
+        if(!isValidChunkID(diinChunkName)) {
+          debug("DSDIFF::File::read() -- DIIN Chunk '" + diinChunkName + "' has invalid ID");
+          setValid(false);
+          break;
+        }
+
+        if(static_cast<long long>(tell()) + diinChunkSize > diinChunkEnd) {
+          debug("DSDIFF::File::read() -- DIIN Chunk '" + diinChunkName
+                + "' has invalid size (larger than the DIIN chunk)");
+          setValid(false);
+          break;
+        }
+
+        Chunk64 chunk;
+        chunk.name = diinChunkName;
+        chunk.size = diinChunkSize;
+        chunk.offset = tell();
+
+        seek(chunk.size, Current);
+
+        // Check padding
+        chunk.padding = 0;
+        long uPosNotPadded = tell();
+        if((uPosNotPadded & 0x01) != 0) {
+          ByteVector iByte = readBlock(1);
+          if((iByte.size() != 1) || (iByte[0] != 0))
+            // Not well formed, re-seek
+            seek(uPosNotPadded, Beginning);
+          else
+            chunk.padding = 1;
+        }
+        d->childChunks[DIINChunk].push_back(chunk);
+      }
+    }
+    else if(d->chunks[i].name == "ID3 " || d->chunks[i].name == "id3 ") {
+      d->id3v2TagChunkID = d->chunks[i].name;
+      d->tag.set(ID3v2Index, new ID3v2::Tag(this, d->chunks[i].offset));
+      d->isID3InPropChunk = false;
+      d->hasID3v2 = true;
+    }
+  }
+
+  if(!isValid())
+    return;
+
+  if(d->childChunkIndex[PROPChunk] < 0) {
+    debug("DSDIFF::File::read() -- no PROP chunk found");
+    setValid(false);
+    return;
+  }
+
+  // Read properties
+
+  unsigned int sampleRate=0;
+  unsigned short channels=0;
+
+  for(unsigned int i = 0; i < d->childChunks[PROPChunk].size(); i++) {
+    if(d->childChunks[PROPChunk][i].name == "ID3 " || d->childChunks[PROPChunk][i].name == "id3 ") {
+      if(d->hasID3v2) {
+        d->duplicateID3V2chunkIndex = i;
+        continue; // ID3V2 tag has already been found at root level
+      }
+      d->id3v2TagChunkID = d->childChunks[PROPChunk][i].name;
+      d->tag.set(ID3v2Index, new ID3v2::Tag(this, d->childChunks[PROPChunk][i].offset));
+      d->isID3InPropChunk = true;
+      d->hasID3v2 = true;
+    }
+    else if(d->childChunks[PROPChunk][i].name == "FS  ") {
+      // Sample rate
+      seek(d->childChunks[PROPChunk][i].offset);
+      sampleRate = readBlock(4).toUInt(0, 4, bigEndian);
+    }
+    else if(d->childChunks[PROPChunk][i].name == "CHNL") {
+      // Channels
+      seek(d->childChunks[PROPChunk][i].offset);
+      channels = readBlock(2).toShort(0, bigEndian);
+    }
+  }
+
+  // Read title & artist from DIIN chunk
+  d->tag.access<DSDIFF::DIIN::Tag>(DIINIndex, true);
+
+  if(d->hasDiin) {
+    for(unsigned int i = 0; i < d->childChunks[DIINChunk].size(); i++) {
+      if(d->childChunks[DIINChunk][i].name == "DITI") {
+        seek(d->childChunks[DIINChunk][i].offset);
+        unsigned int titleStrLength = readBlock(4).toUInt(0, 4, bigEndian);
+        if(titleStrLength <= d->childChunks[DIINChunk][i].size) {
+          ByteVector titleStr = readBlock(titleStrLength);
+          d->tag.access<DSDIFF::DIIN::Tag>(DIINIndex, false)->setTitle(titleStr);
+        }
+      }
+      else if(d->childChunks[DIINChunk][i].name == "DIAR") {
+        seek(d->childChunks[DIINChunk][i].offset);
+        unsigned int artistStrLength = readBlock(4).toUInt(0, 4, bigEndian);
+        if(artistStrLength <= d->childChunks[DIINChunk][i].size) {
+          ByteVector artistStr = readBlock(artistStrLength);
+          d->tag.access<DSDIFF::DIIN::Tag>(DIINIndex, false)->setArtist(artistStr);
+        }
+      }
+    }
+  }
+
+  if(readProperties) {
+    if(lengthDSDSamplesTimeChannels == 0) {
+      // DST compressed signal : need to compute length of DSD uncompressed frames
+      if(dstFrameRate > 0)
+        lengthDSDSamplesTimeChannels = (unsigned long long)dstNumFrames
+          * (unsigned long long)sampleRate / (unsigned long long)dstFrameRate;
+      else
+        lengthDSDSamplesTimeChannels = 0;
+    }
+    else {
+      // In DSD uncompressed files, the read number of samples is the total for each channel
+      if(channels > 0)
+        lengthDSDSamplesTimeChannels /= channels;
+    }
+    int bitrate = 0;
+    if(lengthDSDSamplesTimeChannels > 0)
+      bitrate = (audioDataSizeinBytes*8*sampleRate) / lengthDSDSamplesTimeChannels / 1000;
+
+    d->properties = new Properties(sampleRate,
+                                   channels,
+                                   lengthDSDSamplesTimeChannels,
+                                   bitrate,
+                                   propertiesStyle);
+  }
+
+  if(!ID3v2Tag()) {
+    d->tag.access<ID3v2::Tag>(ID3v2Index, true);
+    d->isID3InPropChunk = false; // By default, ID3 chunk is at root level
+    d->hasID3v2 = false;
+  }
+}
+
+void DSDIFF::File::writeChunk(const ByteVector &name, const ByteVector &data,
+                              unsigned long long offset, unsigned long replace,
+                              unsigned int leadingPadding)
+{
+  ByteVector combined;
+  if(leadingPadding)
+    combined.append(ByteVector(leadingPadding, '\x00'));
+
+  combined.append(name);
+  combined.append(ByteVector::fromLongLong(data.size(), d->endianness == BigEndian));
+  combined.append(data);
+  if((data.size() & 0x01) != 0)
+    combined.append('\x00');
+
+  insert(combined, offset, replace);
+}
+
diff --git a/taglib/dsdiff/dsdifffile.h b/taglib/dsdiff/dsdifffile.h
new file mode 100644 (file)
index 0000000..8b88874
--- /dev/null
@@ -0,0 +1,260 @@
+/***************************************************************************
+ copyright            : (C) 2016 by Damien Plisson, Audirvana
+ email                : damien78@audirvana.com
+ ***************************************************************************/
+
+/***************************************************************************
+ *   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_DSDIFFFILE_H
+#define TAGLIB_DSDIFFFILE_H
+
+#include "rifffile.h"
+#include "id3v2tag.h"
+#include "dsdiffproperties.h"
+#include "dsdiffdiintag.h"
+
+namespace TagLib {
+
+  //! An implementation of DSDIFF metadata
+
+  /*!
+   * This is implementation of DSDIFF metadata.
+   *
+   * This supports an ID3v2 tag as well as reading stream from the ID3 RIFF
+   * chunk as well as properties from the file.
+   * Description of the DSDIFF format is available 
+   * at http://dsd-guide.com/sites/default/files/white-papers/DSDIFF_1.5_Spec.pdf
+   * DSDIFF standard does not explictly specify the ID3V2 chunk
+   * It can be found at the root level, but also sometimes inside the PROP chunk
+   * In addition, title and artist info are stored as part of the standard
+   */
+
+  namespace DSDIFF {
+
+    //! An implementation of TagLib::File with DSDIFF specific methods
+
+    /*!
+     * This implements and provides an interface for DSDIFF files to the
+     * TagLib::Tag and TagLib::AudioProperties interfaces by way of implementing
+     * the abstract TagLib::File API as well as providing some additional
+     * information specific to DSDIFF files.
+     */
+
+    class TAGLIB_EXPORT File : public TagLib::File
+    {
+    public:
+      /*!
+       * Constructs an DSDIFF file from \a file.  If \a readProperties is true
+       * the file's audio properties will also be read.
+       *
+       * \note In the current implementation, \a propertiesStyle is ignored.
+       */
+      File(FileName file, bool readProperties = true,
+           Properties::ReadStyle propertiesStyle = Properties::Average);
+
+      /*!
+       * Constructs an DSDIFF file from \a stream.  If \a readProperties is true
+       * the file's audio properties will also be read.
+       *
+       * \note TagLib will *not* take ownership of the stream, the caller is
+       * responsible for deleting it after the File object.
+       *
+       * \note In the current implementation, \a propertiesStyle is ignored.
+       */
+      File(IOStream *stream, bool readProperties = true,
+           Properties::ReadStyle propertiesStyle = Properties::Average);
+
+      /*!
+       * Destroys this instance of the File.
+       */
+      virtual ~File();
+
+      /*!
+       * Returns a pointer to a tag that is the union of the ID3v2 and DIIN
+       * tags. The ID3v2 tag is given priority in reading the information -- if
+       * requested information exists in both the ID3v2 tag and the ID3v1 tag,
+       * the information from the ID3v2 tag will be returned.
+       *
+       * If you would like more granular control over the content of the tags,
+       * with the concession of generality, use the tag-type specific calls.
+       *
+       * \note As this tag is not implemented as an ID3v2 tag or a DIIN tag,
+       * but a union of the two this pointer may not be cast to the specific
+       * tag types.
+       *
+       * \see ID3v2Tag()
+       * \see DIINTag()
+       */
+      virtual Tag *tag() const;
+
+      /*!
+       * Returns the ID3V2 Tag for this file.
+       *
+       * \note This always returns a valid pointer regardless of whether or not
+       * the file on disk has an ID3v2 tag.  Use hasID3v2Tag() to check if the
+       * file on disk actually has an ID3v2 tag.
+       *
+       * \see hasID3v2Tag()
+       */
+      virtual ID3v2::Tag *ID3v2Tag() const;
+
+      /*!
+       * Returns the DSDIFF DIIN Tag for this file
+       *
+       */
+      DSDIFF::DIIN::Tag *DIINTag() const;
+
+      /*!
+       * Implements the unified property interface -- export function.
+       * This method forwards to ID3v2::Tag::properties().
+       */
+      PropertyMap properties() const;
+
+      void removeUnsupportedProperties(const StringList &properties);
+
+      /*!
+       * Implements the unified property interface -- import function.
+       * This method forwards to ID3v2::Tag::setProperties().
+       */
+      PropertyMap setProperties(const PropertyMap &);
+
+      /*!
+       * Returns the AIFF::Properties for this file.  If no audio properties
+       * were read then this will return a null pointer.
+       */
+      virtual Properties *audioProperties() const;
+
+      /*!
+       * Save the file.  If at least one tag -- ID3v1 or DIIN -- exists this
+       * will duplicate its content into the other tag.  This returns true
+       * if saving was successful.
+       *
+       * If neither exists or if both tags are empty, this will strip the tags
+       * from the file.
+       *
+       * This is the same as calling save(AllTags);
+       *
+       * If you would like more granular control over the content of the tags,
+       * with the concession of generality, use paramaterized save call below.
+       *
+       * \see save(int tags)
+       */
+      virtual bool save();
+
+      /*!
+       * Save the file.  This will attempt to save all of the tag types that are
+       * specified by OR-ing together TagTypes values.  The save() method above
+       * uses AllTags.  This returns true if saving was successful.
+       *
+       * This strips all tags not included in the mask, but does not modify them
+       * in memory, so later calls to save() which make use of these tags will
+       * remain valid.  This also strips empty tags.
+       */
+      bool save(int tags);
+
+      /*!
+       * Returns whether or not the file on disk actually has an ID3v2 tag.
+       *
+       * \see ID3v2Tag()
+       */
+      bool hasID3v2Tag() const;
+
+      /*!
+       * Returns whether or not the file on disk actually has the DSDIFF 
+       * Title & Artist tag.
+       *
+       * \see DIINTag()
+       */
+      bool hasDIINTag() const;
+
+      /*!
+       * Returns whether or not the given \a stream can be opened as a DSDIFF
+       * file.
+       *
+       * \note This method is designed to do a quick check.  The result may
+       * not necessarily be correct.
+       */
+       static bool isSupported(IOStream *stream);
+
+    protected:
+      enum Endianness { BigEndian, LittleEndian };
+
+      File(FileName file, Endianness endianness);
+      File(IOStream *stream, Endianness endianness);
+
+    private:
+      File(const File &);
+      File &operator=(const File &);
+
+      /*!
+       * Sets the data for the the specified chunk at root level to \a data.
+       *
+       * \warning This will update the file immediately.
+       */
+      void setRootChunkData(unsigned int i, const ByteVector &data);
+
+      /*!
+       * Sets the data for the root-level chunk \a name to \a data.
+       * If a root-level chunk with the given name already exists
+       * it will be overwritten, otherwise it will be
+       * created after the existing chunks.
+       *
+       * \warning This will update the file immediately.
+       */
+      void setRootChunkData(const ByteVector &name, const ByteVector &data);
+
+      /*!
+       * Sets the data for the the specified child chunk to \a data.
+       *
+       * If data is null, then remove the chunk
+       *
+       * \warning This will update the file immediately.
+       */
+      void setChildChunkData(unsigned int i, const ByteVector &data,
+                             unsigned int childChunkNum);
+
+      /*!
+       * Sets the data for the child 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 inside child chunk.
+       *
+       * If data is null, then remove the chunks with \a name name
+       *
+       * \warning This will update the file immediately.
+       */
+      void setChildChunkData(const ByteVector &name, const ByteVector &data,
+                             unsigned int childChunkNum);
+
+      void updateRootChunksStructure(unsigned int startingChunk);
+
+      void read(bool readProperties, Properties::ReadStyle propertiesStyle);
+      void writeChunk(const ByteVector &name, const ByteVector &data,
+                      unsigned long long offset, unsigned long replace = 0,
+                      unsigned int leadingPadding = 0);
+
+      class FilePrivate;
+      FilePrivate *d;
+    };
+  }
+}
+
+#endif
+
diff --git a/taglib/dsdiff/dsdiffproperties.cpp b/taglib/dsdiff/dsdiffproperties.cpp
new file mode 100644 (file)
index 0000000..b8d362c
--- /dev/null
@@ -0,0 +1,120 @@
+/***************************************************************************
+ copyright            : (C) 2016 by Damien Plisson, Audirvana
+ email                : damien78@audirvana.com
+ ***************************************************************************/
+
+/***************************************************************************
+ *   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 <tstring.h>
+#include <tdebug.h>
+
+#include "dsdiffproperties.h"
+
+using namespace TagLib;
+
+class DSDIFF::Properties::PropertiesPrivate
+{
+public:
+  PropertiesPrivate() :
+  length(0),
+  bitrate(0),
+  sampleRate(0),
+  channels(0),
+  sampleWidth(0),
+  sampleCount(0)
+  {
+  }
+
+  int length;
+  int bitrate;
+  int sampleRate;
+  int channels;
+  int sampleWidth;
+  unsigned long long sampleCount;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+// public members
+////////////////////////////////////////////////////////////////////////////////
+
+DSDIFF::Properties::Properties(const unsigned int sampleRate,
+                               const unsigned short channels,
+                               const unsigned long long samplesCount,
+                               const int bitrate,
+                               ReadStyle style) : AudioProperties(style)
+{
+  d = new PropertiesPrivate;
+
+  d->channels    = channels;
+  d->sampleCount = samplesCount;
+  d->sampleWidth = 1;
+  d->sampleRate  = sampleRate;
+  d->bitrate     = bitrate;
+  d->length      = d->sampleRate > 0
+    ? static_cast<int>((d->sampleCount * 1000.0) / d->sampleRate + 0.5)
+    : 0;
+}
+
+DSDIFF::Properties::~Properties()
+{
+  delete d;
+}
+
+int DSDIFF::Properties::length() const
+{
+  return lengthInSeconds();
+}
+
+int DSDIFF::Properties::lengthInSeconds() const
+{
+  return d->length / 1000;
+}
+
+int DSDIFF::Properties::lengthInMilliseconds() const
+{
+  return d->length;
+}
+
+int DSDIFF::Properties::bitrate() const
+{
+  return d->bitrate;
+}
+
+int DSDIFF::Properties::sampleRate() const
+{
+  return d->sampleRate;
+}
+
+int DSDIFF::Properties::channels() const
+{
+  return d->channels;
+}
+
+int DSDIFF::Properties::bitsPerSample() const
+{
+  return d->sampleWidth;
+}
+
+long long DSDIFF::Properties::sampleCount() const
+{
+  return d->sampleCount;
+}
+
diff --git a/taglib/dsdiff/dsdiffproperties.h b/taglib/dsdiff/dsdiffproperties.h
new file mode 100644 (file)
index 0000000..6aff039
--- /dev/null
@@ -0,0 +1,83 @@
+/***************************************************************************
+    copyright            : (C) 2016 by Damien Plisson, Audirvana
+    email                : damien78@audirvana.com
+***************************************************************************/
+
+/***************************************************************************
+ *   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_DSDIFFPROPERTIES_H
+#define TAGLIB_DSDIFFPROPERTIES_H
+
+#include "audioproperties.h"
+
+namespace TagLib {
+
+  namespace DSDIFF {
+
+    class File;
+
+    //! An implementation of audio property reading for DSDIFF
+
+    /*!
+     * This reads the data from an DSDIFF stream found in the AudioProperties
+     * API.
+     */
+
+    class TAGLIB_EXPORT Properties : public AudioProperties
+    {
+    public:
+      /*!
+       * Create an instance of DSDIFF::Properties with the data read from the
+       * ByteVector \a data.
+       */
+      Properties(const unsigned int sampleRate, const unsigned short channels,
+                 const unsigned long long samplesCount, const int bitrate,
+                 ReadStyle style);
+
+      /*!
+       * Destroys this DSDIFF::Properties instance.
+       */
+      virtual ~Properties();
+
+      // Reimplementations.
+
+      virtual int length() const;
+      virtual int lengthInSeconds() const;
+      virtual int lengthInMilliseconds() const;
+      virtual int bitrate() const;
+      virtual int sampleRate() const;
+      virtual int channels() const;
+
+      int bitsPerSample() const;
+      long long sampleCount() const;
+
+    private:
+      Properties(const Properties &);
+      Properties &operator=(const Properties &);
+
+      class PropertiesPrivate;
+      PropertiesPrivate *d;
+    };
+  }
+}
+
+#endif
+
diff --git a/taglib/dsf/dsffile.cpp b/taglib/dsf/dsffile.cpp
new file mode 100644 (file)
index 0000000..1b2c240
--- /dev/null
@@ -0,0 +1,241 @@
+/***************************************************************************
+ copyright            : (C) 2013 - 2018 by Stephen F. Booth
+ email                : me@sbooth.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 <tbytevector.h>
+#include <tdebug.h>
+#include <id3v2tag.h>
+#include <tstringlist.h>
+#include <tpropertymap.h>
+#include <tagutils.h>
+
+#include "dsffile.h"
+
+using namespace TagLib;
+
+// The DSF specification is located at http://dsd-guide.com/sites/default/files/white-papers/DSFFileFormatSpec_E.pdf
+
+class DSF::File::FilePrivate
+{
+public:
+  FilePrivate() :
+  properties(0),
+  tag(0)
+  {
+  }
+
+  ~FilePrivate()
+  {
+    delete properties;
+    delete tag;
+  }
+
+  long long fileSize;
+  long long metadataOffset;
+  Properties *properties;
+  ID3v2::Tag *tag;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+// static members
+////////////////////////////////////////////////////////////////////////////////
+
+bool DSF::File::isSupported(IOStream *stream)
+{
+    // A DSF file has to start with "DSD "
+    const ByteVector id = Utils::readHeader(stream, 4, false);
+    return id.startsWith("DSD ");
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// public members
+////////////////////////////////////////////////////////////////////////////////
+
+DSF::File::File(FileName file, bool readProperties,
+                Properties::ReadStyle propertiesStyle) :
+  TagLib::File(file),
+  d(new FilePrivate())
+{
+  if(isOpen())
+    read(readProperties, propertiesStyle);
+}
+
+DSF::File::File(IOStream *stream, bool readProperties,
+                Properties::ReadStyle propertiesStyle) :
+  TagLib::File(stream),
+  d(new FilePrivate())
+{
+  if(isOpen())
+    read(readProperties, propertiesStyle);
+}
+
+DSF::File::~File()
+{
+  delete d;
+}
+
+ID3v2::Tag *DSF::File::tag() const
+{
+  return d->tag;
+}
+
+PropertyMap DSF::File::properties() const
+{
+  return d->tag->properties();
+}
+
+PropertyMap DSF::File::setProperties(const PropertyMap &properties)
+{
+  return d->tag->setProperties(properties);
+}
+
+DSF::Properties *DSF::File::audioProperties() const
+{
+  return d->properties;
+}
+
+bool DSF::File::save()
+{
+  if(readOnly()) {
+    debug("DSF::File::save() -- File is read only.");
+    return false;
+  }
+
+  if(!isValid()) {
+    debug("DSF::File::save() -- Trying to save invalid file.");
+    return false;
+  }
+
+  // Three things must be updated: the file size, the tag data, and the metadata offset
+
+  if(d->tag->isEmpty()) {
+    long long newFileSize = d->metadataOffset ? d->metadataOffset : d->fileSize;
+
+    // Update the file size
+    if(d->fileSize != newFileSize) {
+      insert(ByteVector::fromLongLong(newFileSize, false), 12, 8);
+      d->fileSize = newFileSize;
+    }
+
+    // Update the metadata offset to 0 since there is no longer a tag
+    if(d->metadataOffset) {
+      insert(ByteVector::fromLongLong(0ULL, false), 20, 8);
+      d->metadataOffset = 0;
+    }
+
+    // Delete the old tag
+    truncate(newFileSize);
+  }
+  else {
+    ByteVector tagData = d->tag->render();
+
+    long long newMetadataOffset = d->metadataOffset ? d->metadataOffset : d->fileSize;
+    long long newFileSize = newMetadataOffset + tagData.size();
+    long long oldTagSize = d->fileSize - newMetadataOffset;
+
+    // Update the file size
+    if(d->fileSize != newFileSize) {
+      insert(ByteVector::fromLongLong(newFileSize, false), 12, 8);
+      d->fileSize = newFileSize;
+    }
+
+    // Update the metadata offset
+    if(d->metadataOffset != newMetadataOffset) {
+      insert(ByteVector::fromLongLong(newMetadataOffset, false), 20, 8);
+      d->metadataOffset = newMetadataOffset;
+    }
+
+    // Delete the old tag and write the new one
+    insert(tagData, newMetadataOffset, static_cast<size_t>(oldTagSize));
+  }
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// private members
+////////////////////////////////////////////////////////////////////////////////
+
+
+void DSF::File::read(bool readProperties, Properties::ReadStyle propertiesStyle)
+{
+  // A DSF file consists of four chunks: DSD chunk, format chunk, data chunk, and metadata chunk
+  // The file format is not chunked in the sense of a RIFF File, though
+
+  // DSD chunk
+  ByteVector chunkName = readBlock(4);
+  if(chunkName != "DSD ") {
+    debug("DSF::File::read() -- Not a DSF file.");
+    setValid(false);
+    return;
+  }
+
+  long long chunkSize = readBlock(8).toLongLong(false);
+
+  // Integrity check
+  if(28 != chunkSize) {
+    debug("DSF::File::read() -- File is corrupted, wrong chunk size");
+    setValid(false);
+    return;
+  }
+
+  d->fileSize = readBlock(8).toLongLong(false);
+
+  // File is malformed or corrupted
+  if(d->fileSize != length()) {
+    debug("DSF::File::read() -- File is corrupted wrong length");
+    setValid(false);
+    return;
+  }
+
+  d->metadataOffset = readBlock(8).toLongLong(false);
+
+  // File is malformed or corrupted
+  if(d->metadataOffset > d->fileSize) {
+    debug("DSF::File::read() -- Invalid metadata offset.");
+    setValid(false);
+    return;
+  }
+
+  // Format chunk
+  chunkName = readBlock(4);
+  if(chunkName != "fmt ") {
+    debug("DSF::File::read() -- Missing 'fmt ' chunk.");
+    setValid(false);
+    return;
+  }
+
+  chunkSize = readBlock(8).toLongLong(false);
+
+  d->properties = new Properties(readBlock(chunkSize), propertiesStyle);
+
+  // Skip the data chunk
+
+  // A metadata offset of 0 indicates the absence of an ID3v2 tag
+  if(0 == d->metadataOffset)
+    d->tag = new ID3v2::Tag();
+  else
+    d->tag = new ID3v2::Tag(this, d->metadataOffset);
+}
+
diff --git a/taglib/dsf/dsffile.h b/taglib/dsf/dsffile.h
new file mode 100644 (file)
index 0000000..e75298d
--- /dev/null
@@ -0,0 +1,128 @@
+/***************************************************************************
+    copyright            : (C) 2013 - 2018 by Stephen F. Booth
+    email                : me@sbooth.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_DSFFILE_H
+#define TAGLIB_DSFFILE_H
+
+#include "tfile.h"
+#include "id3v2tag.h"
+#include "dsfproperties.h"
+
+namespace TagLib {
+
+  //! An implementation of DSF metadata
+
+  /*!
+   * This is implementation of DSF metadata.
+   *
+   * This supports an ID3v2 tag as well as properties from the file.
+   */
+
+  namespace DSF {
+
+    //! An implementation of TagLib::File with DSF specific methods
+
+    /*!
+     * This implements and provides an interface for DSF files to the
+     * TagLib::Tag and TagLib::AudioProperties interfaces by way of implementing
+     * the abstract TagLib::File API as well as providing some additional
+     * information specific to DSF files.
+     */
+
+    class TAGLIB_EXPORT File : public TagLib::File
+    {
+    public:
+      /*!
+       * Contructs an DSF file from \a file.  If \a readProperties is true the
+       * file's audio properties will also be read using \a propertiesStyle.  If
+       * false, \a propertiesStyle is ignored.
+       */
+      File(FileName file, bool readProperties = true,
+           Properties::ReadStyle propertiesStyle = Properties::Average);
+
+      /*!
+       * Contructs an DSF file from \a file.  If \a readProperties is true the
+       * file's audio properties will also be read using \a propertiesStyle.  If
+       * false, \a propertiesStyle is ignored.
+       */
+      File(IOStream *stream, bool readProperties = true,
+           Properties::ReadStyle propertiesStyle = Properties::Average);
+
+      /*!
+       * Destroys this instance of the File.
+       */
+      virtual ~File();
+
+      /*!
+       * Returns the Tag for this file.
+       */
+      ID3v2::Tag *tag() const;
+
+      /*!
+       * Implements the unified property interface -- export function.
+       * This method forwards to ID3v2::Tag::properties().
+       */
+      PropertyMap properties() const;
+
+      /*!
+       * Implements the unified property interface -- import function.
+       * This method forwards to ID3v2::Tag::setProperties().
+       */
+      PropertyMap setProperties(const PropertyMap &);
+
+      /*!
+       * Returns the DSF::AudioProperties for this file.  If no audio properties
+       * were read then this will return a null pointer.
+       */
+      virtual Properties *audioProperties() const;
+
+      /*!
+       * Saves the file.
+       */
+      virtual bool save();
+
+      /*!
+       * Returns whether or not the given \a stream can be opened as a DSF
+       * file.
+       *
+       * \note This method is designed to do a quick check.  The result may
+       * not necessarily be correct.
+       */
+      static bool isSupported(IOStream *stream);
+
+    private:
+      File(const File &);
+      File &operator=(const File &);
+
+      void read(bool readProperties, Properties::ReadStyle propertiesStyle);
+
+      class FilePrivate;
+      FilePrivate *d;
+    };
+  }
+}
+
+#endif
+
diff --git a/taglib/dsf/dsfproperties.cpp b/taglib/dsf/dsfproperties.cpp
new file mode 100644 (file)
index 0000000..18511fa
--- /dev/null
@@ -0,0 +1,161 @@
+/***************************************************************************
+ copyright            : (C) 2013 by Stephen F. Booth
+ email                : me@sbooth.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 <tstring.h>
+#include <tdebug.h>
+
+#include "dsfproperties.h"
+
+using namespace TagLib;
+
+class DSF::Properties::PropertiesPrivate
+{
+public:
+  PropertiesPrivate() :
+  formatVersion(0),
+  formatID(0),
+  channelType(0),
+  channelNum(0),
+  samplingFrequency(0),
+  bitsPerSample(0),
+  sampleCount(0),
+  blockSizePerChannel(0),
+  bitrate(0),
+  length(0)
+  {
+  }
+
+  // Nomenclature is from DSF file format specification
+  unsigned int formatVersion;
+  unsigned int formatID;
+  unsigned int channelType;
+  unsigned int channelNum;
+  unsigned int samplingFrequency;
+  unsigned int bitsPerSample;
+  long long sampleCount;
+  unsigned int blockSizePerChannel;
+
+  // Computed
+  unsigned int bitrate;
+  unsigned int length;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+// public members
+////////////////////////////////////////////////////////////////////////////////
+
+DSF::Properties::Properties(const ByteVector &data, ReadStyle style) : TagLib::AudioProperties(style)
+{
+  d = new PropertiesPrivate;
+  read(data);
+}
+
+DSF::Properties::~Properties()
+{
+  delete d;
+}
+
+int DSF::Properties::length() const
+{
+  return lengthInSeconds();
+}
+
+int DSF::Properties::lengthInSeconds() const
+{
+  return d->length / 1000;
+}
+
+int DSF::Properties::lengthInMilliseconds() const
+{
+  return d->length;
+}
+
+int DSF::Properties::bitrate() const
+{
+  return d->bitrate;
+}
+
+int DSF::Properties::sampleRate() const
+{
+  return d->samplingFrequency;
+}
+
+int DSF::Properties::channels() const
+{
+  return d->channelNum;
+}
+
+// DSF specific
+int DSF::Properties::formatVersion() const
+{
+  return d->formatVersion;
+}
+
+int DSF::Properties::formatID() const
+{
+  return d->formatID;
+}
+
+int DSF::Properties::channelType() const
+{
+  return d->channelType;
+}
+
+int DSF::Properties::bitsPerSample() const
+{
+  return d->bitsPerSample;
+}
+
+long long DSF::Properties::sampleCount() const
+{
+  return d->sampleCount;
+}
+
+int DSF::Properties::blockSizePerChannel() const
+{
+  return d->blockSizePerChannel;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// private members
+////////////////////////////////////////////////////////////////////////////////
+
+void DSF::Properties::read(const ByteVector &data)
+{
+  d->formatVersion         = data.toUInt(0U,false);
+  d->formatID              = data.toUInt(4U,false);
+  d->channelType           = data.toUInt(8U,false);
+  d->channelNum            = data.toUInt(12U,false);
+  d->samplingFrequency     = data.toUInt(16U,false);
+  d->bitsPerSample         = data.toUInt(20U,false);
+  d->sampleCount           = data.toLongLong(24U,false);
+  d->blockSizePerChannel   = data.toUInt(32U,false);
+
+  d->bitrate
+  = static_cast<unsigned int>((d->samplingFrequency * d->bitsPerSample * d->channelNum) / 1000.0 + 0.5);
+  d->length
+  = d->samplingFrequency > 0 ? static_cast<unsigned int>(d->sampleCount * 1000.0 / d->samplingFrequency + 0.5) : 0;
+}
+
diff --git a/taglib/dsf/dsfproperties.h b/taglib/dsf/dsfproperties.h
new file mode 100644 (file)
index 0000000..273a9f5
--- /dev/null
@@ -0,0 +1,92 @@
+/***************************************************************************
+    copyright            : (C) 2013 by Stephen F. Booth
+    email                : me@sbooth.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_DSFPROPERTIES_H
+#define TAGLIB_DSFPROPERTIES_H
+
+#include "audioproperties.h"
+
+namespace TagLib {
+
+  namespace DSF {
+
+    class File;
+
+    //! An implementation of audio property reading for DSF
+
+    /*!
+     * This reads the data from a DSF stream found in the AudioProperties
+     * API.
+     */
+
+    class TAGLIB_EXPORT Properties : public TagLib::AudioProperties
+    {
+    public:
+      /*!
+       * Create an instance of DSF::AudioProperties with the data read from the
+       * ByteVector \a data.
+       */
+      Properties(const ByteVector &data, ReadStyle style);
+
+      /*!
+       * Destroys this DSF::AudioProperties instance.
+        */
+      virtual ~Properties();
+
+      // Reimplementations.
+
+      virtual int length() const;
+      virtual int lengthInSeconds() const;
+      virtual int lengthInMilliseconds() const;
+      virtual int bitrate() const;
+      virtual int sampleRate() const;
+      virtual int channels() const;
+
+      int formatVersion() const;
+      int formatID() const;
+
+      /*!
+       * Channel type values: 1 = mono, 2 = stereo, 3 = 3 channels, 
+       * 4 = quad, 5 = 4 channels, 6 = 5 channels, 7 = 5.1 channels
+       */
+      int channelType() const;
+      int bitsPerSample() const;
+      long long sampleCount() const;
+      int blockSizePerChannel() const;
+
+    private:
+      Properties(const Properties &);
+      Properties &operator=(const Properties &);
+
+      void read(const ByteVector &data);
+
+      class PropertiesPrivate;
+      PropertiesPrivate *d;
+    };
+  }
+}
+
+#endif
+
index 935c371bddd83f2513b035c0f096d2a84d192e91..5ec4dc06d6b375dc955baf781aded4fffa2cf6a7 100644 (file)
@@ -52,6 +52,8 @@
 #include "s3mfile.h"
 #include "itfile.h"
 #include "xmfile.h"
+#include "dsffile.h"
+#include "dsdifffile.h"
 
 using namespace TagLib;
 
@@ -135,6 +137,10 @@ namespace
       return new IT::File(stream, readAudioProperties, audioPropertiesStyle);
     if(ext == "XM")
       return new XM::File(stream, readAudioProperties, audioPropertiesStyle);
+    if(ext == "DFF" || ext == "DSDIFF")
+      return new DSDIFF::File(stream, readAudioProperties, audioPropertiesStyle);
+    if(ext == "DSF")
+      return new DSF::File(stream, readAudioProperties, audioPropertiesStyle);
 
     return 0;
   }
@@ -174,6 +180,10 @@ namespace
       file = new RIFF::WAV::File(stream, readAudioProperties, audioPropertiesStyle);
     else if(APE::File::isSupported(stream))
       file = new APE::File(stream, readAudioProperties, audioPropertiesStyle);
+    else if(DSDIFF::File::isSupported(stream))
+      file = new DSDIFF::File(stream, readAudioProperties, audioPropertiesStyle);
+    else if(DSF::File::isSupported(stream))
+      file = new DSF::File(stream, readAudioProperties, audioPropertiesStyle);
 
     // isSupported() only does a quick check, so double check the file here.
 
@@ -255,6 +265,10 @@ namespace
       return new IT::File(fileName, readAudioProperties, audioPropertiesStyle);
     if(ext == "XM")
       return new XM::File(fileName, readAudioProperties, audioPropertiesStyle);
+    if(ext == "DFF" || ext == "DSDIFF")
+      return new DSDIFF::File(fileName, readAudioProperties, audioPropertiesStyle);
+    if(ext == "DSF")
+      return new DSF::File(fileName, readAudioProperties, audioPropertiesStyle);
 
     return 0;
   }
@@ -387,6 +401,9 @@ StringList FileRef::defaultFileExtensions()
   l.append("s3m");
   l.append("it");
   l.append("xm");
+  l.append("dsf");
+  l.append("dff");
+  l.append("dsdiff"); // alias for "dff"
 
   return l;
 }
index aff1684d9c2d6f71abe8a57a7c9922ed93051928..055b0d6d30f48353c4e6c0878a7d16e0feac75c7 100644 (file)
@@ -63,6 +63,8 @@
 #include "itfile.h"
 #include "xmfile.h"
 #include "mp4file.h"
+#include "dsffile.h"
+#include "dsdifffile.h"
 
 using namespace TagLib;
 
@@ -148,6 +150,10 @@ PropertyMap File::properties() const
     return dynamic_cast<const MP4::File* >(this)->properties();
   if(dynamic_cast<const ASF::File* >(this))
     return dynamic_cast<const ASF::File* >(this)->properties();
+  if(dynamic_cast<const DSF::File* >(this))
+    return dynamic_cast<const DSF::File* >(this)->properties();
+  if(dynamic_cast<const DSDIFF::File* >(this))
+    return dynamic_cast<const DSDIFF::File* >(this)->properties();
   return tag()->properties();
 }
 
@@ -177,6 +183,10 @@ void File::removeUnsupportedProperties(const StringList &properties)
     dynamic_cast<MP4::File* >(this)->removeUnsupportedProperties(properties);
   else if(dynamic_cast<ASF::File* >(this))
     dynamic_cast<ASF::File* >(this)->removeUnsupportedProperties(properties);
+  else if(dynamic_cast<DSF::File* >(this))
+    dynamic_cast<DSF::File* >(this)->removeUnsupportedProperties(properties);
+  else if(dynamic_cast<DSDIFF::File* >(this))
+    dynamic_cast<DSDIFF::File* >(this)->removeUnsupportedProperties(properties);
   else
     tag()->removeUnsupportedProperties(properties);
 }
@@ -219,6 +229,10 @@ PropertyMap File::setProperties(const PropertyMap &properties)
     return dynamic_cast<MP4::File* >(this)->setProperties(properties);
   else if(dynamic_cast<ASF::File* >(this))
     return dynamic_cast<ASF::File* >(this)->setProperties(properties);
+  else if(dynamic_cast<DSF::File* >(this))
+    return dynamic_cast<DSF::File* >(this)->setProperties(properties);
+  else if(dynamic_cast<DSDIFF::File* >(this))
+    return dynamic_cast<DSDIFF::File* >(this)->setProperties(properties);
   else
     return tag()->setProperties(properties);
 }
index 0c258828dd20507f34eb48b3aaebcf1f78e6ee75..f3205877d98b3b1c5e41a0bb4036ee4a7b15a1c0 100644 (file)
@@ -24,6 +24,8 @@ INCLUDE_DIRECTORIES(
   ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/s3m
   ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/it
   ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/xm
+  ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/dsf
+  ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/dsdiff
 )
 
 SET(test_runner_SRCS
@@ -66,6 +68,8 @@ SET(test_runner_SRCS
   test_mpc.cpp
   test_opus.cpp
   test_speex.cpp
+  test_dsf.cpp
+  test_dsdiff.cpp
 )
 
 INCLUDE_DIRECTORIES(${CPPUNIT_INCLUDE_DIR})
diff --git a/tests/data/empty10ms.dff b/tests/data/empty10ms.dff
new file mode 100644 (file)
index 0000000..9dc0b9e
Binary files /dev/null and b/tests/data/empty10ms.dff differ
diff --git a/tests/data/empty10ms.dsf b/tests/data/empty10ms.dsf
new file mode 100644 (file)
index 0000000..31a4d7c
Binary files /dev/null and b/tests/data/empty10ms.dsf differ
diff --git a/tests/test_dsdiff.cpp b/tests/test_dsdiff.cpp
new file mode 100644 (file)
index 0000000..0d16af7
--- /dev/null
@@ -0,0 +1,116 @@
+#include <string>
+#include <stdio.h>
+#include <tag.h>
+#include <tbytevectorlist.h>
+#include <dsdifffile.h>
+#include <cppunit/extensions/HelperMacros.h>
+#include "utils.h"
+
+using namespace std;
+using namespace TagLib;
+
+class TestDSDIFF : public CppUnit::TestFixture
+{
+  CPPUNIT_TEST_SUITE(TestDSDIFF);
+  CPPUNIT_TEST(testProperties);
+  CPPUNIT_TEST(testTags);
+  CPPUNIT_TEST(testSaveID3v2);
+  CPPUNIT_TEST(testRepeatedSave);
+  CPPUNIT_TEST_SUITE_END();
+
+public:
+
+  void testProperties()
+  {
+    DSDIFF::File f(TEST_FILE_PATH_C("empty10ms.dff"));
+    CPPUNIT_ASSERT(f.audioProperties());
+    CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->length());
+    CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->lengthInSeconds());
+    CPPUNIT_ASSERT_EQUAL(10, f.audioProperties()->lengthInMilliseconds());
+    CPPUNIT_ASSERT_EQUAL(5644, f.audioProperties()->bitrate());
+    CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels());
+    CPPUNIT_ASSERT_EQUAL(2822400, f.audioProperties()->sampleRate());
+    CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->bitsPerSample());
+    CPPUNIT_ASSERT_EQUAL((long long)28224, f.audioProperties()->sampleCount());
+  }
+
+  void testTags()
+  {
+    ScopedFileCopy copy("empty10ms", ".dff");
+    string newname = copy.fileName();
+
+    DSDIFF::File *f = new DSDIFF::File(newname.c_str());
+    CPPUNIT_ASSERT_EQUAL(String(""), f->tag()->artist());
+    f->tag()->setArtist("The Artist");
+    f->save();
+    delete f;
+
+    f = new DSDIFF::File(newname.c_str());
+    CPPUNIT_ASSERT_EQUAL(String("The Artist"), f->tag()->artist());
+    delete f;
+  }
+  
+  void testSaveID3v2()
+  {
+    ScopedFileCopy copy("empty10ms", ".dff");
+    string newname = copy.fileName();
+    
+    {
+      DSDIFF::File f(newname.c_str());
+      CPPUNIT_ASSERT(!f.hasID3v2Tag());
+      
+      f.tag()->setTitle(L"TitleXXX");
+      f.save();
+      CPPUNIT_ASSERT(f.hasID3v2Tag());
+    }
+    {
+      DSDIFF::File f(newname.c_str());
+      CPPUNIT_ASSERT(f.hasID3v2Tag());
+      CPPUNIT_ASSERT_EQUAL(String(L"TitleXXX"), f.tag()->title());
+      
+      f.tag()->setTitle("");
+      f.save();
+      CPPUNIT_ASSERT(!f.hasID3v2Tag());
+    }
+    {
+      DSDIFF::File f(newname.c_str());
+      CPPUNIT_ASSERT(!f.hasID3v2Tag());
+      f.tag()->setTitle(L"TitleXXX");
+      f.save();
+      CPPUNIT_ASSERT(f.hasID3v2Tag());
+    }
+    {
+      DSDIFF::File f(newname.c_str());
+      CPPUNIT_ASSERT(f.hasID3v2Tag());
+      CPPUNIT_ASSERT_EQUAL(String(L"TitleXXX"), f.tag()->title());
+    }
+  }
+  
+  void testRepeatedSave()
+  {
+    ScopedFileCopy copy("empty10ms", ".dff");
+    string newname = copy.fileName();
+    
+    {
+      DSDIFF::File f(newname.c_str());
+      CPPUNIT_ASSERT_EQUAL(String(""), f.tag()->title());
+      f.tag()->setTitle("NEW TITLE");
+      f.save();
+      CPPUNIT_ASSERT_EQUAL(String("NEW TITLE"), f.tag()->title());
+      f.tag()->setTitle("NEW TITLE 2");
+      f.save();
+      CPPUNIT_ASSERT_EQUAL(String("NEW TITLE 2"), f.tag()->title());
+      CPPUNIT_ASSERT_EQUAL(8252L, f.length());
+      f.save();
+      CPPUNIT_ASSERT_EQUAL(8252L, f.length());
+    }
+    {
+      DSDIFF::File f(newname.c_str());
+      CPPUNIT_ASSERT_EQUAL(String("NEW TITLE 2"), f.tag()->title());
+    }
+  }
+  
+
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(TestDSDIFF);
diff --git a/tests/test_dsf.cpp b/tests/test_dsf.cpp
new file mode 100644 (file)
index 0000000..a096eef
--- /dev/null
@@ -0,0 +1,57 @@
+#include <string>
+#include <stdio.h>
+#include <tag.h>
+#include <tbytevectorlist.h>
+#include <dsffile.h>
+#include <cppunit/extensions/HelperMacros.h>
+#include "utils.h"
+
+using namespace std;
+using namespace TagLib;
+
+class TestDSF : public CppUnit::TestFixture
+{
+  CPPUNIT_TEST_SUITE(TestDSF);
+  CPPUNIT_TEST(testBasic);
+  CPPUNIT_TEST(testTags);
+  CPPUNIT_TEST_SUITE_END();
+
+public:
+
+  void testBasic()
+  {
+    DSF::File f(TEST_FILE_PATH_C("empty10ms.dsf"));
+    CPPUNIT_ASSERT(f.audioProperties());
+    CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->length());
+    CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->lengthInSeconds());
+    CPPUNIT_ASSERT_EQUAL(10, f.audioProperties()->lengthInMilliseconds());
+    CPPUNIT_ASSERT_EQUAL(5645, f.audioProperties()->bitrate());
+    CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels());
+    CPPUNIT_ASSERT_EQUAL(2822400, f.audioProperties()->sampleRate());
+    CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->formatVersion());
+    CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->formatID());
+    CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channelType());
+    CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->bitsPerSample());
+    CPPUNIT_ASSERT_EQUAL((long long)28224, f.audioProperties()->sampleCount());
+    CPPUNIT_ASSERT_EQUAL(4096, f.audioProperties()->blockSizePerChannel());
+  }
+
+  void testTags()
+  {
+    ScopedFileCopy copy("empty10ms", ".dsf");
+    string newname = copy.fileName();
+
+    DSF::File *f = new DSF::File(newname.c_str());
+    CPPUNIT_ASSERT_EQUAL(String(""), f->tag()->artist());
+    f->tag()->setArtist("The Artist");
+    f->save();
+    delete f;
+
+    f = new DSF::File(newname.c_str());
+    CPPUNIT_ASSERT_EQUAL(String("The Artist"), f->tag()->artist());
+    delete f;
+  }
+
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(TestDSF);
index e766a891556ee8761a69b7ae5cdc363eac4428a5..8394b87581dcec89b636fc1e2783d2bc4e7d140d 100644 (file)
@@ -39,6 +39,8 @@
 #include <wavfile.h>
 #include <apefile.h>
 #include <aifffile.h>
+#include <dsffile.h>
+#include <dsdifffile.h>
 #include <tfilestream.h>
 #include <tbytevectorstream.h>
 #include <cppunit/extensions/HelperMacros.h>
@@ -79,6 +81,8 @@ class TestFileRef : public CppUnit::TestFixture
   CPPUNIT_TEST(testWav);
   CPPUNIT_TEST(testAIFF_1);
   CPPUNIT_TEST(testAIFF_2);
+  CPPUNIT_TEST(testDSF);
+  CPPUNIT_TEST(testDSDIFF);
   CPPUNIT_TEST(testUnsupported);
   CPPUNIT_TEST(testCreate);
   CPPUNIT_TEST(testFileResolver);
@@ -288,6 +292,16 @@ public:
   {
     fileRefSave<RIFF::AIFF::File>("alaw", ".aifc");
   }
+  
+  void testDSF()
+  {
+    fileRefSave<DSF::File>("empty10ms",".dsf");
+  }
+  
+  void testDSDIFF()
+  {
+    fileRefSave<DSDIFF::File>("empty10ms",".dff");
+  }
 
   void testUnsupported()
   {