diff --git a/taglib/dsf/dsffile.cpp b/taglib/dsf/dsffile.cpp index 31b4256c..9f4f8c8a 100644 --- a/taglib/dsf/dsffile.cpp +++ b/taglib/dsf/dsffile.cpp @@ -36,12 +36,18 @@ using namespace TagLib; class DSF::File::FilePrivate { public: - FilePrivate() = default; + FilePrivate(ID3v2::FrameFactory *frameFactory) + : ID3v2FrameFactory(frameFactory ? frameFactory + : ID3v2::FrameFactory::instance()) + { + } + ~FilePrivate() = default; FilePrivate(const FilePrivate &) = delete; FilePrivate &operator=(const FilePrivate &) = delete; + const ID3v2::FrameFactory *ID3v2FrameFactory; long long fileSize = 0; long long metadataOffset = 0; std::unique_ptr properties; @@ -56,18 +62,20 @@ bool DSF::File::isSupported(IOStream *stream) } DSF::File::File(FileName file, bool readProperties, - AudioProperties::ReadStyle propertiesStyle) : + AudioProperties::ReadStyle propertiesStyle, + ID3v2::FrameFactory *frameFactory) : TagLib::File(file), - d(std::make_unique()) + d(std::make_unique(frameFactory)) { if(isOpen()) read(propertiesStyle); } DSF::File::File(IOStream *stream, bool readProperties, - AudioProperties::ReadStyle propertiesStyle) : + AudioProperties::ReadStyle propertiesStyle, + ID3v2::FrameFactory *frameFactory) : TagLib::File(stream), - d(std::make_unique()) + d(std::make_unique(frameFactory)) { if(isOpen()) read(propertiesStyle); @@ -96,6 +104,11 @@ DSF::Properties *DSF::File::audioProperties() const } bool DSF::File::save() +{ + return save(ID3v2::v4); +} + +bool DSF::File::save(ID3v2::Version version) { if(readOnly()) { debug("DSF::File::save() - Cannot save to a read only file."); @@ -123,7 +136,7 @@ bool DSF::File::save() truncate(newFileSize); } else { - ByteVector tagData = d->tag->render(); + ByteVector tagData = d->tag->render(version); long long newMetadataOffset = d->metadataOffset ? d->metadataOffset : d->fileSize; long long newFileSize = newMetadataOffset + tagData.size(); @@ -164,19 +177,19 @@ void DSF::File::read(AudioProperties::ReadStyle propertiesStyle) return; } - long long chunkSize = readBlock(8).toLongLong(false); + long long dsdHeaderSize = readBlock(8).toLongLong(false); // Integrity check - if(chunkSize != 28) { - debug("DSF::File::read() -- File is corrupted, wrong chunk size"); + if(dsdHeaderSize != 28) { + debug("DSF::File::read() -- File is corrupted, wrong DSD header size"); setValid(false); return; } d->fileSize = readBlock(8).toLongLong(false); - // File is malformed or corrupted - if(d->fileSize != length()) { + // File is malformed or corrupted, allow trailing garbage + if(d->fileSize > length()) { debug("DSF::File::read() -- File is corrupted wrong length"); setValid(false); return; @@ -199,9 +212,14 @@ void DSF::File::read(AudioProperties::ReadStyle propertiesStyle) return; } - chunkSize = readBlock(8).toLongLong(false); + long long fmtHeaderSize = readBlock(8).toLongLong(false); + if(fmtHeaderSize != 52) { + debug("DSF::File::read() -- File is corrupted, wrong FMT header size"); + setValid(false); + return; + } - d->properties = std::make_unique(readBlock(chunkSize), propertiesStyle); + d->properties = std::make_unique(readBlock(fmtHeaderSize), propertiesStyle); // Skip the data chunk @@ -209,5 +227,6 @@ void DSF::File::read(AudioProperties::ReadStyle propertiesStyle) if(d->metadataOffset == 0) d->tag = std::make_unique(); else - d->tag = std::make_unique(this, d->metadataOffset); + d->tag = std::make_unique(this, d->metadataOffset, + d->ID3v2FrameFactory); } diff --git a/taglib/dsf/dsffile.h b/taglib/dsf/dsffile.h index 3203dd51..b5c7b979 100644 --- a/taglib/dsf/dsffile.h +++ b/taglib/dsf/dsffile.h @@ -45,10 +45,14 @@ namespace TagLib { * \note In the current implementation, both \a readProperties and * \a propertiesStyle are ignored. The audio properties are always * read. + * + * If this file contains and ID3v2 tag the frames will be created using + * \a frameFactory (default if null). */ File(FileName file, bool readProperties = true, AudioProperties::ReadStyle propertiesStyle = - AudioProperties::Average); + AudioProperties::Average, + ID3v2::FrameFactory *frameFactory = nullptr); /*! * Constructs a DSD stream file from \a stream. @@ -57,18 +61,25 @@ namespace TagLib { * \a propertiesStyle are ignored. The audio properties are always * read. * + * If this file contains and ID3v2 tag the frames will be created using + * \a frameFactory (default if null). + * * \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); + AudioProperties::Average, + ID3v2::FrameFactory *frameFactory = nullptr); /*! * Destroys this instance of the File. */ ~File() override; + /*! + * Returns the ID3v2 Tag for this file. + */ ID3v2::Tag *tag() const override; /*! @@ -91,19 +102,26 @@ namespace TagLib { /*! * 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); + /*! + * Save the file. + * + * \a version specifies the ID3v2 version to be used for writing tags. + */ + bool save(ID3v2::Version version); + + /*! + * 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); diff --git a/taglib/dsf/dsfproperties.cpp b/taglib/dsf/dsfproperties.cpp index 1ce76086..678fce2b 100644 --- a/taglib/dsf/dsfproperties.cpp +++ b/taglib/dsf/dsfproperties.cpp @@ -117,17 +117,18 @@ int DSF::Properties::blockSizePerChannel() const 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->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; + 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/tests/test_dsf.cpp b/tests/test_dsf.cpp index afd97843..65e0878e 100644 --- a/tests/test_dsf.cpp +++ b/tests/test_dsf.cpp @@ -1,8 +1,11 @@ #include -#include -#include -#include -#include +#include + +#include "tbytevectorlist.h" +#include "tpropertymap.h" +#include "tag.h" +#include "dsffile.h" +#include "plainfile.h" #include #include "utils.h" @@ -22,7 +25,6 @@ public: { 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()); @@ -41,15 +43,30 @@ public: 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; + { + DSF::File f(newname.c_str()); + CPPUNIT_ASSERT(f.properties().isEmpty()); + CPPUNIT_ASSERT_EQUAL(String(""), f.tag()->artist()); + f.tag()->setArtist("The Artist"); + PropertyMap properties = f.properties(); + properties["ALBUMARTIST"] = StringList("Album Artist"); + f.setProperties(properties); + f.save(); + } - f = new DSF::File(newname.c_str()); - CPPUNIT_ASSERT_EQUAL(String("The Artist"), f->tag()->artist()); - delete f; + { + DSF::File f(newname.c_str()); + CPPUNIT_ASSERT_EQUAL(String("The Artist"), f.tag()->artist()); + CPPUNIT_ASSERT_EQUAL(String("The Artist"), f.properties()["ARTIST"].front()); + CPPUNIT_ASSERT_EQUAL(String("Album Artist"), f.properties()["ALBUMARTIST"].front()); + f.setProperties(PropertyMap()); + f.save(); + } + + // Check if file without tags is same as original empty file + const ByteVector dsfData = PlainFile(TEST_FILE_PATH_C("empty10ms.dsf")).readAll(); + const ByteVector fileData = PlainFile(newname.c_str()).readAll(); + CPPUNIT_ASSERT(dsfData == fileData); } };