From 39d1d682372ba292467c44f935e9c264305fc6c0 Mon Sep 17 00:00:00 2001 From: "Stephen F. Booth" Date: Mon, 25 Sep 2023 21:38:46 +0200 Subject: [PATCH] DSD Stream File (DSF) support --- taglib/CMakeLists.txt | 9 ++ taglib/dsf/dsffile.cpp | 213 +++++++++++++++++++++++++++++++++++ taglib/dsf/dsffile.h | 117 +++++++++++++++++++ taglib/dsf/dsfproperties.cpp | 133 ++++++++++++++++++++++ taglib/dsf/dsfproperties.h | 71 ++++++++++++ taglib/fileref.cpp | 8 ++ tests/CMakeLists.txt | 2 + tests/data/empty10ms.dsf | Bin 0 -> 8284 bytes tests/test_dsf.cpp | 57 ++++++++++ tests/test_fileref.cpp | 8 ++ tests/test_sizes.cpp | 4 + 11 files changed, 622 insertions(+) create mode 100644 taglib/dsf/dsffile.cpp create mode 100644 taglib/dsf/dsffile.h create mode 100644 taglib/dsf/dsfproperties.cpp create mode 100644 taglib/dsf/dsfproperties.h create mode 100644 tests/data/empty10ms.dsf create mode 100644 tests/test_dsf.cpp diff --git a/taglib/CMakeLists.txt b/taglib/CMakeLists.txt index f4afbcf8..480366d3 100644 --- a/taglib/CMakeLists.txt +++ b/taglib/CMakeLists.txt @@ -24,6 +24,7 @@ include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/s3m ${CMAKE_CURRENT_SOURCE_DIR}/it ${CMAKE_CURRENT_SOURCE_DIR}/xm + ${CMAKE_CURRENT_SOURCE_DIR}/dsf ) set(tag_HDRS @@ -131,6 +132,8 @@ set(tag_HDRS s3m/s3mproperties.h xm/xmfile.h xm/xmproperties.h + dsf/dsffile.h + dsf/dsfproperties.h ) set(mpeg_SRCS @@ -286,6 +289,11 @@ set(xm_SRCS xm/xmproperties.cpp ) +set(dsf_SRCS + dsf/dsffile.cpp + dsf/dsfproperties.cpp +) + set(toolkit_SRCS toolkit/tstring.cpp toolkit/tstringlist.cpp @@ -306,6 +314,7 @@ set(tag_LIB_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} + ${dsf_SRCS} tag.cpp tagunion.cpp fileref.cpp diff --git a/taglib/dsf/dsffile.cpp b/taglib/dsf/dsffile.cpp new file mode 100644 index 00000000..31b4256c --- /dev/null +++ b/taglib/dsf/dsffile.cpp @@ -0,0 +1,213 @@ +/*************************************************************************** + copyright : (C) 2013-2023 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 "dsffile.h" + +#include "tdebug.h" +#include "tpropertymap.h" +#include "tagutils.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() = default; + ~FilePrivate() = default; + + FilePrivate(const FilePrivate &) = delete; + FilePrivate &operator=(const FilePrivate &) = delete; + + long long fileSize = 0; + long long metadataOffset = 0; + std::unique_ptr properties; + std::unique_ptr tag; +}; + +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 "); +} + +DSF::File::File(FileName file, bool readProperties, + AudioProperties::ReadStyle propertiesStyle) : + TagLib::File(file), + d(std::make_unique()) +{ + if(isOpen()) + read(propertiesStyle); +} + +DSF::File::File(IOStream *stream, bool readProperties, + AudioProperties::ReadStyle propertiesStyle) : + TagLib::File(stream), + d(std::make_unique()) +{ + if(isOpen()) + read(propertiesStyle); +} + +DSF::File::~File() = default; + +ID3v2::Tag *DSF::File::tag() const +{ + return d->tag.get(); +} + +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.get(); +} + +bool DSF::File::save() +{ + if(readOnly()) { + debug("DSF::File::save() - Cannot save to a read only 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(oldTagSize)); + } + + return true; +} + +void DSF::File::read(AudioProperties::ReadStyle propertiesStyle) +{ + if(!isOpen()) + return; + + // 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(chunkSize != 28) { + 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 = std::make_unique(readBlock(chunkSize), propertiesStyle); + + // Skip the data chunk + + // A metadata offset of 0 indicates the absence of an ID3v2 tag + if(d->metadataOffset == 0) + d->tag = std::make_unique(); + else + d->tag = std::make_unique(this, d->metadataOffset); +} diff --git a/taglib/dsf/dsffile.h b/taglib/dsf/dsffile.h new file mode 100644 index 00000000..3203dd51 --- /dev/null +++ b/taglib/dsf/dsffile.h @@ -0,0 +1,117 @@ +/*************************************************************************** + copyright : (C) 2013-2023 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 + +#include "taglib_export.h" +#include "tfile.h" + +#include "dsfproperties.h" + +#include "id3v2tag.h" + +namespace TagLib { + namespace DSF { + class TAGLIB_EXPORT File : public TagLib::File { + public: + /*! + * Constructs a DSD stream file from \a file. + * + * \note In the current implementation, both \a readProperties and + * \a propertiesStyle are ignored. The audio properties are always + * read. + */ + File(FileName file, bool readProperties = true, + AudioProperties::ReadStyle propertiesStyle = + AudioProperties::Average); + + /*! + * Constructs a DSD stream file from \a stream. + * + * \note In the current implementation, both \a readProperties and + * \a propertiesStyle are ignored. The audio properties are always + * read. + * + * \note TagLib will *not* take ownership of the stream, the caller is + * responsible for deleting it after the File object. + */ + File(IOStream *stream, bool readProperties = true, + AudioProperties::ReadStyle propertiesStyle = + AudioProperties::Average); + + /*! + * Destroys this instance of the File. + */ + ~File() override; + + ID3v2::Tag *tag() const override; + + /*! + * Implements the unified property interface -- export function. + * Forwards to ID3v2::Tag::properties(). + */ + PropertyMap properties() const override; + + /*! + * Implements the unified property interface -- import function. + * Forwards to ID3v2::Tag::setProperties(). + */ + PropertyMap setProperties(const PropertyMap &) override; + + /*! + * Returns the DSF::Properties for this file. If no audio properties + * were read then this will return a null pointer. + */ + Properties *audioProperties() const override; + + /*! + * Save the file. + * + * This returns true if the save was successful. + */ + bool save() override; + + /*! + * 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: + void read(AudioProperties::ReadStyle propertiesStyle); + + class FilePrivate; + std::unique_ptr d; + }; + } // namespace DSF +} // namespace TagLib + +#endif diff --git a/taglib/dsf/dsfproperties.cpp b/taglib/dsf/dsfproperties.cpp new file mode 100644 index 00000000..1ce76086 --- /dev/null +++ b/taglib/dsf/dsfproperties.cpp @@ -0,0 +1,133 @@ +/*************************************************************************** + copyright : (C) 2013-2023 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 "dsfproperties.h" + +using namespace TagLib; + +class DSF::Properties::PropertiesPrivate +{ +public: + PropertiesPrivate() = default; + ~PropertiesPrivate() = default; + + PropertiesPrivate(const PropertiesPrivate &) = delete; + PropertiesPrivate &operator=(const PropertiesPrivate &) = delete; + + // Nomenclature is from DSF file format specification + unsigned int formatVersion = 0; + unsigned int formatID = 0; + unsigned int channelType = 0; + unsigned int channelNum = 0; + unsigned int samplingFrequency = 0; + unsigned int bitsPerSample = 0; + long long sampleCount = 0; + unsigned int blockSizePerChannel = 0; + + // Computed + unsigned int bitrate = 0; + unsigned int length = 0; +}; + +DSF::Properties::Properties(const ByteVector &data, ReadStyle style) : + AudioProperties(style), + d(std::make_unique()) +{ + read(data); +} + +DSF::Properties::~Properties() = default; + +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; +} + +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((d->samplingFrequency * d->bitsPerSample * d->channelNum) / 1000.0 + 0.5); + d->length + = d->samplingFrequency > 0 ? static_cast(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 index 00000000..3bd977ec --- /dev/null +++ b/taglib/dsf/dsfproperties.h @@ -0,0 +1,71 @@ +/*************************************************************************** + copyright : (C) 2013-2023 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 + +#include "taglib_export.h" +#include "tbytevector.h" +#include "audioproperties.h" + +namespace TagLib { + namespace DSF { + class TAGLIB_EXPORT Properties : public AudioProperties { + public: + Properties(const ByteVector &data, ReadStyle style); + ~Properties() override; + + Properties(const Properties &) = delete; + Properties &operator=(const Properties &) = delete; + + int lengthInMilliseconds() const override; + int bitrate() const override; + int sampleRate() const override; + int channels() const override; + + 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: + void read(const ByteVector &data); + + class PropertiesPrivate; + std::unique_ptr d; + }; + } // namespace DSF +} // namespace TagLib + +#endif diff --git a/taglib/fileref.cpp b/taglib/fileref.cpp index 88ed3b98..b4848fcc 100644 --- a/taglib/fileref.cpp +++ b/taglib/fileref.cpp @@ -52,6 +52,7 @@ #include "wavfile.h" #include "wavpackfile.h" #include "xmfile.h" +#include "dsffile.h" using namespace TagLib; @@ -164,6 +165,8 @@ namespace file = new IT::File(stream, readAudioProperties, audioPropertiesStyle); else if(ext == "XM") file = new XM::File(stream, readAudioProperties, audioPropertiesStyle); + else if(ext == "DSF") + file = new DSF::File(stream, readAudioProperties, audioPropertiesStyle); // if file is not valid, leave it to content-based detection. @@ -211,6 +214,8 @@ namespace file = new RIFF::WAV::File(stream, readAudioProperties, audioPropertiesStyle); else if(APE::File::isSupported(stream)) file = new APE::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. @@ -290,6 +295,8 @@ namespace return new IT::File(fileName, readAudioProperties, audioPropertiesStyle); if(ext == "XM") return new XM::File(fileName, readAudioProperties, audioPropertiesStyle); + if(ext == "DSF") + return new DSF::File(fileName, readAudioProperties, audioPropertiesStyle); return nullptr; } @@ -417,6 +424,7 @@ StringList FileRef::defaultFileExtensions() l.append("s3m"); l.append("it"); l.append("xm"); + l.append("dsf"); return l; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 56cfeb04..481cf0e2 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -24,6 +24,7 @@ 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 ) SET(test_runner_SRCS @@ -66,6 +67,7 @@ SET(test_runner_SRCS test_mpc.cpp test_opus.cpp test_speex.cpp + test_dsf.cpp test_sizes.cpp ) diff --git a/tests/data/empty10ms.dsf b/tests/data/empty10ms.dsf new file mode 100644 index 0000000000000000000000000000000000000000..31a4d7c1b4f0b81575890feced0431d6943a4474 GIT binary patch literal 8284 zcmZvhUu@fEy5?sWQ{dE?i&~I@B-o1yOB$lmEH<{4MN`b;#3LCZYXcE2(nnO}Vv>+O z5={kYj%19D-VBn3C8R(_OFES@nXOe(Ymp`m*d+x^KaYo$u+)l1#2m`JM@W2A&#MYH%_dO_8CRaLW3PPQz|JXsP9_0a6;cJHKT zvmN+#wY#UAqMD9(nO0U4KIoeIfNdRWKUzlIvCpn{GyN4Y(Gok>OEc@3ebek@98+B$ zwguZ>?c(U^v$P@QGZ z@vD~J*U~@Q!`_kM*y-UP+iAfujZ?O}w=z@~95ZXAg?sUj7o4M>-80(nFE|DtA2OP7 zxM!KEN4tiRif61|N@NC0FH)+>YR6lL>|t#8hb2bj)zqbrE#=x%*50aGTPgbcYB@Hq zrjoI_-JOGSGRDr8FVk~09hl?zSh@M+y{FMYW9eWbS@FO4hwAg9?57oyo|u!TniHS- zn+JEu(!<8AQp{J2xLow(>m-KH`~PioWZnN{vtE<@kqK=4{FwYj5uxjK2`x=y=W(3K zM+=@`k4?-_MHJ7G2!>O|V2C=0Q5UgD4ULRO9u&iQAK}SGrXwhED_9HWrwb?%B(GDR z{N|ePExh0x4b@S!faFkbNVjkMxZkzp@KJ5M2WgDf?)ZY zFCw9Y6b+J8-AhtZ(dVb~5_%pFQBbEVNcs$JQTr z+KY*n=`h<)PaK+wzBRD4j-FT^La2z{qd~T#pPC(0xSRMN3(L-F+h`?P1K8@Y@rSS3 zyPuxvL*rAiJJ6lQ)#2-Qw&R$c?CL^iUtG>?cha3itMj_O*y`(jaaHWZp^876%L!+& z67LK|aofrEU!RymvwxIvUiOTkz6jB3EIOHf`_*^0HZU`N_O7_l%0htX;(pIeYw>}# zXJkZSVbE*areT|hnyK4HpxZN9K~U4XHsdVaGmlS}w03Iv^~ooiy>)1BnTmEzV>Dyy zSfO|6T{aN6w-lqwGMZg9ma2!}9;9OSl;Ibu$qHSvqU(Xc9J9{NHIhp68A;z(uuF5C zqEMxSXVmzM;`oOZV*SaKOjc)0kxEo159&Bak)M(HSAM@t;l%pv72+$2^7|XLV)Wq^ z5|3aQ&TaazF>K5q3gIM~mx{={1V+KW^5yd$Ec7rR#5X(`Y(YQlLQg6D=S^Q28zFGS zU-RWr5_ajD6!b=NVFdFK(Hqj=jpd{-!*5}=`?WQ1E-azmT#yJ$KgaO!!*G4gCxwyR z?A78%V@AnwuuB7lK}Vv;fieIl6Iw1oPnaV9i%slctMk@lDv_ z8$l}gUl%>`yLS!U)gt1>tFG--Xk#2Ag_Zo}H5kIIKBI!+4~ zYq45PwD`;aHEtX)sq~|`b@j(BZLh}*hX%xm7*A-bW{QTLv9#=wrfGtpb=$qRW{A$U zg;ql7@2w>Iy^OZs7KbMwB*s}UBP`q<7-C=RXSHsoWBe(5W(uuWy52f#YppZ8-%D%{ z(?i4gYwy$?8vRUHTOMY+=JHuKzTNt3JN{88y*x-eFU^6`QMU*BYNi$6?uwnh9{+dy zREKA?5HP1%Cp!d<`7zyk*%MotuFw@hhjeGfv9*>M-xgcq;J1tWnz(B8PqYEMEe(*f`eor$?f@(64cI}(H z2E@uIm*d(=`B-U}t>4peMq{3)Y$awcRSuPuwWF}sv!yD-9PaE?t9&_7I{r2qpab+( zn&gyy8b4^d$xkC47vkop`6p~ST0A%hY^ z1|zH!yeRwxFk*cqa$NDL3v8kL?}-dkrFEV z{QeM08v%RQ<7P zJbt7m;!dh%TDs=kblPcA?C%+>&@$C{M|%ld(6PnNistBvmM+{qGk@FH2j;!*D>L2K zoh;nm?wb3CV|35jna;q3q)FRPv|hHmnv>`_hI3{+EBjh&nC_=RLi&rXp}GIEomuXH z(rDe4uDBY9A`l(@Z2WE~(;Bw_w9;R3Ob91^pbxY-6ji&g45M!h>_p-tp`-S*qGQGf z`k8HO4s%x!I*E+X)kQJkXht@XaN5Uh>z<)zRAG2CBP1Y&6fHwLKI!!oOA9Ork9X~p zn}&H)++)SdA44XprQ$y*22aNiSz0(;;x&d=uU>w7{H$8FcKE9CwNm8*^V&|Ok*owt z0op2F4wUFvtTg^;N0DM6{uEuh@S#dl#hC{E+FRHB+iQkj~; zKbw{75*(ZlBRE!~BtJf0sX@XKB?@HD@6S=NgQUD9)zMNo7m3`e2MHqY#m32^Hy3#u zi{w2xRSUw-%=wz12OkuFm!BqTpbxiD$bXPfF^mwNvCVKjh~$vmbPgTCrTYj`e+L_V zP`i(gVizAEx!?#<{5g^L)&CCL@F4}SCscfAj9Nn;ppbF-d=ffZ*tmhze1-7;x&f&b z6{dn4Yakpq>cQ(*pgOEJEun>J;vz9Be~E+&VTj!PIgAWq4?S3}h|Uo8&9z8$IuwDh z#t1SJEl$guJ`#r^=E;X-J{rl9BQ!XUrAp!nn&q7;o{^Zp6i-;~C=i6)7a z+?;~^l_~$rw|AQ9>OosfmvVDaSr;+mAId(Qg~(E4IVleUxx?)%ng!bm+T@?LWkwmnT_$AJjy1 zvV)%3SsduY%+Oo}I7xIDJHsqQfYx6bXigfmVVDKQcx6HWrT6=0Kim0huluTZX7u&W zu-(_Y>1E+==hW=K0;qt%$#xUY!d=H$h3u{Eq#e^q0Fs6U648 z`xFq0_!lh!lwzLtOvwH8biZebf@8q{tRWiC2_x*ARz`n$t=k68{6V=U?C@-*l1y<) zz5yJ;rjq4kCCOJyWjK5q6{e9amy_it$Cs-Wj%~nKv1$@7K_O&#APHCHB%g#5Tr*aM z#xNT;Uo?CI#O^`9|qYk1I^I%tMvX$A;TOC|#~J zxn#KkwKbqQC|oY{P=6!FHf88f3TiA@Sk@hp9BaV%%jF7uTW-qG8rre-g(g!m7y>ltb_hlQ9e#6!yA445ZjUN47!waoz$`RmXaR~t zjSa30GY(HcLR5v?_zH{%N&rPvWN00x1Zcvo(47Hj(d{9hY*flJSLI<*pfMh5kmU-) zD_jMJ%qLkW;jRHJh&#qAEE^nSGV~Cp5FUpcRp9KsHOB}gxEg$~1nb5Nk{uEPJ1{~IYyNnYLMT>ghsl@@zD$6JhQZXGHbEt;vPpi=DL6==8GC$(2FdwuWri%%+8fg_fylst^YSZnsUQV*~TI zdd%>qs%l!V&BhH4ra=rWsE=u9%j#`GuG>q&l)=#@a#~EI>3^P7W4p==MX{A8XDGaz z3UJ5KNmWs3mZj!=O&HAq#YNd&E+&-AXUexiq#*=7eXNs#%1!9Ua8)HiT!>#90j$>k#DjW$ANTnCE z$2(ie6mu0M>HDeOMojyJd0$|TQnbNcR;_YOZS%Yk5DsCnh27rL-U++MsNxIuo~Sc} zRK_sv)Bx}Iwh93X&J`x&qWAURv? z#9JM$8^7B>YY*N8PP)6&0zb^`4BCH!Byju89;CrWgJ|mxvS+%m?EvsEEGJryDXu2= zH76r%14;Fb({^^amr1OOLMxqcMCha_?wfi-Y(b#MyKHOG5X8Ry-PNuQiK}I4hSk2@ zwR_j%njq>|9Uj0Y=D@kp`(%$1cVmw=R<(Jfe2)>o{{&`Jqc!$WIaX2u6%x5>?66Ux z11itP=AZ8HZVne?<{*3f_D-Neb4umFKe^78o6n|V)rk))jR`JAmYTCk<<`WcOg3hL zUZn`o1aV6sG!`1um@8A(P61tsKj=drQ#SQ7=%2}${a=iUDu z#vYOoFT{qo6fV>eOhSR_gCQx1fhkqo{2Z8FzJbmZ!hjd3-%EJNe7K1I*`=8W;BXPX zymu5U22ngnplE3HEsR1D96|=cy#;Ua{xsqB64!~~#SrpVsBqt#L!_`bgcN;+`{!~< zKAgXCG2+Qj=Y4O7JXrC*bOY)Q)zIshRR25(L?s2I9?~1ip%0|#+=f>g8H-5M1X1@! zbG6O?B2j3F$ldamf)RqGJYzJ9ReU%hho6MyATHPbT%_`Oa{OKSRul*DBbCi4h0!&f zrk>Pi3f3Gq^&LG3B(G^1Qz(+t!tM)rj74+lBQxYF@cU>f|M-|p3hOrvTkZ^Mzahy zlR1jT)r@BKdOfg_K}ZZ8{v8?c^r6O zXIA!u?(k%BXo(#$epgrq)Nq{MX!VY|~eMF1Oso_Mzd`sAGTPVF;WY^A}NPDAZF zgr&XMT^txMZLx0(pX$)2=xA}LEyP<|y3@{R;2i_wbWGD2riQ(CFKb#^5exuBRWqis zdt}1MtGjA8un<>u(2In@3Wrv8*Gg&jP4i^yn!pHqiUHu?Ys9Qn;1PdSwci6`+`{AZ zVflcgFYoSD6^^+L7Ct?1&(b9tsD`^RKQ|xaoWUnF;+yw#)c9@kHt>z& z$LILxb+S~AR>)H2J(v5F<8yOWd=ezw|343&Md!*i<3cCoX-uqqIA!fDLCzacces+a z#emVts14?ju@p}I?(3w*8^7P>4Nhd@mj!|T(M=#AS~UB8;htgJpdK0P{e=OO5Heyy z(?t#3R=}84e8JHzV^Qe$_RO=?LPv))Sl1W2nR_4;->qbvtkyAK9_f9EmEFN_2lk-1 zxc`TRWq6IEC+-dm;QzFvblX2DcXy6sce(I5r1#_8IXW-(7G=x0TWAmq(EkZ(!S%oWg*34$m{{`{SqXDrn8QIh1raeuMq^~1Z0 z<_+}S5LNe4i2Tl1=-U_wLIhrG_~32JL)gAKFERZ4m`13 zu;@jxi;;~m67rBWv>q(}u5gZ=E=b|G@d%0o|BvF((a{nF?k1F)$rYz3B9M=TKt|ww z(S{WDVi*|;MJSTWJ)Drpe1s&yOqMsLhh*g6>M$7xbt<}3m;6uuVFLTg|AcDRr%JQU zC{`*{<8##+`O10|^3=&vlLXSI0F>UNA+>rk^)yP;r6vb}3W!sNmn_H6mFgC}WSJDo zfkP!p3!o>o`HV4@!yQ)pVXJB|*4$G@yY$aS4A{ecx1t{^g7*E%P1Uq}yr!nDBm0C| z5E24dV~MyC|G@RdApH_Fr~1KmRuuNzpd}|dBv%H!gv>6qjQC$Pr=4)%eMfqb9j<_h zyl)J(;mVmg?44S}p17QVeLu)L#(sKr@lzlVt)s3Ezy|1^LR7(9lVPUyDmzH;Yj+b6 z2c1F25jvn9r?vpO*S^>qq<_>~!`I4oA70-K%SmLmq&4^u4#M53?*r&TIMg|75XX}9ljaE~sGMh}60cQD> zOQ$J7OPB8(9H3tr?f@yfxb4DWH7U!0q5y($vl@dNu@ss` z08d@m1st!Gnt-k@+`8-v>=^@qeuLlOFq8p*Sr^yYD$p>r04Q8>+XF`c%mLQ`;G58% z%cgH&)O8TtH=Riydicg3NJ1B(1oYUYcNh}jG1TVLvWw46xJm-b0=&ZaQ1=_$cIg)^ z2pEpb_uw%6)BqF!`UOYhryj!zTm?sjhaS4s$uK`KM={qhC_@RDJ(z-@91DhLQ_y`F zAP<1gvd{yjDmO~5CE_|HP=7^s-2$NJDhK`p;6029Jd{SM3{S&&p|A{HhWdcs!2bO91?p +#include +#include +#include +#include +#include +#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(static_cast(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); diff --git a/tests/test_fileref.cpp b/tests/test_fileref.cpp index 8b62459f..7010a025 100644 --- a/tests/test_fileref.cpp +++ b/tests/test_fileref.cpp @@ -45,6 +45,7 @@ #include "wavpackfile.h" #include "opusfile.h" #include "xmfile.h" +#include "dsffile.h" #include #include "utils.h" @@ -99,6 +100,7 @@ class TestFileRef : public CppUnit::TestFixture CPPUNIT_TEST(testAIFF_2); CPPUNIT_TEST(testWavPack); CPPUNIT_TEST(testOpus); + CPPUNIT_TEST(testDSF); CPPUNIT_TEST(testUnsupported); CPPUNIT_TEST(testCreate); CPPUNIT_TEST(testAudioProperties); @@ -331,6 +333,11 @@ public: fileRefSave("correctness_gain_silent_output", ".opus"); } + void testDSF() + { + fileRefSave("empty10ms",".dsf"); + } + void testUnsupported() { FileRef f1(TEST_FILE_PATH_C("no-extension")); @@ -386,6 +393,7 @@ public: CPPUNIT_ASSERT(extensions.contains("wv")); CPPUNIT_ASSERT(extensions.contains("opus")); CPPUNIT_ASSERT(extensions.contains("xm")); + CPPUNIT_ASSERT(extensions.contains("dsf")); } void testFileResolver() diff --git a/tests/test_sizes.cpp b/tests/test_sizes.cpp index 2598cea3..4a88ba1c 100644 --- a/tests/test_sizes.cpp +++ b/tests/test_sizes.cpp @@ -41,6 +41,8 @@ #include "audioproperties.h" #include "chapterframe.h" #include "commentsframe.h" +#include "dsffile.h" +#include "dsfproperties.h" #include "eventtimingcodesframe.h" #include "fileref.h" #include "flacfile.h" @@ -159,6 +161,8 @@ public: CPPUNIT_ASSERT_EQUAL(classSize(1, true), sizeof(TagLib::ByteVectorStream)); CPPUNIT_ASSERT_EQUAL(classSize(0, true), sizeof(TagLib::DebugListener)); CPPUNIT_ASSERT_EQUAL(classSize(1, true), sizeof(TagLib::FLAC::File)); + CPPUNIT_ASSERT_EQUAL(classSize(1, true), sizeof(TagLib::DSF::File)); + CPPUNIT_ASSERT_EQUAL(classSize(1, true), sizeof(TagLib::DSF::Properties)); CPPUNIT_ASSERT_EQUAL(classSize(0, true), sizeof(TagLib::FLAC::MetadataBlock)); CPPUNIT_ASSERT_EQUAL(classSize(1, true), sizeof(TagLib::FLAC::Picture)); CPPUNIT_ASSERT_EQUAL(classSize(1, true), sizeof(TagLib::FLAC::Properties));