From d71398c953bb80377a861d079e7ab74ab18954f3 Mon Sep 17 00:00:00 2001 From: Jonas Kvinge Date: Sat, 27 Oct 2018 02:45:49 +0200 Subject: [PATCH] Add DSF and DSDIFF file types management (#878) * Add DSF and DSDIFF file types management * Fixes for some build chains * unit64_t replaced by unsigned long long, warning fixes * Remove C++11 extension incompatible with some build chains (enumeration in a nested name specifier) * Change typedef types (uint, ulong, ...) to standard types remove BUILD_FRAMEWORK changes from this pull request * Replace deprecated String::null and ByteVector::null by String() and ByteVector() Styling update, thanks to FestusHagen * Restyling * Restyling to reduce length of excessively long lines * Add to detectByExtension * Added `isSupported(IOStream *stream)` to `DSF::File` and `DSDIFF::File` --- taglib/CMakeLists.txt | 20 +- taglib/audioproperties.cpp | 6 + taglib/dsdiff/dsdiffdiintag.cpp | 162 ++++++ taglib/dsdiff/dsdiffdiintag.h | 150 ++++++ taglib/dsdiff/dsdifffile.cpp | 812 +++++++++++++++++++++++++++++ taglib/dsdiff/dsdifffile.h | 260 +++++++++ taglib/dsdiff/dsdiffproperties.cpp | 120 +++++ taglib/dsdiff/dsdiffproperties.h | 83 +++ taglib/dsf/dsffile.cpp | 241 +++++++++ taglib/dsf/dsffile.h | 128 +++++ taglib/dsf/dsfproperties.cpp | 161 ++++++ taglib/dsf/dsfproperties.h | 92 ++++ taglib/fileref.cpp | 17 + taglib/toolkit/tfile.cpp | 14 + tests/CMakeLists.txt | 4 + tests/data/empty10ms.dff | Bin 0 -> 7186 bytes tests/data/empty10ms.dsf | Bin 0 -> 8284 bytes tests/test_dsdiff.cpp | 116 +++++ tests/test_dsf.cpp | 57 ++ tests/test_fileref.cpp | 14 + 20 files changed, 2456 insertions(+), 1 deletion(-) create mode 100644 taglib/dsdiff/dsdiffdiintag.cpp create mode 100644 taglib/dsdiff/dsdiffdiintag.h create mode 100644 taglib/dsdiff/dsdifffile.cpp create mode 100644 taglib/dsdiff/dsdifffile.h create mode 100644 taglib/dsdiff/dsdiffproperties.cpp create mode 100644 taglib/dsdiff/dsdiffproperties.h 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.dff create mode 100644 tests/data/empty10ms.dsf create mode 100644 tests/test_dsdiff.cpp create mode 100644 tests/test_dsf.cpp diff --git a/taglib/CMakeLists.txt b/taglib/CMakeLists.txt index 64939071..a5cb4626 100644 --- a/taglib/CMakeLists.txt +++ b/taglib/CMakeLists.txt @@ -24,6 +24,8 @@ include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/s3m ${CMAKE_CURRENT_SOURCE_DIR}/it ${CMAKE_CURRENT_SOURCE_DIR}/xm + ${CMAKE_CURRENT_SOURCE_DIR}/dsf + ${CMAKE_CURRENT_SOURCE_DIR}/dsdiff ${CMAKE_SOURCE_DIR}/3rdparty ) @@ -138,6 +140,11 @@ set(tag_HDRS s3m/s3mproperties.h xm/xmfile.h xm/xmproperties.h + dsf/dsffile.h + dsf/dsfproperties.h + dsdiff/dsdifffile.h + dsdiff/dsdiffproperties.h + dsdiff/dsdiffdiintag.h ) set(mpeg_SRCS @@ -293,6 +300,17 @@ set(xm_SRCS xm/xmproperties.cpp ) +set(dsf_SRCS + dsf/dsffile.cpp + dsf/dsfproperties.cpp +) + +set(dsdiff_SRCS + dsdiff/dsdifffile.cpp + dsdiff/dsdiffproperties.cpp + dsdiff/dsdiffdiintag.cpp +) + set(toolkit_SRCS toolkit/tstring.cpp toolkit/tstringlist.cpp @@ -324,7 +342,7 @@ set(tag_LIB_SRCS ${mpeg_SRCS} ${id3v1_SRCS} ${id3v2_SRCS} ${frames_SRCS} ${ogg_SRCS} ${vorbis_SRCS} ${oggflacs_SRCS} ${mpc_SRCS} ${ape_SRCS} ${toolkit_SRCS} ${flacs_SRCS} ${wavpack_SRCS} ${speex_SRCS} ${trueaudio_SRCS} ${riff_SRCS} ${aiff_SRCS} ${wav_SRCS} - ${asf_SRCS} ${mp4_SRCS} ${mod_SRCS} ${s3m_SRCS} ${it_SRCS} ${xm_SRCS} ${opus_SRCS} + ${asf_SRCS} ${mp4_SRCS} ${mod_SRCS} ${s3m_SRCS} ${it_SRCS} ${xm_SRCS} ${opus_SRCS} ${dsf_SRCS} ${dsdiff_SRCS} ${zlib_SRCS} tag.cpp tagunion.cpp diff --git a/taglib/audioproperties.cpp b/taglib/audioproperties.cpp index c29051a6..94cddc72 100644 --- a/taglib/audioproperties.cpp +++ b/taglib/audioproperties.cpp @@ -38,6 +38,8 @@ #include "vorbisproperties.h" #include "wavproperties.h" #include "wavpackproperties.h" +#include "dsfproperties.h" +#include "dsdiffproperties.h" #include "audioproperties.h" @@ -73,6 +75,10 @@ using namespace TagLib; return dynamic_cast(this)->function_name(); \ else if(dynamic_cast(this)) \ return dynamic_cast(this)->function_name(); \ + else if(dynamic_cast(this)) \ + return dynamic_cast(this)->function_name(); \ + else if(dynamic_cast(this)) \ + return dynamic_cast(this)->function_name(); \ else \ return (default_value); diff --git a/taglib/dsdiff/dsdiffdiintag.cpp b/taglib/dsdiff/dsdiffdiintag.cpp new file mode 100644 index 00000000..c027410a --- /dev/null +++ b/taglib/dsdiff/dsdiffdiintag.cpp @@ -0,0 +1,162 @@ +/*************************************************************************** + copyright : (C) 2016 by Damien Plisson, Audirvana + email : damien78@audirvana.com + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include "dsdiffdiintag.h" +#include "tstringlist.h" +#include "tpropertymap.h" + +using namespace TagLib; +using namespace DSDIFF::DIIN; + +class DSDIFF::DIIN::Tag::TagPrivate +{ +public: + TagPrivate() + { + } + + String title; + String artist; +}; + +DSDIFF::DIIN::Tag::Tag() : TagLib::Tag() +{ + d = new TagPrivate; +} + +DSDIFF::DIIN::Tag::~Tag() +{ + delete d; +} + +String DSDIFF::DIIN::Tag::title() const +{ + return d->title; +} + +String DSDIFF::DIIN::Tag::artist() const +{ + return d->artist; +} + +String DSDIFF::DIIN::Tag::album() const +{ + return String(); +} + +String DSDIFF::DIIN::Tag::comment() const +{ + return String(); +} + +String DSDIFF::DIIN::Tag::genre() const +{ + return String(); +} + +unsigned int DSDIFF::DIIN::Tag::year() const +{ + return 0; +} + +unsigned int DSDIFF::DIIN::Tag::track() const +{ + return 0; +} + +void DSDIFF::DIIN::Tag::setTitle(const String &title) +{ + if(title.isNull() || title.isEmpty()) + d->title = String(); + else + d->title = title; +} + +void DSDIFF::DIIN::Tag::setArtist(const String &artist) +{ + if(artist.isNull() || artist.isEmpty()) + d->artist = String(); + else + d->artist = artist; +} + +void DSDIFF::DIIN::Tag::setAlbum(const String &) +{ +} + +void DSDIFF::DIIN::Tag::setComment(const String &) +{ +} + +void DSDIFF::DIIN::Tag::setGenre(const String &) +{ +} + +void DSDIFF::DIIN::Tag::setYear(unsigned int) +{ +} + +void DSDIFF::DIIN::Tag::setTrack(unsigned int) +{ +} + +PropertyMap DSDIFF::DIIN::Tag::properties() const +{ + PropertyMap properties; + properties["TITLE"] = d->title; + properties["ARTIST"] = d->artist; + return properties; +} + +PropertyMap DSDIFF::DIIN::Tag::setProperties(const PropertyMap &origProps) +{ + PropertyMap properties(origProps); + properties.removeEmpty(); + StringList oneValueSet; + + if(properties.contains("TITLE")) { + d->title = properties["TITLE"].front(); + oneValueSet.append("TITLE"); + } else + d->title = String(); + + if(properties.contains("ARTIST")) { + d->artist = properties["ARTIST"].front(); + oneValueSet.append("ARTIST"); + } else + d->artist = String(); + + // for each tag that has been set above, remove the first entry in the corresponding + // value list. The others will be returned as unsupported by this format. + for(StringList::Iterator it = oneValueSet.begin(); it != oneValueSet.end(); ++it) { + if(properties[*it].size() == 1) + properties.erase(*it); + else + properties[*it].erase(properties[*it].begin()); + } + + return properties; +} + diff --git a/taglib/dsdiff/dsdiffdiintag.h b/taglib/dsdiff/dsdiffdiintag.h new file mode 100644 index 00000000..0eff545e --- /dev/null +++ b/taglib/dsdiff/dsdiffdiintag.h @@ -0,0 +1,150 @@ +/*************************************************************************** + copyright : (C) 2016 by Damien Plisson, Audirvana + email : damien78@audirvana.com + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_DSDIFFDIINTAG_H +#define TAGLIB_DSDIFFDIINTAG_H + +#include "tag.h" + +namespace TagLib { + + namespace DSDIFF { + + namespace DIIN { + + /*! + * Tags from the Edited Master Chunk Info + * + * Only Title and Artist tags are supported + */ + class TAGLIB_EXPORT Tag : public TagLib::Tag + { + public: + Tag(); + virtual ~Tag(); + + /*! + * Returns the track name; if no track name is present in the tag + * String() will be returned. + */ + String title() const; + + /*! + * Returns the artist name; if no artist name is present in the tag + * String() will be returned. + */ + String artist() const; + + /*! + * Not supported. Therefore always returns String(). + */ + String album() const; + + /*! + * Not supported. Therefore always returns String(). + */ + String comment() const; + + /*! + * Not supported. Therefore always returns String(). + */ + String genre() const; + + /*! + * Not supported. Therefore always returns 0. + */ + unsigned int year() const; + + /*! + * Not supported. Therefore always returns 0. + */ + unsigned int track() const; + + /*! + * Sets the title to \a title. If \a title is String() then this + * value will be cleared. + */ + void setTitle(const String &title); + + /*! + * Sets the artist to \a artist. If \a artist is String() then this + * value will be cleared. + */ + void setArtist(const String &artist); + + /*! + * Not supported and therefore ignored. + */ + void setAlbum(const String &album); + + /*! + * Not supported and therefore ignored. + */ + void setComment(const String &comment); + + /*! + * Not supported and therefore ignored. + */ + void setGenre(const String &genre); + + /*! + * Not supported and therefore ignored. + */ + void setYear(unsigned int year); + + /*! + * Not supported and therefore ignored. + */ + void setTrack(unsigned int track); + + /*! + * Implements the unified property interface -- export function. + * Since the DIIN tag is very limited, the exported map is as well. + */ + PropertyMap properties() const; + + /*! + * Implements the unified property interface -- import function. + * Because of the limitations of the DIIN file tag, any tags besides + * TITLE and ARTIST, will be + * returned. Additionally, if the map contains tags with multiple values, + * all but the first will be contained in the returned map of unsupported + * properties. + */ + PropertyMap setProperties(const PropertyMap &); + + private: + Tag(const Tag &); + Tag &operator=(const Tag &); + + class TagPrivate; + TagPrivate *d; + }; + } + } +} + +#endif + diff --git a/taglib/dsdiff/dsdifffile.cpp b/taglib/dsdiff/dsdifffile.cpp new file mode 100644 index 00000000..73b2dbb2 --- /dev/null +++ b/taglib/dsdiff/dsdifffile.cpp @@ -0,0 +1,812 @@ +/*************************************************************************** + copyright : (C) 2016 by Damien Plisson, Audirvana + email : damien78@audirvana.com + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include +#include +#include +#include +#include +#include + +#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 chunks; + std::vector 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(ID3v2Index, false); +} + +bool DSDIFF::File::hasID3v2Tag() const +{ + return d->hasID3v2; +} + +DSDIFF::DIIN::Tag *DSDIFF::File::DIINTag() const +{ + return d->tag.access(DIINIndex, false); +} + +bool DSDIFF::File::hasDIINTag() const +{ + return d->hasDiin; +} + +PropertyMap DSDIFF::File::properties() const +{ + if(d->hasID3v2) + return d->tag.access(ID3v2Index, false)->properties(); + + return PropertyMap(); +} + +void DSDIFF::File::removeUnsupportedProperties(const StringList &unsupported) +{ + if(d->hasID3v2) + d->tag.access(ID3v2Index, false)->removeUnsupportedProperties(unsupported); + + if(d->hasDiin) + d->tag.access(DIINIndex, false)->removeUnsupportedProperties(unsupported); +} + +PropertyMap DSDIFF::File::setProperties(const PropertyMap &properties) +{ + return d->tag.access(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(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(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(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 &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 &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(d->chunks.size())) + nextRootChunkIdx = d->chunks[d->childChunkIndex[childChunkNum] + 1].offset - 12; + + writeChunk(name, data, offset, + std::max(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(startingChunk)) { + std::vector &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(startingChunk)) { + std::vector &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(tell()) + chunkSize > static_cast(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(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(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(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(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(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(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(ID3v2Index, true); + d->isID3InPropChunk = false; // By default, ID3 chunk is at root level + d->hasID3v2 = false; + } +} + +void DSDIFF::File::writeChunk(const ByteVector &name, const ByteVector &data, + unsigned long long offset, unsigned long replace, + unsigned int leadingPadding) +{ + ByteVector combined; + if(leadingPadding) + combined.append(ByteVector(leadingPadding, '\x00')); + + combined.append(name); + combined.append(ByteVector::fromLongLong(data.size(), d->endianness == BigEndian)); + combined.append(data); + if((data.size() & 0x01) != 0) + combined.append('\x00'); + + insert(combined, offset, replace); +} + diff --git a/taglib/dsdiff/dsdifffile.h b/taglib/dsdiff/dsdifffile.h new file mode 100644 index 00000000..8b888744 --- /dev/null +++ b/taglib/dsdiff/dsdifffile.h @@ -0,0 +1,260 @@ +/*************************************************************************** + copyright : (C) 2016 by Damien Plisson, Audirvana + email : damien78@audirvana.com + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_DSDIFFFILE_H +#define TAGLIB_DSDIFFFILE_H + +#include "rifffile.h" +#include "id3v2tag.h" +#include "dsdiffproperties.h" +#include "dsdiffdiintag.h" + +namespace TagLib { + + //! An implementation of DSDIFF metadata + + /*! + * This is implementation of DSDIFF metadata. + * + * This supports an ID3v2 tag as well as reading stream from the ID3 RIFF + * chunk as well as properties from the file. + * Description of the DSDIFF format is available + * at http://dsd-guide.com/sites/default/files/white-papers/DSDIFF_1.5_Spec.pdf + * DSDIFF standard does not explictly specify the ID3V2 chunk + * It can be found at the root level, but also sometimes inside the PROP chunk + * In addition, title and artist info are stored as part of the standard + */ + + namespace DSDIFF { + + //! An implementation of TagLib::File with DSDIFF specific methods + + /*! + * This implements and provides an interface for DSDIFF files to the + * TagLib::Tag and TagLib::AudioProperties interfaces by way of implementing + * the abstract TagLib::File API as well as providing some additional + * information specific to DSDIFF files. + */ + + class TAGLIB_EXPORT File : public TagLib::File + { + public: + /*! + * Constructs an DSDIFF file from \a file. If \a readProperties is true + * the file's audio properties will also be read. + * + * \note In the current implementation, \a propertiesStyle is ignored. + */ + File(FileName file, bool readProperties = true, + Properties::ReadStyle propertiesStyle = Properties::Average); + + /*! + * Constructs an DSDIFF file from \a stream. If \a readProperties is true + * the file's audio properties will also be read. + * + * \note TagLib will *not* take ownership of the stream, the caller is + * responsible for deleting it after the File object. + * + * \note In the current implementation, \a propertiesStyle is ignored. + */ + File(IOStream *stream, bool readProperties = true, + Properties::ReadStyle propertiesStyle = Properties::Average); + + /*! + * Destroys this instance of the File. + */ + virtual ~File(); + + /*! + * Returns a pointer to a tag that is the union of the ID3v2 and DIIN + * tags. The ID3v2 tag is given priority in reading the information -- if + * requested information exists in both the ID3v2 tag and the ID3v1 tag, + * the information from the ID3v2 tag will be returned. + * + * If you would like more granular control over the content of the tags, + * with the concession of generality, use the tag-type specific calls. + * + * \note As this tag is not implemented as an ID3v2 tag or a DIIN tag, + * but a union of the two this pointer may not be cast to the specific + * tag types. + * + * \see ID3v2Tag() + * \see DIINTag() + */ + virtual Tag *tag() const; + + /*! + * Returns the ID3V2 Tag for this file. + * + * \note This always returns a valid pointer regardless of whether or not + * the file on disk has an ID3v2 tag. Use hasID3v2Tag() to check if the + * file on disk actually has an ID3v2 tag. + * + * \see hasID3v2Tag() + */ + virtual ID3v2::Tag *ID3v2Tag() const; + + /*! + * Returns the DSDIFF DIIN Tag for this file + * + */ + DSDIFF::DIIN::Tag *DIINTag() const; + + /*! + * Implements the unified property interface -- export function. + * This method forwards to ID3v2::Tag::properties(). + */ + PropertyMap properties() const; + + void removeUnsupportedProperties(const StringList &properties); + + /*! + * Implements the unified property interface -- import function. + * This method forwards to ID3v2::Tag::setProperties(). + */ + PropertyMap setProperties(const PropertyMap &); + + /*! + * Returns the AIFF::Properties for this file. If no audio properties + * were read then this will return a null pointer. + */ + virtual Properties *audioProperties() const; + + /*! + * Save the file. If at least one tag -- ID3v1 or DIIN -- exists this + * will duplicate its content into the other tag. This returns true + * if saving was successful. + * + * If neither exists or if both tags are empty, this will strip the tags + * from the file. + * + * This is the same as calling save(AllTags); + * + * If you would like more granular control over the content of the tags, + * with the concession of generality, use paramaterized save call below. + * + * \see save(int tags) + */ + virtual bool save(); + + /*! + * Save the file. This will attempt to save all of the tag types that are + * specified by OR-ing together TagTypes values. The save() method above + * uses AllTags. This returns true if saving was successful. + * + * This strips all tags not included in the mask, but does not modify them + * in memory, so later calls to save() which make use of these tags will + * remain valid. This also strips empty tags. + */ + bool save(int tags); + + /*! + * Returns whether or not the file on disk actually has an ID3v2 tag. + * + * \see ID3v2Tag() + */ + bool hasID3v2Tag() const; + + /*! + * Returns whether or not the file on disk actually has the DSDIFF + * Title & Artist tag. + * + * \see DIINTag() + */ + bool hasDIINTag() const; + + /*! + * Returns whether or not the given \a stream can be opened as a DSDIFF + * file. + * + * \note This method is designed to do a quick check. The result may + * not necessarily be correct. + */ + static bool isSupported(IOStream *stream); + + protected: + enum Endianness { BigEndian, LittleEndian }; + + File(FileName file, Endianness endianness); + File(IOStream *stream, Endianness endianness); + + private: + File(const File &); + File &operator=(const File &); + + /*! + * Sets the data for the the specified chunk at root level to \a data. + * + * \warning This will update the file immediately. + */ + void setRootChunkData(unsigned int i, const ByteVector &data); + + /*! + * Sets the data for the root-level chunk \a name to \a data. + * If a root-level chunk with the given name already exists + * it will be overwritten, otherwise it will be + * created after the existing chunks. + * + * \warning This will update the file immediately. + */ + void setRootChunkData(const ByteVector &name, const ByteVector &data); + + /*! + * Sets the data for the the specified child chunk to \a data. + * + * If data is null, then remove the chunk + * + * \warning This will update the file immediately. + */ + void setChildChunkData(unsigned int i, const ByteVector &data, + unsigned int childChunkNum); + + /*! + * Sets the data for the child chunk \a name to \a data. If a chunk with + * the given name already exists it will be overwritten, otherwise it will + * be created after the existing chunks inside child chunk. + * + * If data is null, then remove the chunks with \a name name + * + * \warning This will update the file immediately. + */ + void setChildChunkData(const ByteVector &name, const ByteVector &data, + unsigned int childChunkNum); + + void updateRootChunksStructure(unsigned int startingChunk); + + void read(bool readProperties, Properties::ReadStyle propertiesStyle); + void writeChunk(const ByteVector &name, const ByteVector &data, + unsigned long long offset, unsigned long replace = 0, + unsigned int leadingPadding = 0); + + class FilePrivate; + FilePrivate *d; + }; + } +} + +#endif + diff --git a/taglib/dsdiff/dsdiffproperties.cpp b/taglib/dsdiff/dsdiffproperties.cpp new file mode 100644 index 00000000..b8d362cc --- /dev/null +++ b/taglib/dsdiff/dsdiffproperties.cpp @@ -0,0 +1,120 @@ +/*************************************************************************** + copyright : (C) 2016 by Damien Plisson, Audirvana + email : damien78@audirvana.com + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include +#include + +#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((d->sampleCount * 1000.0) / d->sampleRate + 0.5) + : 0; +} + +DSDIFF::Properties::~Properties() +{ + delete d; +} + +int DSDIFF::Properties::length() const +{ + return lengthInSeconds(); +} + +int DSDIFF::Properties::lengthInSeconds() const +{ + return d->length / 1000; +} + +int DSDIFF::Properties::lengthInMilliseconds() const +{ + return d->length; +} + +int DSDIFF::Properties::bitrate() const +{ + return d->bitrate; +} + +int DSDIFF::Properties::sampleRate() const +{ + return d->sampleRate; +} + +int DSDIFF::Properties::channels() const +{ + return d->channels; +} + +int DSDIFF::Properties::bitsPerSample() const +{ + return d->sampleWidth; +} + +long long DSDIFF::Properties::sampleCount() const +{ + return d->sampleCount; +} + diff --git a/taglib/dsdiff/dsdiffproperties.h b/taglib/dsdiff/dsdiffproperties.h new file mode 100644 index 00000000..6aff0398 --- /dev/null +++ b/taglib/dsdiff/dsdiffproperties.h @@ -0,0 +1,83 @@ +/*************************************************************************** + copyright : (C) 2016 by Damien Plisson, Audirvana + email : damien78@audirvana.com +***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_DSDIFFPROPERTIES_H +#define TAGLIB_DSDIFFPROPERTIES_H + +#include "audioproperties.h" + +namespace TagLib { + + namespace DSDIFF { + + class File; + + //! An implementation of audio property reading for DSDIFF + + /*! + * This reads the data from an DSDIFF stream found in the AudioProperties + * API. + */ + + class TAGLIB_EXPORT Properties : public AudioProperties + { + public: + /*! + * Create an instance of DSDIFF::Properties with the data read from the + * ByteVector \a data. + */ + Properties(const unsigned int sampleRate, const unsigned short channels, + const unsigned long long samplesCount, const int bitrate, + ReadStyle style); + + /*! + * Destroys this DSDIFF::Properties instance. + */ + virtual ~Properties(); + + // Reimplementations. + + virtual int length() const; + virtual int lengthInSeconds() const; + virtual int lengthInMilliseconds() const; + virtual int bitrate() const; + virtual int sampleRate() const; + virtual int channels() const; + + int bitsPerSample() const; + long long sampleCount() const; + + private: + Properties(const Properties &); + Properties &operator=(const Properties &); + + class PropertiesPrivate; + PropertiesPrivate *d; + }; + } +} + +#endif + diff --git a/taglib/dsf/dsffile.cpp b/taglib/dsf/dsffile.cpp new file mode 100644 index 00000000..1b2c2403 --- /dev/null +++ b/taglib/dsf/dsffile.cpp @@ -0,0 +1,241 @@ +/*************************************************************************** + copyright : (C) 2013 - 2018 by Stephen F. Booth + email : me@sbooth.org + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include +#include +#include +#include +#include +#include + +#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(oldTagSize)); + } + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +// private members +//////////////////////////////////////////////////////////////////////////////// + + +void DSF::File::read(bool readProperties, Properties::ReadStyle propertiesStyle) +{ + // A DSF file consists of four chunks: DSD chunk, format chunk, data chunk, and metadata chunk + // The file format is not chunked in the sense of a RIFF File, though + + // DSD chunk + ByteVector chunkName = readBlock(4); + if(chunkName != "DSD ") { + debug("DSF::File::read() -- Not a DSF file."); + setValid(false); + return; + } + + long long chunkSize = readBlock(8).toLongLong(false); + + // Integrity check + if(28 != chunkSize) { + debug("DSF::File::read() -- File is corrupted, wrong chunk size"); + setValid(false); + return; + } + + d->fileSize = readBlock(8).toLongLong(false); + + // File is malformed or corrupted + if(d->fileSize != length()) { + debug("DSF::File::read() -- File is corrupted wrong length"); + setValid(false); + return; + } + + d->metadataOffset = readBlock(8).toLongLong(false); + + // File is malformed or corrupted + if(d->metadataOffset > d->fileSize) { + debug("DSF::File::read() -- Invalid metadata offset."); + setValid(false); + return; + } + + // Format chunk + chunkName = readBlock(4); + if(chunkName != "fmt ") { + debug("DSF::File::read() -- Missing 'fmt ' chunk."); + setValid(false); + return; + } + + chunkSize = readBlock(8).toLongLong(false); + + d->properties = new Properties(readBlock(chunkSize), propertiesStyle); + + // Skip the data chunk + + // A metadata offset of 0 indicates the absence of an ID3v2 tag + if(0 == d->metadataOffset) + d->tag = new ID3v2::Tag(); + else + d->tag = new ID3v2::Tag(this, d->metadataOffset); +} + diff --git a/taglib/dsf/dsffile.h b/taglib/dsf/dsffile.h new file mode 100644 index 00000000..e75298d7 --- /dev/null +++ b/taglib/dsf/dsffile.h @@ -0,0 +1,128 @@ +/*************************************************************************** + copyright : (C) 2013 - 2018 by Stephen F. Booth + email : me@sbooth.org + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_DSFFILE_H +#define TAGLIB_DSFFILE_H + +#include "tfile.h" +#include "id3v2tag.h" +#include "dsfproperties.h" + +namespace TagLib { + + //! An implementation of DSF metadata + + /*! + * This is implementation of DSF metadata. + * + * This supports an ID3v2 tag as well as properties from the file. + */ + + namespace DSF { + + //! An implementation of TagLib::File with DSF specific methods + + /*! + * This implements and provides an interface for DSF files to the + * TagLib::Tag and TagLib::AudioProperties interfaces by way of implementing + * the abstract TagLib::File API as well as providing some additional + * information specific to DSF files. + */ + + class TAGLIB_EXPORT File : public TagLib::File + { + public: + /*! + * Contructs an DSF file from \a file. If \a readProperties is true the + * file's audio properties will also be read using \a propertiesStyle. If + * false, \a propertiesStyle is ignored. + */ + File(FileName file, bool readProperties = true, + Properties::ReadStyle propertiesStyle = Properties::Average); + + /*! + * Contructs an DSF file from \a file. If \a readProperties is true the + * file's audio properties will also be read using \a propertiesStyle. If + * false, \a propertiesStyle is ignored. + */ + File(IOStream *stream, bool readProperties = true, + Properties::ReadStyle propertiesStyle = Properties::Average); + + /*! + * Destroys this instance of the File. + */ + virtual ~File(); + + /*! + * Returns the Tag for this file. + */ + ID3v2::Tag *tag() const; + + /*! + * Implements the unified property interface -- export function. + * This method forwards to ID3v2::Tag::properties(). + */ + PropertyMap properties() const; + + /*! + * Implements the unified property interface -- import function. + * This method forwards to ID3v2::Tag::setProperties(). + */ + PropertyMap setProperties(const PropertyMap &); + + /*! + * Returns the DSF::AudioProperties for this file. If no audio properties + * were read then this will return a null pointer. + */ + virtual Properties *audioProperties() const; + + /*! + * Saves the file. + */ + virtual bool save(); + + /*! + * Returns whether or not the given \a stream can be opened as a DSF + * file. + * + * \note This method is designed to do a quick check. The result may + * not necessarily be correct. + */ + static bool isSupported(IOStream *stream); + + private: + File(const File &); + File &operator=(const File &); + + void read(bool readProperties, Properties::ReadStyle propertiesStyle); + + class FilePrivate; + FilePrivate *d; + }; + } +} + +#endif + diff --git a/taglib/dsf/dsfproperties.cpp b/taglib/dsf/dsfproperties.cpp new file mode 100644 index 00000000..18511fa7 --- /dev/null +++ b/taglib/dsf/dsfproperties.cpp @@ -0,0 +1,161 @@ +/*************************************************************************** + copyright : (C) 2013 by Stephen F. Booth + email : me@sbooth.org + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include +#include + +#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((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..273a9f5a --- /dev/null +++ b/taglib/dsf/dsfproperties.h @@ -0,0 +1,92 @@ +/*************************************************************************** + copyright : (C) 2013 by Stephen F. Booth + email : me@sbooth.org + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_DSFPROPERTIES_H +#define TAGLIB_DSFPROPERTIES_H + +#include "audioproperties.h" + +namespace TagLib { + + namespace DSF { + + class File; + + //! An implementation of audio property reading for DSF + + /*! + * This reads the data from a DSF stream found in the AudioProperties + * API. + */ + + class TAGLIB_EXPORT Properties : public TagLib::AudioProperties + { + public: + /*! + * Create an instance of DSF::AudioProperties with the data read from the + * ByteVector \a data. + */ + Properties(const ByteVector &data, ReadStyle style); + + /*! + * Destroys this DSF::AudioProperties instance. + */ + virtual ~Properties(); + + // Reimplementations. + + virtual int length() const; + virtual int lengthInSeconds() const; + virtual int lengthInMilliseconds() const; + virtual int bitrate() const; + virtual int sampleRate() const; + virtual int channels() const; + + int formatVersion() const; + int formatID() const; + + /*! + * Channel type values: 1 = mono, 2 = stereo, 3 = 3 channels, + * 4 = quad, 5 = 4 channels, 6 = 5 channels, 7 = 5.1 channels + */ + int channelType() const; + int bitsPerSample() const; + long long sampleCount() const; + int blockSizePerChannel() const; + + private: + Properties(const Properties &); + Properties &operator=(const Properties &); + + void read(const ByteVector &data); + + class PropertiesPrivate; + PropertiesPrivate *d; + }; + } +} + +#endif + diff --git a/taglib/fileref.cpp b/taglib/fileref.cpp index 935c371b..5ec4dc06 100644 --- a/taglib/fileref.cpp +++ b/taglib/fileref.cpp @@ -52,6 +52,8 @@ #include "s3mfile.h" #include "itfile.h" #include "xmfile.h" +#include "dsffile.h" +#include "dsdifffile.h" using namespace TagLib; @@ -135,6 +137,10 @@ namespace return new IT::File(stream, readAudioProperties, audioPropertiesStyle); if(ext == "XM") return new XM::File(stream, readAudioProperties, audioPropertiesStyle); + if(ext == "DFF" || ext == "DSDIFF") + return new DSDIFF::File(stream, readAudioProperties, audioPropertiesStyle); + if(ext == "DSF") + return new DSF::File(stream, readAudioProperties, audioPropertiesStyle); return 0; } @@ -174,6 +180,10 @@ namespace file = new RIFF::WAV::File(stream, readAudioProperties, audioPropertiesStyle); else if(APE::File::isSupported(stream)) file = new APE::File(stream, readAudioProperties, audioPropertiesStyle); + else if(DSDIFF::File::isSupported(stream)) + file = new DSDIFF::File(stream, readAudioProperties, audioPropertiesStyle); + else if(DSF::File::isSupported(stream)) + file = new DSF::File(stream, readAudioProperties, audioPropertiesStyle); // isSupported() only does a quick check, so double check the file here. @@ -255,6 +265,10 @@ namespace return new IT::File(fileName, readAudioProperties, audioPropertiesStyle); if(ext == "XM") return new XM::File(fileName, readAudioProperties, audioPropertiesStyle); + if(ext == "DFF" || ext == "DSDIFF") + return new DSDIFF::File(fileName, readAudioProperties, audioPropertiesStyle); + if(ext == "DSF") + return new DSF::File(fileName, readAudioProperties, audioPropertiesStyle); return 0; } @@ -387,6 +401,9 @@ StringList FileRef::defaultFileExtensions() l.append("s3m"); l.append("it"); l.append("xm"); + l.append("dsf"); + l.append("dff"); + l.append("dsdiff"); // alias for "dff" return l; } diff --git a/taglib/toolkit/tfile.cpp b/taglib/toolkit/tfile.cpp index aff1684d..055b0d6d 100644 --- a/taglib/toolkit/tfile.cpp +++ b/taglib/toolkit/tfile.cpp @@ -63,6 +63,8 @@ #include "itfile.h" #include "xmfile.h" #include "mp4file.h" +#include "dsffile.h" +#include "dsdifffile.h" using namespace TagLib; @@ -148,6 +150,10 @@ PropertyMap File::properties() const return dynamic_cast(this)->properties(); if(dynamic_cast(this)) return dynamic_cast(this)->properties(); + if(dynamic_cast(this)) + return dynamic_cast(this)->properties(); + if(dynamic_cast(this)) + return dynamic_cast(this)->properties(); return tag()->properties(); } @@ -177,6 +183,10 @@ void File::removeUnsupportedProperties(const StringList &properties) dynamic_cast(this)->removeUnsupportedProperties(properties); else if(dynamic_cast(this)) dynamic_cast(this)->removeUnsupportedProperties(properties); + else if(dynamic_cast(this)) + dynamic_cast(this)->removeUnsupportedProperties(properties); + else if(dynamic_cast(this)) + dynamic_cast(this)->removeUnsupportedProperties(properties); else tag()->removeUnsupportedProperties(properties); } @@ -219,6 +229,10 @@ PropertyMap File::setProperties(const PropertyMap &properties) return dynamic_cast(this)->setProperties(properties); else if(dynamic_cast(this)) return dynamic_cast(this)->setProperties(properties); + else if(dynamic_cast(this)) + return dynamic_cast(this)->setProperties(properties); + else if(dynamic_cast(this)) + return dynamic_cast(this)->setProperties(properties); else return tag()->setProperties(properties); } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 0c258828..f3205877 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -24,6 +24,8 @@ INCLUDE_DIRECTORIES( ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/s3m ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/it ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/xm + ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/dsf + ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/dsdiff ) SET(test_runner_SRCS @@ -66,6 +68,8 @@ SET(test_runner_SRCS test_mpc.cpp test_opus.cpp test_speex.cpp + test_dsf.cpp + test_dsdiff.cpp ) INCLUDE_DIRECTORIES(${CPPUNIT_INCLUDE_DIR}) diff --git a/tests/data/empty10ms.dff b/tests/data/empty10ms.dff new file mode 100644 index 0000000000000000000000000000000000000000..9dc0b9ecdfd67c6e5cf8cb776aeac93c84200dc6 GIT binary patch literal 7186 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 n!DH@pWX@G)8Y5+O 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 +#include +#include +#include +#include +#include +#include "utils.h" + +using namespace std; +using namespace TagLib; + +class TestDSDIFF : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(TestDSDIFF); + CPPUNIT_TEST(testProperties); + CPPUNIT_TEST(testTags); + CPPUNIT_TEST(testSaveID3v2); + CPPUNIT_TEST(testRepeatedSave); + CPPUNIT_TEST_SUITE_END(); + +public: + + void testProperties() + { + DSDIFF::File f(TEST_FILE_PATH_C("empty10ms.dff")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(10, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(5644, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(2822400, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL((long long)28224, f.audioProperties()->sampleCount()); + } + + void testTags() + { + ScopedFileCopy copy("empty10ms", ".dff"); + string newname = copy.fileName(); + + DSDIFF::File *f = new DSDIFF::File(newname.c_str()); + CPPUNIT_ASSERT_EQUAL(String(""), f->tag()->artist()); + f->tag()->setArtist("The Artist"); + f->save(); + delete f; + + f = new DSDIFF::File(newname.c_str()); + CPPUNIT_ASSERT_EQUAL(String("The Artist"), f->tag()->artist()); + delete f; + } + + void testSaveID3v2() + { + ScopedFileCopy copy("empty10ms", ".dff"); + string newname = copy.fileName(); + + { + DSDIFF::File f(newname.c_str()); + CPPUNIT_ASSERT(!f.hasID3v2Tag()); + + f.tag()->setTitle(L"TitleXXX"); + f.save(); + CPPUNIT_ASSERT(f.hasID3v2Tag()); + } + { + DSDIFF::File f(newname.c_str()); + CPPUNIT_ASSERT(f.hasID3v2Tag()); + CPPUNIT_ASSERT_EQUAL(String(L"TitleXXX"), f.tag()->title()); + + f.tag()->setTitle(""); + f.save(); + CPPUNIT_ASSERT(!f.hasID3v2Tag()); + } + { + DSDIFF::File f(newname.c_str()); + CPPUNIT_ASSERT(!f.hasID3v2Tag()); + f.tag()->setTitle(L"TitleXXX"); + f.save(); + CPPUNIT_ASSERT(f.hasID3v2Tag()); + } + { + DSDIFF::File f(newname.c_str()); + CPPUNIT_ASSERT(f.hasID3v2Tag()); + CPPUNIT_ASSERT_EQUAL(String(L"TitleXXX"), f.tag()->title()); + } + } + + void testRepeatedSave() + { + ScopedFileCopy copy("empty10ms", ".dff"); + string newname = copy.fileName(); + + { + DSDIFF::File f(newname.c_str()); + CPPUNIT_ASSERT_EQUAL(String(""), f.tag()->title()); + f.tag()->setTitle("NEW TITLE"); + f.save(); + CPPUNIT_ASSERT_EQUAL(String("NEW TITLE"), f.tag()->title()); + f.tag()->setTitle("NEW TITLE 2"); + f.save(); + CPPUNIT_ASSERT_EQUAL(String("NEW TITLE 2"), f.tag()->title()); + CPPUNIT_ASSERT_EQUAL(8252L, f.length()); + f.save(); + CPPUNIT_ASSERT_EQUAL(8252L, f.length()); + } + { + DSDIFF::File f(newname.c_str()); + CPPUNIT_ASSERT_EQUAL(String("NEW TITLE 2"), f.tag()->title()); + } + } + + +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestDSDIFF); diff --git a/tests/test_dsf.cpp b/tests/test_dsf.cpp new file mode 100644 index 00000000..a096eef0 --- /dev/null +++ b/tests/test_dsf.cpp @@ -0,0 +1,57 @@ +#include +#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((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); diff --git a/tests/test_fileref.cpp b/tests/test_fileref.cpp index e766a891..8394b875 100644 --- a/tests/test_fileref.cpp +++ b/tests/test_fileref.cpp @@ -39,6 +39,8 @@ #include #include #include +#include +#include #include #include #include @@ -79,6 +81,8 @@ class TestFileRef : public CppUnit::TestFixture CPPUNIT_TEST(testWav); CPPUNIT_TEST(testAIFF_1); CPPUNIT_TEST(testAIFF_2); + CPPUNIT_TEST(testDSF); + CPPUNIT_TEST(testDSDIFF); CPPUNIT_TEST(testUnsupported); CPPUNIT_TEST(testCreate); CPPUNIT_TEST(testFileResolver); @@ -288,6 +292,16 @@ public: { fileRefSave("alaw", ".aifc"); } + + void testDSF() + { + fileRefSave("empty10ms",".dsf"); + } + + void testDSDIFF() + { + fileRefSave("empty10ms",".dff"); + } void testUnsupported() { -- 2.40.0