${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
)
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
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
${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
#include "vorbisproperties.h"
#include "wavproperties.h"
#include "wavpackproperties.h"
+#include "dsfproperties.h"
+#include "dsdiffproperties.h"
#include "audioproperties.h"
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);
--- /dev/null
+/***************************************************************************
+ 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;
+}
+
--- /dev/null
+/***************************************************************************
+ 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
+
--- /dev/null
+/***************************************************************************
+ 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);
+}
+
--- /dev/null
+/***************************************************************************
+ 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
+
--- /dev/null
+/***************************************************************************
+ 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;
+}
+
--- /dev/null
+/***************************************************************************
+ 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
+
--- /dev/null
+/***************************************************************************
+ 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);
+}
+
--- /dev/null
+/***************************************************************************
+ 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
+
--- /dev/null
+/***************************************************************************
+ 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;
+}
+
--- /dev/null
+/***************************************************************************
+ 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
+
#include "s3mfile.h"
#include "itfile.h"
#include "xmfile.h"
+#include "dsffile.h"
+#include "dsdifffile.h"
using namespace TagLib;
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;
}
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.
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;
}
l.append("s3m");
l.append("it");
l.append("xm");
+ l.append("dsf");
+ l.append("dff");
+ l.append("dsdiff"); // alias for "dff"
return l;
}
#include "itfile.h"
#include "xmfile.h"
#include "mp4file.h"
+#include "dsffile.h"
+#include "dsdifffile.h"
using namespace TagLib;
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();
}
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);
}
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);
}
${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
test_mpc.cpp
test_opus.cpp
test_speex.cpp
+ test_dsf.cpp
+ test_dsdiff.cpp
)
INCLUDE_DIRECTORIES(${CPPUNIT_INCLUDE_DIR})
--- /dev/null
+#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);
--- /dev/null
+#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);
#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>
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);
{
fileRefSave<RIFF::AIFF::File>("alaw", ".aifc");
}
+
+ void testDSF()
+ {
+ fileRefSave<DSF::File>("empty10ms",".dsf");
+ }
+
+ void testDSDIFF()
+ {
+ fileRefSave<DSDIFF::File>("empty10ms",".dff");
+ }
void testUnsupported()
{