diff --git a/.gitignore b/.gitignore index 941063c5..8bc0c65d 100644 --- a/.gitignore +++ b/.gitignore @@ -19,8 +19,10 @@ CMakeFiles/ /taglib.pc /tests/test_runner /tests/Testing +/taglib/libtag.a /taglib_config.h /taglib-config +/bindings/c/libtag_c.a /bindings/c/taglib_c.pc /bindings/c/Debug /bindings/c/MinSizeRel @@ -42,5 +44,6 @@ CMakeFiles/ /taglib/tag.dir/Release /ALL_BUILD.dir /ZERO_CHECK.dir +taglib.h.stamp taglib.xcodeproj CMakeScripts diff --git a/AUTHORS b/AUTHORS index e2b73429..279ee221 100644 --- a/AUTHORS +++ b/AUTHORS @@ -4,6 +4,8 @@ Lukas Lalinsky Implementation of multiple new file formats, many bug fixes, maintainer Tsuda Kageyu A lot of bug fixes and performance improvements, maintainer. +Stephen F. Booth + DSF metadata implementation, bug fixes, maintainer. Ismael Orenstein Xing header implementation Allan Sandfeld Jensen @@ -12,6 +14,8 @@ Teemu Tervo Numerous bug reports and fixes Mathias Panzenböck Mod, S3M, IT and XM metadata implementations +Damien Plisson + DSDIFF metadata implementation Please send all patches and questions to taglib-devel@kde.org rather than to individual developers! diff --git a/CMakeLists.txt b/CMakeLists.txt index 38f664c3..8974da05 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,10 +36,6 @@ if(ENABLE_CCACHE) endif() option(VISIBILITY_HIDDEN "Build with -fvisibility=hidden" OFF) -if(VISIBILITY_HIDDEN) - add_definitions(-fvisibility=hidden) -endif() - option(BUILD_TESTS "Build the test suite" OFF) option(BUILD_EXAMPLES "Build the examples" OFF) option(BUILD_BINDINGS "Build the bindings" ON) diff --git a/NEWS b/NEWS index 9c97fb45..8f8b8ac1 100644 --- a/NEWS +++ b/NEWS @@ -1,15 +1,23 @@ ============================ + * Added support for DSF and DSDIFF files. * Added support for WinRT. * Added support for classical music tags of iTunes 12.5. + * Added support for file descriptor to FileStream. + * Added support for 'cmID', 'purl', 'egid' MP4 atoms. * Enabled FileRef to detect file types based on the stream content. * Dropped support for Windows 9x and NT 4.0 or older. + * Check for mandatory header objects in ASF files. + * Fixed OOB read on invalid Ogg FLAC files (CVE-2018-11439). + * Fixed handling of empty MPEG files. * Fixed reading MP4 atoms with zero length. * Fixed reading FLAC files with zero-sized seektables. * Fixed handling of lowercase field names in Vorbis Comments. * Fixed handling of 'rate' atoms in MP4 files. * Fixed handling of invalid UTF-8 sequences. * Fixed possible file corruptions when saving Ogg files. + * TableOfContentsFrame::toString() improved. + * UserTextIdentificationFrame::toString() improved. * Marked FileRef::create() deprecated. * Several smaller bug fixes and performance improvements. diff --git a/README.md b/README.md index d976db3d..5f087995 100644 --- a/README.md +++ b/README.md @@ -2,15 +2,15 @@ [![Build Status](https://travis-ci.org/taglib/taglib.svg?branch=master)](https://travis-ci.org/taglib/taglib) -### TagLib Audio Meta-Data Library +### TagLib Audio Metadata Library http://taglib.org/ -TagLib is a library for reading and editing the meta-data of several +TagLib is a library for reading and editing the metadata of several popular audio formats. Currently it supports both ID3v1 and [ID3v2][] -for MP3 files, [Ogg Vorbis][] comments and ID3 tags and Vorbis comments -in [FLAC][], MPC, Speex, WavPack, TrueAudio, WAV, AIFF, MP4 and ASF -files. +for MP3 files, [Ogg Vorbis][] comments and ID3 tags +in [FLAC][], MPC, Speex, WavPack, TrueAudio, WAV, AIFF, MP4, APE, +DSF, DFF, and ASF files. TagLib is distributed under the [GNU Lesser General Public License][] (LGPL) and [Mozilla Public License][] (MPL). Essentially that means that diff --git a/bindings/c/CMakeLists.txt b/bindings/c/CMakeLists.txt index 387f1bd1..ebb1267f 100644 --- a/bindings/c/CMakeLists.txt +++ b/bindings/c/CMakeLists.txt @@ -21,7 +21,14 @@ set(tag_c_HDRS tag_c.h) add_library(tag_c tag_c.cpp ${tag_c_HDRS}) target_link_libraries(tag_c tag) -set_target_properties(tag_c PROPERTIES PUBLIC_HEADER "${tag_c_HDRS}") +set_target_properties(tag_c PROPERTIES + PUBLIC_HEADER "${tag_c_HDRS}" + DEFINE_SYMBOL MAKE_TAGLIB_LIB +) +if(VISIBILITY_HIDDEN) + set_target_properties(tag_c PROPERTIES C_VISIBILITY_PRESET hidden + ) +endif() if(BUILD_FRAMEWORK) set_target_properties(tag_c PROPERTIES FRAMEWORK TRUE) endif() diff --git a/taglib/CMakeLists.txt b/taglib/CMakeLists.txt index 5839c281..2514190d 100644 --- a/taglib/CMakeLists.txt +++ b/taglib/CMakeLists.txt @@ -27,6 +27,7 @@ include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/ebml ${CMAKE_CURRENT_SOURCE_DIR}/ebml/matroska ${CMAKE_CURRENT_SOURCE_DIR}/dsf + ${CMAKE_CURRENT_SOURCE_DIR}/dsdiff ${CMAKE_SOURCE_DIR}/3rdparty ) @@ -151,6 +152,9 @@ set(tag_HDRS ebml/matroska/ebmlmatroskaaudio.h dsf/dsffile.h dsf/dsfproperties.h + dsdiff/dsdifffile.h + dsdiff/dsdiffproperties.h + dsdiff/dsdiffdiintag.h ) set(mpeg_SRCS @@ -321,6 +325,12 @@ set(matroska_SRCS ebml/matroska/ebmlmatroskaaudio.cpp ) +set(dsdiff_SRCS + dsdiff/dsdifffile.cpp + dsdiff/dsdiffproperties.cpp + dsdiff/dsdiffdiintag.cpp +) + set(toolkit_SRCS toolkit/taglib.cpp toolkit/tstring.cpp @@ -357,7 +367,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} - ${ebml_SRCS} ${matroska_SRCS} ${dsf_SRCS} + ${ebml_SRCS} ${matroska_SRCS} ${dsf_SRCS} ${dsdiff_SRCS} ${zlib_SRCS} tag.cpp tagunion.cpp @@ -380,6 +390,11 @@ set_target_properties(tag PROPERTIES LINK_INTERFACE_LIBRARIES "" PUBLIC_HEADER "${tag_HDRS}" ) +if(VISIBILITY_HIDDEN) + set_target_properties(tag PROPERTIES C_VISIBILITY_PRESET hidden + ) +endif() + if(BUILD_FRAMEWORK) unset(INSTALL_NAME_DIR) set_target_properties(tag PROPERTIES diff --git a/taglib/dsdiff/dsdiffdiintag.cpp b/taglib/dsdiff/dsdiffdiintag.cpp new file mode 100644 index 00000000..6daf5bed --- /dev/null +++ b/taglib/dsdiff/dsdiffdiintag.cpp @@ -0,0 +1,166 @@ +/*************************************************************************** + 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" +#include "tpicturemap.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; +} + +PictureMap DSDIFF::DIIN::Tag::pictures() const +{ + return PictureMap(); +} + +void DSDIFF::DIIN::Tag::setTitle(const String &title) +{ + d->title = title; +} + +void DSDIFF::DIIN::Tag::setArtist(const String &artist) +{ + 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) +{ +} + +void DSDIFF::DIIN::Tag::setPictures( const PictureMap& l ) +{ +} + +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..8cf3ebd6 --- /dev/null +++ b/taglib/dsdiff/dsdiffdiintag.h @@ -0,0 +1,160 @@ +/*************************************************************************** + 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. + */ + virtual String title() const; + + /*! + * Returns the artist name; if no artist name is present in the tag + * String() will be returned. + */ + virtual String artist() const; + + /*! + * Not supported. Therefore always returns String(). + */ + virtual String album() const; + + /*! + * Not supported. Therefore always returns String(). + */ + virtual String comment() const; + + /*! + * Not supported. Therefore always returns String(). + */ + virtual String genre() const; + + /*! + * Not supported. Therefore always returns 0. + */ + virtual unsigned int year() const; + + /*! + * Not supported. Therefore always returns 0. + */ + virtual unsigned int track() const; + + /*! + * Not supported. Therefore always returns an empty list. + */ + virtual PictureMap pictures() const; + + /*! + * Sets the title to \a title. If \a title is String() then this + * value will be cleared. + */ + virtual void setTitle(const String &title); + + /*! + * Sets the artist to \a artist. If \a artist is String() then this + * value will be cleared. + */ + virtual void setArtist(const String &artist); + + /*! + * Not supported and therefore ignored. + */ + virtual void setAlbum(const String &album); + + /*! + * Not supported and therefore ignored. + */ + virtual void setComment(const String &comment); + + /*! + * Not supported and therefore ignored. + */ + virtual void setGenre(const String &genre); + + /*! + * Not supported and therefore ignored. + */ + virtual void setYear(unsigned int year); + + /*! + * Not supported and therefore ignored. + */ + virtual void setTrack(unsigned int track); + + /*! + * Not supported and therefore ignored. + */ + virtual void setPictures( const PictureMap& l ); + + /*! + * 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..e83312cb --- /dev/null +++ b/taglib/dsdiff/dsdifffile.cpp @@ -0,0 +1,844 @@ +/*************************************************************************** + 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 + +#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), + id3v2TagChunkID("ID3 "), + hasID3v2(false), + hasDiin(false) + { + childChunkIndex[ID3v2Index] = -1; + childChunkIndex[DIINIndex] = -1; + } + + 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. + + SCOPED_PTR properties; + + DoubleTagUnion 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, + AudioProperties::ReadStyle propertiesStyle) : TagLib::File(file) +{ + d = new FilePrivate; + d->endianness = BigEndian; + if(isOpen()) + read(readProperties, propertiesStyle); +} + +DSDIFF::File::File(IOStream *stream, bool readProperties, + AudioProperties::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::AudioProperties *DSDIFF::File::audioProperties() const +{ + return d->properties.get(); +} + +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().isEmpty()) { + ByteVector diinTitle; + if(d->endianness == BigEndian) + diinTitle.append(ByteVector::fromUInt32BE(diinTag->title().size())); + else + diinTitle.append(ByteVector::fromUInt32LE(diinTag->title().size())); + diinTitle.append(ByteVector::fromCString(diinTag->title().toCString())); + setChildChunkData("DITI", diinTitle, DIINChunk); + } + else + setChildChunkData("DITI", ByteVector(), DIINChunk); + + if(!diinTag->artist().isEmpty()) { + ByteVector diinArtist; + if(d->endianness == BigEndian) + diinArtist.append(ByteVector::fromUInt32BE(diinTag->artist().size())); + else + diinArtist.append(ByteVector::fromUInt32LE(diinTag->artist().size())); + 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.isEmpty()) { + // Null data: remove chunk + // Update global size + unsigned long long removedChunkTotalSize = d->chunks[i].size + d->chunks[i].padding + 12; + d->size -= removedChunkTotalSize; + if(d->endianness == BigEndian) + insert(ByteVector::fromUInt64BE(d->size), 4, 8); + else + insert(ByteVector::fromUInt64LE(d->size), 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); + if(d->endianness == BigEndian) + insert(ByteVector::fromUInt64BE(d->size), 4, 8); + else + insert(ByteVector::fromUInt64LE(d->size), 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; + if(d->endianness == BigEndian) + insert(ByteVector::fromUInt64BE(d->size), 4, 8); + else + insert(ByteVector::fromUInt64LE(d->size), 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.isEmpty()) { + // Null data: remove chunk + // Update global size + unsigned long long removedChunkTotalSize = childChunks[i].size + childChunks[i].padding + 12; + d->size -= removedChunkTotalSize; + if(d->endianness == BigEndian) + insert(ByteVector::fromUInt64BE(d->size), 4, 8); + else + insert(ByteVector::fromUInt64LE(d->size), 4, 8); + // Update child chunk size + d->chunks[d->childChunkIndex[childChunkNum]].size -= removedChunkTotalSize; + if(d->endianness == BigEndian) + insert(ByteVector::fromUInt64BE(d->chunks[d->childChunkIndex[childChunkNum]].size), + d->chunks[d->childChunkIndex[childChunkNum]].offset - 8, 8); + else + insert(ByteVector::fromUInt64LE(d->chunks[d->childChunkIndex[childChunkNum]].size), + 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); + if(d->endianness == BigEndian) + insert(ByteVector::fromUInt64BE(d->size), 4, 8); + else + insert(ByteVector::fromUInt64LE(d->size), 4, 8); + // And the PROP chunk size + d->chunks[d->childChunkIndex[childChunkNum]].size += ((data.size() + 1) & ~1) + - (childChunks[i].size + childChunks[i].padding); + if(d->endianness == BigEndian) + insert(ByteVector::fromUInt64BE(d->chunks[d->childChunkIndex[childChunkNum]].size), + d->chunks[d->childChunkIndex[childChunkNum]].offset - 8, 8); + else + insert(ByteVector::fromUInt64LE(d->chunks[d->childChunkIndex[childChunkNum]].size), + 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.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; + if(d->endianness == BigEndian) + insert(ByteVector::fromUInt64BE(d->size), 4, 8); + else + insert(ByteVector::fromUInt64LE(d->size), 4, 8); + // And the child chunk size + d->chunks[d->childChunkIndex[childChunkNum]].size += (offset & 1) + + ((data.size() + 1) & ~1) + 12; + if(d->endianness == BigEndian) + insert(ByteVector::fromUInt64BE(d->chunks[d->childChunkIndex[childChunkNum]].size), + d->chunks[d->childChunkIndex[childChunkNum]].offset - 8, 8); + else + insert(ByteVector::fromUInt64LE(d->chunks[d->childChunkIndex[childChunkNum]].size), + 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, AudioProperties::ReadStyle propertiesStyle) +{ + bool bigEndian = (d->endianness == BigEndian); + + d->type = readBlock(4); + d->size = bigEndian ? readBlock(8).toInt64BE(0) : readBlock(8).toInt64LE(0); + 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 = bigEndian ? readBlock(8).toInt64BE(0) : readBlock(8).toInt64LE(0); + + 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 = bigEndian ? readBlock(8).toInt64BE(0) : readBlock(8).toInt64LE(0); + + 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 = bigEndian ? readBlock(4).toUInt32BE(0) : readBlock(4).toUInt32LE(0); + dstFrameRate = bigEndian ? readBlock(2).toUInt16BE(0) : readBlock(2).toUInt16LE(0); + 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 = bigEndian ? readBlock(8).toInt64BE(0) : readBlock(8).toInt64LE(0); + + 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 = bigEndian ? readBlock(8).toInt64BE(0) : readBlock(8).toInt64LE(0); + + 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 = bigEndian ? readBlock(4).toUInt32BE(0) : readBlock(4).toUInt32LE(0); + } + else if(d->childChunks[PROPChunk][i].name == "CHNL") { + // Channels + seek(d->childChunks[PROPChunk][i].offset); + channels = bigEndian ? readBlock(2).toInt16BE(0) : readBlock(2).toInt16LE(0); + } + } + + // 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 = bigEndian ? readBlock(4).toUInt32BE(0) : readBlock(4).toUInt32LE(0); + 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 = bigEndian ? readBlock(4).toUInt32BE(0) : readBlock(4).toUInt32LE(0); + 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.reset(new AudioProperties(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); + if(d->endianness == BigEndian) + combined.append(ByteVector::fromUInt64BE(data.size())); + else + combined.append(ByteVector::fromUInt64LE(data.size())); + 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..bb28bca2 --- /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, + AudioProperties::ReadStyle propertiesStyle = AudioProperties::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, + AudioProperties::ReadStyle propertiesStyle = AudioProperties::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 DSDIFF::Properties for this file. If no audio properties + * were read then this will return a null pointer. + */ + virtual AudioProperties *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, AudioProperties::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..eb1fca64 --- /dev/null +++ b/taglib/dsdiff/dsdiffproperties.cpp @@ -0,0 +1,117 @@ +/*************************************************************************** + 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::AudioProperties::AudioPropertiesPrivate +{ +public: + AudioPropertiesPrivate() : + 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::AudioProperties::AudioProperties(const unsigned int sampleRate, + const unsigned short channels, + const unsigned long long samplesCount, + const int bitrate, + ReadStyle style) : TagLib::AudioProperties(), d(new AudioPropertiesPrivate) +{ + 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::AudioProperties::~AudioProperties() +{ + delete d; +} + +int DSDIFF::AudioProperties::length() const +{ + return lengthInSeconds(); +} + +int DSDIFF::AudioProperties::lengthInSeconds() const +{ + return d->length / 1000; +} + +int DSDIFF::AudioProperties::lengthInMilliseconds() const +{ + return d->length; +} + +int DSDIFF::AudioProperties::bitrate() const +{ + return d->bitrate; +} + +int DSDIFF::AudioProperties::sampleRate() const +{ + return d->sampleRate; +} + +int DSDIFF::AudioProperties::channels() const +{ + return d->channels; +} + +int DSDIFF::AudioProperties::bitsPerSample() const +{ + return d->sampleWidth; +} + +long long DSDIFF::AudioProperties::sampleCount() const +{ + return d->sampleCount; +} diff --git a/taglib/dsdiff/dsdiffproperties.h b/taglib/dsdiff/dsdiffproperties.h new file mode 100644 index 00000000..2f1573a6 --- /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 AudioProperties : public TagLib::AudioProperties + { + public: + /*! + * Create an instance of DSDIFF::AudioProperties with the data read from the + * ByteVector \a data. + */ + AudioProperties(const unsigned int sampleRate, const unsigned short channels, + const unsigned long long samplesCount, const int bitrate, + ReadStyle style); + + /*! + * Destroys this DSDIFF::AudioProperties instance. + */ + virtual ~AudioProperties(); + + // 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: + AudioProperties(const AudioProperties &); + AudioProperties &operator=(const AudioProperties &); + + class AudioPropertiesPrivate; + AudioPropertiesPrivate *d; + }; + } +} + +#endif + diff --git a/taglib/dsf/dsffile.cpp b/taglib/dsf/dsffile.cpp index edd0251b..6b68998b 100644 --- a/taglib/dsf/dsffile.cpp +++ b/taglib/dsf/dsffile.cpp @@ -1,6 +1,6 @@ /*************************************************************************** - copyright : (C) 2013 by Stephen F. Booth - email : me@sbooth.org + copyright : (C) 2013 - 2018 by Stephen F. Booth + email : me@sbooth.org ***************************************************************************/ /*************************************************************************** @@ -29,6 +29,7 @@ #include #include #include +#include #include "dsffile.h" @@ -50,6 +51,17 @@ public: SCOPED_PTR 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 //////////////////////////////////////////////////////////////////////////////// @@ -87,6 +99,16 @@ DSF::AudioProperties *DSF::File::audioProperties() const return d->properties.get(); } +PropertyMap DSF::File::properties() const +{ + return d->tag->properties(); +} + +PropertyMap DSF::File::setProperties(const PropertyMap &properties) +{ + return d->tag->setProperties(properties); +} + bool DSF::File::save() { if(readOnly()) { @@ -210,4 +232,3 @@ void DSF::File::read(bool readProperties, AudioProperties::ReadStyle propertiesS else d->tag.reset(new ID3v2::Tag(this, d->metadataOffset)); } - diff --git a/taglib/dsf/dsffile.h b/taglib/dsf/dsffile.h index 1afa2c31..aa842fcc 100644 --- a/taglib/dsf/dsffile.h +++ b/taglib/dsf/dsffile.h @@ -1,5 +1,5 @@ /*************************************************************************** - copyright : (C) 2013 by Stephen F. Booth + copyright : (C) 2013 - 2018 by Stephen F. Booth email : me@sbooth.org ***************************************************************************/ @@ -32,19 +32,19 @@ namespace TagLib { - //! An implementation of DSF metadata + //! An implementation of DSF metadata - /*! - * This is implementation of DSF metadata. - * - * This supports an ID3v2 tag as well as properties from the file. - */ + /*! + * 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 + //! 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 @@ -86,11 +86,32 @@ namespace TagLib { */ virtual AudioProperties *audioProperties() const; + /*! + * Implements the unified property interface -- export function. + * This method forwards to ID3v2::Tag::properties(). + */ + virtual PropertyMap properties() const; + + /*! + * Implements the unified property interface -- import function. + * This method forwards to ID3v2::Tag::setProperties(). + */ + virtual PropertyMap setProperties(const PropertyMap &); + /*! * 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 &); diff --git a/taglib/dsf/dsfproperties.cpp b/taglib/dsf/dsfproperties.cpp index c5c4fd3f..9a2b7714 100644 --- a/taglib/dsf/dsfproperties.cpp +++ b/taglib/dsf/dsfproperties.cpp @@ -1,6 +1,6 @@ /*************************************************************************** - copyright : (C) 2013 by Stephen F. Booth - email : me@sbooth.org + copyright : (C) 2013 by Stephen F. Booth + email : me@sbooth.org ***************************************************************************/ /*************************************************************************** diff --git a/taglib/dsf/dsfproperties.h b/taglib/dsf/dsfproperties.h index 982adabc..0448bd0c 100644 --- a/taglib/dsf/dsfproperties.h +++ b/taglib/dsf/dsfproperties.h @@ -66,6 +66,11 @@ namespace TagLib { 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; diff --git a/taglib/fileref.cpp b/taglib/fileref.cpp index ff463375..3f74a4c6 100644 --- a/taglib/fileref.cpp +++ b/taglib/fileref.cpp @@ -53,6 +53,7 @@ #include "itfile.h" #include "xmfile.h" #include "dsffile.h" +#include "dsdifffile.h" using namespace TagLib; @@ -136,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; } @@ -175,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. @@ -352,6 +361,8 @@ StringList FileRef::defaultFileExtensions() l.append("it"); l.append("xm"); l.append("dsf"); + l.append("dff"); + l.append("dsdiff"); // alias for "dff" return l; } diff --git a/taglib/mp4/mp4tag.cpp b/taglib/mp4/mp4tag.cpp index a8501c18..3b7ecc67 100644 --- a/taglib/mp4/mp4tag.cpp +++ b/taglib/mp4/mp4tag.cpp @@ -79,7 +79,8 @@ MP4::Tag::Tag(TagLib::File *file, MP4::Atoms *atoms) : parseInt(atom); } else if(atom->name == "tvsn" || atom->name == "tves" || atom->name == "cnID" || - atom->name == "sfID" || atom->name == "atID" || atom->name == "geID") { + atom->name == "sfID" || atom->name == "atID" || atom->name == "geID" || + atom->name == "cmID") { parseUInt(atom); } else if(atom->name == "plID") { @@ -94,6 +95,9 @@ MP4::Tag::Tag(TagLib::File *file, MP4::Atoms *atoms) : else if(atom->name == "covr") { parseCovr(atom); } + else if(atom->name == "purl" || atom->name == "egid") { + parseText(atom, -1); + } else { parseText(atom); } @@ -481,7 +485,8 @@ MP4::Tag::save() data.append(renderInt(name.data(String::Latin1), it->second)); } else if(name == "tvsn" || name == "tves" || name == "cnID" || - name == "sfID" || name == "atID" || name == "geID") { + name == "sfID" || name == "atID" || name == "geID" || + name == "cmID") { data.append(renderUInt(name.data(String::Latin1), it->second)); } else if(name == "plID") { @@ -493,6 +498,9 @@ MP4::Tag::save() else if(name == "covr") { data.append(renderCovr(name.data(String::Latin1), it->second)); } + else if(name == "purl" || name == "egid") { + data.append(renderText(name.data(String::Latin1), it->second, TypeImplicit)); + } else if(name.size() == 4){ data.append(renderText(name.data(String::Latin1), it->second)); } diff --git a/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp b/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp index 12514337..472348f4 100644 --- a/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp +++ b/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp @@ -216,7 +216,23 @@ void TableOfContentsFrame::removeEmbeddedFrames(const ByteVector &id) String TableOfContentsFrame::toString() const { - return String(); + String s = String(d->elementID) + + ": top level: " + (d->isTopLevel ? "true" : "false") + + ", ordered: " + (d->isOrdered ? "true" : "false"); + + if(!d->childElements.isEmpty()) { + s+= ", chapters: [ " + String(d->childElements.toByteVector(", ")) + " ]"; + } + + if(!d->embeddedFrameList.isEmpty()) { + StringList frameIDs; + for(FrameList::ConstIterator it = d->embeddedFrameList.begin(); + it != d->embeddedFrameList.end(); ++it) + frameIDs.append((*it)->frameID()); + s += ", sub-frames: [ " + frameIDs.toString(", ") + " ]"; + } + + return s; } PropertyMap TableOfContentsFrame::asProperties() const diff --git a/taglib/mpeg/id3v2/frames/textidentificationframe.cpp b/taglib/mpeg/id3v2/frames/textidentificationframe.cpp index 7eb413d5..56b36d4c 100644 --- a/taglib/mpeg/id3v2/frames/textidentificationframe.cpp +++ b/taglib/mpeg/id3v2/frames/textidentificationframe.cpp @@ -339,7 +339,13 @@ UserTextIdentificationFrame::UserTextIdentificationFrame(const String &descripti String UserTextIdentificationFrame::toString() const { - return "[" + description() + "] " + fieldList().toString(); + // first entry is the description itself, drop from values list + StringList l = fieldList(); + for(StringList::Iterator it = l.begin(); it != l.end(); ++it) { + l.erase(it); + break; + } + return "[" + description() + "] " + l.toString(); } String UserTextIdentificationFrame::description() const diff --git a/taglib/mpeg/id3v2/id3v2framefactory.cpp b/taglib/mpeg/id3v2/id3v2framefactory.cpp index efc28b16..f3ec22ec 100644 --- a/taglib/mpeg/id3v2/id3v2framefactory.cpp +++ b/taglib/mpeg/id3v2/id3v2framefactory.cpp @@ -322,10 +322,11 @@ void FrameFactory::rebuildAggregateFrames(ID3v2::Tag *tag) const tag->frameList("TDAT").size() == 1) { TextIdentificationFrame *tdrc = - static_cast(tag->frameList("TDRC").front()); + dynamic_cast(tag->frameList("TDRC").front()); UnknownFrame *tdat = static_cast(tag->frameList("TDAT").front()); - if(tdrc->fieldList().size() == 1 && + if(tdrc && + tdrc->fieldList().size() == 1 && tdrc->fieldList().front().size() == 4 && tdat->data().size() >= 5) { diff --git a/taglib/mpeg/mpegfile.cpp b/taglib/mpeg/mpegfile.cpp index ec13de9e..78732874 100644 --- a/taglib/mpeg/mpegfile.cpp +++ b/taglib/mpeg/mpegfile.cpp @@ -108,6 +108,9 @@ bool MPEG::File::isSupported(IOStream *stream) long long headerOffset; const ByteVector buffer = Utils::readHeader(stream, bufferSize(), true, &headerOffset); + if(buffer.isEmpty()) + return false; + const long long originalPosition = stream->tell(); AdapterFile file(stream); diff --git a/taglib/ogg/flac/oggflacfile.cpp b/taglib/ogg/flac/oggflacfile.cpp index 852c9d2f..7ff092d2 100644 --- a/taglib/ogg/flac/oggflacfile.cpp +++ b/taglib/ogg/flac/oggflacfile.cpp @@ -221,11 +221,21 @@ void Ogg::FLAC::File::scan() if(!metadataHeader.startsWith("fLaC")) { // FLAC 1.1.2+ + // See https://xiph.org/flac/ogg_mapping.html for the header specification. + if(metadataHeader.size() < 13) + return; + + if(metadataHeader[0] != 0x7f) + return; + if(metadataHeader.mid(1, 4) != "FLAC") return; - if(metadataHeader[5] != 1) - return; // not version 1 + if(metadataHeader[5] != 1 && metadataHeader[6] != 0) + return; // not version 1.0 + + if(metadataHeader.mid(9, 4) != "fLaC") + return; metadataHeader = metadataHeader.mid(13); } diff --git a/taglib/toolkit/taglib.h b/taglib/toolkit/taglib.h index e7666c3b..a2720dad 100644 --- a/taglib/toolkit/taglib.h +++ b/taglib/toolkit/taglib.h @@ -128,7 +128,7 @@ namespace TagLib * * \section installing Installing TagLib * - * Please see the TagLib website for the latest + * Please see the TagLib website for the latest * downloads. * * TagLib can be built using the CMake build system. TagLib installs a taglib-config and pkg-config file to @@ -173,11 +173,10 @@ namespace TagLib * * Questions about TagLib should be directed to the TagLib mailing list, not directly to the author. * - * - TagLib Homepage + * - TagLib Homepage * - TagLib Mailing List (taglib-devel@kde.org) * - * \author Scott Wheeler et al. - * + * \author TagLib authors. */ #endif diff --git a/taglib/toolkit/tfilestream.cpp b/taglib/toolkit/tfilestream.cpp index c92d4e2c..1b2a72f8 100644 --- a/taglib/toolkit/tfilestream.cpp +++ b/taglib/toolkit/tfilestream.cpp @@ -63,6 +63,11 @@ namespace #endif } + FileHandle openFile(const int fileDescriptor, bool readOnly) + { + return InvalidFileHandle; + } + void closeFile(FileHandle file) { CloseHandle(file); @@ -103,6 +108,11 @@ namespace return fopen(path, readOnly ? "rb" : "rb+"); } + FileHandle openFile(const int fileDescriptor, bool readOnly) + { + return fdopen(fileDescriptor, readOnly ? "rb" : "rb+"); + } + void closeFile(FileHandle file) { fclose(file); @@ -152,13 +162,28 @@ FileStream::FileStream(FileName fileName, bool openReadOnly) : d->file = openFile(fileName, true); if(d->file == InvalidFileHandle) - { # ifdef _WIN32 debug("Could not open file " + String(fileName.wstr())); # else debug("Could not open file " + String(static_cast(d->name))); # endif - } +} + +FileStream::FileStream(int fileDescriptor, bool openReadOnly) + : d(new FileStreamPrivate("")) +{ + // First try with read / write mode, if that fails, fall back to read only. + + if(!openReadOnly) + d->file = openFile(fileDescriptor, false); + + if(d->file != InvalidFileHandle) + d->readOnly = false; + else + d->file = openFile(fileDescriptor, true); + + if(d->file == InvalidFileHandle) + debug("Could not open file using file descriptor"); } FileStream::~FileStream() @@ -258,8 +283,7 @@ void FileStream::insert(const ByteVector &data, long long start, size_t replace) ByteVector buffer = data; ByteVector aboutToOverwrite(bufferLength); - while(true) - { + while(true) { // Seek to the current read position and read the data that we're about // to overwrite. Appropriately increment the readPosition. @@ -307,8 +331,7 @@ void FileStream::removeBlock(long long start, size_t length) ByteVector buffer(bufferLength, 0); - for(unsigned int bytesRead = -1; bytesRead != 0;) - { + for(unsigned int bytesRead = -1; bytesRead != 0;) { seek(readPosition); bytesRead = static_cast(readFile(d->file, buffer)); readPosition += bytesRead; @@ -478,9 +501,8 @@ void FileStream::truncate(long long length) #else const int error = ftruncate(fileno(d->file), length); - if(error != 0) { + if(error != 0) debug("FileStream::truncate() -- Coundn't truncate the file."); - } #endif } diff --git a/taglib/toolkit/tfilestream.h b/taglib/toolkit/tfilestream.h index f190bce7..b4c0fe95 100644 --- a/taglib/toolkit/tfilestream.h +++ b/taglib/toolkit/tfilestream.h @@ -52,6 +52,11 @@ namespace TagLib { */ FileStream(FileName file, bool openReadOnly = false); + /*! + * Construct a File object and opens the \a file using file descriptor. + */ + FileStream(int fileDescriptor, bool openReadOnly = false); + /*! * Destroys this FileStream instance. */ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 46bc9aec..5213d52c 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -27,6 +27,7 @@ INCLUDE_DIRECTORIES( ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/dsf ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/ebml ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/ebml/matroska + ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/dsdiff ) SET(test_runner_SRCS @@ -72,6 +73,7 @@ SET(test_runner_SRCS test_speex.cpp test_dsf.cpp test_matroska.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 00000000..9dc0b9ec Binary files /dev/null and b/tests/data/empty10ms.dff differ diff --git a/tests/data/empty10ms.dsf b/tests/data/empty10ms.dsf new file mode 100644 index 00000000..31a4d7c1 Binary files /dev/null and b/tests/data/empty10ms.dsf differ diff --git a/tests/test_dsdiff.cpp b/tests/test_dsdiff.cpp new file mode 100644 index 00000000..4b56bcf6 --- /dev/null +++ b/tests/test_dsdiff.cpp @@ -0,0 +1,116 @@ +#include +#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(8252LL, f.length()); + f.save(); + CPPUNIT_ASSERT_EQUAL(8252LL, 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 index b243b490..b71558d4 100644 --- a/tests/test_dsf.cpp +++ b/tests/test_dsf.cpp @@ -12,13 +12,14 @@ using namespace TagLib; class TestDSF : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(TestDSF); - CPPUNIT_TEST(testBasic); + CPPUNIT_TEST(testBasic1); + CPPUNIT_TEST(testBasic2); CPPUNIT_TEST(testTags); CPPUNIT_TEST_SUITE_END(); public: - void testBasic() + void testBasic1() { DSF::File f(TEST_FILE_PATH_C("empty.dsf")); CPPUNIT_ASSERT(f.audioProperties()); @@ -33,12 +34,29 @@ public: CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->channelType()); CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->bitsPerSample()); CPPUNIT_ASSERT_EQUAL((long long)0, f.audioProperties()->sampleCount()); + } + + void testBasic2() + { + 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("empty", ".dsf"); + ScopedFileCopy copy("empty10ms", ".dsf"); string newname = copy.fileName(); DSF::File *f = new DSF::File(newname.c_str()); diff --git a/tests/test_fileref.cpp b/tests/test_fileref.cpp index ee2d48d8..b81b3777 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(testFileResolver); CPPUNIT_TEST_SUITE_END(); @@ -287,6 +291,16 @@ public: { fileRefSave("alaw", ".aifc"); } + + void testDSF() + { + fileRefSave("empty10ms",".dsf"); + } + + void testDSDIFF() + { + fileRefSave("empty10ms",".dff"); + } void testUnsupported() { diff --git a/tests/test_synchdata.cpp b/tests/test_synchdata.cpp index 5b023ce1..7c84b375 100644 --- a/tests/test_synchdata.cpp +++ b/tests/test_synchdata.cpp @@ -75,8 +75,8 @@ public: void testToUIntBroken() { - char data[] = { 0, 0, 0, -1 }; - char data2[] = { 0, 0, -1, -1 }; + char data[] = { 0, 0, 0, (char)-1 }; + char data2[] = { 0, 0, (char)-1, (char)-1 }; CPPUNIT_ASSERT_EQUAL((unsigned int)255, ID3v2::SynchData::toUInt(ByteVector(data, 4))); CPPUNIT_ASSERT_EQUAL((unsigned int)65535, ID3v2::SynchData::toUInt(ByteVector(data2, 4))); @@ -84,7 +84,7 @@ public: void testToUIntBrokenAndTooLarge() { - char data[] = { 0, 0, 0, -1, 0 }; + char data[] = { 0, 0, 0, (char)-1, 0 }; ByteVector v(data, 5); CPPUNIT_ASSERT_EQUAL((unsigned int)255, ID3v2::SynchData::toUInt(v));