diff options
29 files changed, 671 insertions, 114 deletions
diff --git a/.travis.yml b/.travis.yml index 88836480..e7f1b2a0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,6 +12,9 @@ compiler: - gcc - clang +arch: + - ppc64le + addons: apt: packages: diff --git a/ConfigureChecks.cmake b/ConfigureChecks.cmake index 5e5fca2f..bcdbfe20 100644 --- a/ConfigureChecks.cmake +++ b/ConfigureChecks.cmake @@ -37,17 +37,6 @@ endif() # Determine which kind of atomic operations your compiler supports. check_cxx_source_compiles(" - #include <atomic> - int main() { - std::atomic_int x(1); - ++x; - --x; - return 0; - } -" HAVE_STD_ATOMIC) - -if(NOT HAVE_STD_ATOMIC) - check_cxx_source_compiles(" int main() { volatile int x; __sync_add_and_fetch(&x, 1); @@ -56,8 +45,8 @@ if(NOT HAVE_STD_ATOMIC) } " HAVE_GCC_ATOMIC) - if(NOT HAVE_GCC_ATOMIC) - check_cxx_source_compiles(" +if(NOT HAVE_GCC_ATOMIC) + check_cxx_source_compiles(" #include <libkern/OSAtomic.h> int main() { volatile int32_t x; @@ -67,8 +56,8 @@ if(NOT HAVE_STD_ATOMIC) } " HAVE_MAC_ATOMIC) - if(NOT HAVE_MAC_ATOMIC) - check_cxx_source_compiles(" + if(NOT HAVE_MAC_ATOMIC) + check_cxx_source_compiles(" #include <windows.h> int main() { volatile LONG x; @@ -78,8 +67,8 @@ if(NOT HAVE_STD_ATOMIC) } " HAVE_WIN_ATOMIC) - if(NOT HAVE_WIN_ATOMIC) - check_cxx_source_compiles(" + if(NOT HAVE_WIN_ATOMIC) + check_cxx_source_compiles(" #include <ia64intrin.h> int main() { volatile int x; @@ -88,7 +77,6 @@ if(NOT HAVE_STD_ATOMIC) return 0; } " HAVE_IA64_ATOMIC) - endif() endif() endif() endif() diff --git a/config.h.cmake b/config.h.cmake index c1a31094..8d8c36ab 100644 --- a/config.h.cmake +++ b/config.h.cmake @@ -11,7 +11,6 @@ #cmakedefine HAVE_OPENBSD_BYTESWAP 1 /* Defined if your compiler supports some atomic operations */ -#cmakedefine HAVE_STD_ATOMIC 1 #cmakedefine HAVE_GCC_ATOMIC 1 #cmakedefine HAVE_MAC_ATOMIC 1 #cmakedefine HAVE_WIN_ATOMIC 1 diff --git a/taglib-config.cmake b/taglib-config.cmake index 2a5c19f9..d500fe60 100644 --- a/taglib-config.cmake +++ b/taglib-config.cmake @@ -16,8 +16,8 @@ EOH prefix=@CMAKE_INSTALL_PREFIX@ exec_prefix=@CMAKE_INSTALL_PREFIX@ -libdir=${exec_prefix}/lib -includedir=${prefix}/include +libdir=@LIB_INSTALL_DIR@ +includedir=@INCLUDE_INSTALL_DIR@ flags="" @@ -32,7 +32,7 @@ do flags="$flags -L$libdir -ltag @ZLIB_LIBRARIES_FLAGS@" ;; --cflags) - flags="$flags -I$includedir/taglib" + flags="$flags -I$includedir -I$includedir/taglib" ;; --version) echo @TAGLIB_LIB_VERSION_STRING@ diff --git a/taglib-config.cmd.cmake b/taglib-config.cmd.cmake index bbf3cf84..1b807ec8 100644 --- a/taglib-config.cmd.cmake +++ b/taglib-config.cmd.cmake @@ -28,7 +28,7 @@ goto theend * It would be preferable if the top level CMakeLists.txt provided the library name during config. ??
:doit
if /i "%1#" == "--libs#" echo -L${LIB_INSTALL_DIR} -llibtag
-if /i "%1#" == "--cflags#" echo -I${INCLUDE_INSTALL_DIR}/taglib
+if /i "%1#" == "--cflags#" echo -I${INCLUDE_INSTALL_DIR} -I${INCLUDE_INSTALL_DIR}/taglib
if /i "%1#" == "--version#" echo ${TAGLIB_LIB_VERSION_STRING}
if /i "%1#" == "--prefix#" echo ${CMAKE_INSTALL_PREFIX}
diff --git a/taglib.pc.cmake b/taglib.pc.cmake index 9f56a3e6..71ee09af 100644 --- a/taglib.pc.cmake +++ b/taglib.pc.cmake @@ -8,4 +8,4 @@ Description: Audio meta-data library Requires: Version: @TAGLIB_LIB_VERSION_STRING@ Libs: -L${libdir} -ltag @ZLIB_LIBRARIES_FLAGS@ -Cflags: -I${includedir}/taglib +Cflags: -I${includedir} -I${includedir}/taglib diff --git a/taglib/CMakeLists.txt b/taglib/CMakeLists.txt index fcff0b34..52c6c32a 100644 --- a/taglib/CMakeLists.txt +++ b/taglib/CMakeLists.txt @@ -337,6 +337,7 @@ set(tag_LIB_SRCS ) add_library(tag ${tag_LIB_SRCS} ${tag_HDRS}) +set_property(TARGET tag PROPERTY CXX_STANDARD 98) if(HAVE_ZLIB AND NOT HAVE_ZLIB_SOURCE) target_link_libraries(tag ${ZLIB_LIBRARIES}) diff --git a/taglib/fileref.cpp b/taglib/fileref.cpp index 935c371b..ba8f4f59 100644 --- a/taglib/fileref.cpp +++ b/taglib/fileref.cpp @@ -27,6 +27,8 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ +#include <cstring> + #include <tfile.h> #include <tfilestream.h> #include <tstring.h> @@ -65,6 +67,8 @@ namespace File *detectByResolvers(FileName fileName, bool readAudioProperties, AudioProperties::ReadStyle audioPropertiesStyle) { + if(::strlen(fileName) == 0) + return 0; ResolverList::ConstIterator it = fileTypeResolvers.begin(); for(; it != fileTypeResolvers.end(); ++it) { File *file = (*it)->createFile(fileName, readAudioProperties, audioPropertiesStyle); @@ -460,7 +464,11 @@ void FileRef::parse(FileName fileName, bool readAudioProperties, void FileRef::parse(IOStream *stream, bool readAudioProperties, AudioProperties::ReadStyle audioPropertiesStyle) { - // User-defined resolvers won't work with a stream. + // Try user-defined resolvers. + + d->file = detectByResolvers(stream->name(), readAudioProperties, audioPropertiesStyle); + if(d->file) + return; // Try to resolve file types based on the file extension. diff --git a/taglib/flac/flacfile.cpp b/taglib/flac/flacfile.cpp index 7f437194..ada215db 100644 --- a/taglib/flac/flacfile.cpp +++ b/taglib/flac/flacfile.cpp @@ -187,16 +187,24 @@ bool FLAC::File::save() // Replace metadata blocks - for(BlockIterator it = d->blocks.begin(); it != d->blocks.end(); ++it) { + MetadataBlock *commentBlock = + new UnknownMetadataBlock(MetadataBlock::VorbisComment, d->xiphCommentData); + for(BlockIterator it = d->blocks.begin(); it != d->blocks.end();) { if((*it)->code() == MetadataBlock::VorbisComment) { - // Set the new Vorbis Comment block + // Remove the old Vorbis Comment block delete *it; - d->blocks.erase(it); - break; + it = d->blocks.erase(it); + continue; } + if(commentBlock && (*it)->code() == MetadataBlock::Picture) { + // Set the new Vorbis Comment block before the first picture block + d->blocks.insert(it, commentBlock); + commentBlock = 0; + } + ++it; } - - d->blocks.append(new UnknownMetadataBlock(MetadataBlock::VorbisComment, d->xiphCommentData)); + if(commentBlock) + d->blocks.append(commentBlock); // Render data for the metadata blocks diff --git a/taglib/mp4/mp4properties.cpp b/taglib/mp4/mp4properties.cpp index faa43c27..6c6976fa 100644 --- a/taglib/mp4/mp4properties.cpp +++ b/taglib/mp4/mp4properties.cpp @@ -31,6 +31,27 @@ using namespace TagLib; +namespace +{ + // Calculate the total bytes used by audio data, used to calculate the bitrate + long long calculateMdatLength(const MP4::AtomList &list) + { + long long totalLength = 0; + for(MP4::AtomList::ConstIterator it = list.begin(); it != list.end(); ++it) { + long length = (*it)->length; + if(length == 0) + return 0; // for safety, see checkValid() in mp4file.cpp + + if((*it)->name == "mdat") + totalLength += length; + + totalLength += calculateMdatLength((*it)->children); + } + + return totalLength; + } +} + class MP4::Properties::PropertiesPrivate { public: @@ -213,7 +234,14 @@ MP4::Properties::read(File *file, Atoms *atoms) pos += 3; } pos += 10; - d->bitrate = static_cast<int>((data.toUInt(pos) + 500) / 1000.0 + 0.5); + const unsigned int bitrateValue = data.toUInt(pos); + if(bitrateValue != 0 || d->length <= 0) { + d->bitrate = static_cast<int>((bitrateValue + 500) / 1000.0 + 0.5); + } + else { + d->bitrate = static_cast<int>( + (calculateMdatLength(atoms->atoms) * 8) / d->length); + } } } } @@ -224,6 +252,13 @@ MP4::Properties::read(File *file, Atoms *atoms) d->channels = data.at(73); d->bitrate = static_cast<int>(data.toUInt(80U) / 1000.0 + 0.5); d->sampleRate = data.toUInt(84U); + + if(d->bitrate == 0 && d->length > 0) { + // There are files which do not contain a nominal bitrate, e.g. those + // generated by refalac64.exe. Calculate the bitrate from the audio + // data size (mdat atoms) and the duration. + d->bitrate = (calculateMdatLength(atoms->atoms) * 8) / d->length; + } } } diff --git a/taglib/mp4/mp4tag.cpp b/taglib/mp4/mp4tag.cpp index bc95ad10..de41c58d 100644 --- a/taglib/mp4/mp4tag.cpp +++ b/taglib/mp4/mp4tag.cpp @@ -775,31 +775,41 @@ MP4::Tag::track() const void MP4::Tag::setTitle(const String &value) { - d->items["\251nam"] = StringList(value); + setTextItem("\251nam", value); } void MP4::Tag::setArtist(const String &value) { - d->items["\251ART"] = StringList(value); + setTextItem("\251ART", value); } void MP4::Tag::setAlbum(const String &value) { - d->items["\251alb"] = StringList(value); + setTextItem("\251alb", value); } void MP4::Tag::setComment(const String &value) { - d->items["\251cmt"] = StringList(value); + setTextItem("\251cmt", value); } void MP4::Tag::setGenre(const String &value) { - d->items["\251gen"] = StringList(value); + setTextItem("\251gen", value); +} + +void +MP4::Tag::setTextItem(const String &key, const String &value) +{ + if (!value.isEmpty()) { + d->items[key] = StringList(value); + } else { + d->items.erase(key); + } } void diff --git a/taglib/mp4/mp4tag.h b/taglib/mp4/mp4tag.h index e5b70af3..ccee8e06 100644 --- a/taglib/mp4/mp4tag.h +++ b/taglib/mp4/mp4tag.h @@ -106,6 +106,13 @@ namespace TagLib { void removeUnsupportedProperties(const StringList& properties); PropertyMap setProperties(const PropertyMap &properties); + protected: + /*! + * Sets the value of \a key to \a value, overwriting any previous value. + * If \a value is empty, the item is removed. + */ + void setTextItem(const String &key, const String &value); + private: AtomDataList parseData2(const Atom *atom, int expectedFlags = -1, bool freeForm = false); diff --git a/taglib/mpeg/id3v1/id3v1genres.cpp b/taglib/mpeg/id3v1/id3v1genres.cpp index 1c707c30..d72a2a75 100644 --- a/taglib/mpeg/id3v1/id3v1genres.cpp +++ b/taglib/mpeg/id3v1/id3v1genres.cpp @@ -59,7 +59,7 @@ namespace L"Ambient", L"Trip-Hop", L"Vocal", - L"Jazz+Funk", + L"Jazz-Funk", L"Fusion", L"Trance", L"Classical", @@ -111,16 +111,16 @@ namespace L"Rock & Roll", L"Hard Rock", L"Folk", - L"Folk/Rock", + L"Folk Rock", L"National Folk", L"Swing", - L"Fusion", - L"Bebob", + L"Fast Fusion", + L"Bebop", L"Latin", L"Revival", L"Celtic", L"Bluegrass", - L"Avantgarde", + L"Avant-garde", L"Gothic Rock", L"Progressive Rock", L"Psychedelic Rock", @@ -155,15 +155,15 @@ namespace L"Drum Solo", L"A Cappella", L"Euro-House", - L"Dance Hall", + L"Dancehall", L"Goa", L"Drum & Bass", L"Club-House", - L"Hardcore", + L"Hardcore Techno", L"Terror", L"Indie", - L"BritPop", - L"Negerpunk", + L"Britpop", + L"Worldbeat", L"Polsk Punk", L"Beat", L"Christian Gangsta Rap", @@ -261,5 +261,26 @@ int ID3v1::genreIndex(const String &name) return i; } + // If the name was not found, try the names which have been changed + static const struct { + const wchar_t *genre; + int code; + } fixUpGenres[] = { + { L"Jazz+Funk", 29 }, + { L"Folk/Rock", 81 }, + { L"Bebob", 85 }, + { L"Avantgarde", 90 }, + { L"Dance Hall", 125 }, + { L"Hardcore", 129 }, + { L"BritPop", 132 }, + { L"Negerpunk", 133 } + }; + static const int fixUpGenresSize = + sizeof(fixUpGenres) / sizeof(fixUpGenres[0]); + for(int i = 0; i < fixUpGenresSize; ++i) { + if(name == fixUpGenres[i].genre) + return fixUpGenres[i].code; + } + return 255; } diff --git a/taglib/mpeg/id3v2/id3v2tag.cpp b/taglib/mpeg/id3v2/id3v2tag.cpp index 8ee477d0..2d102395 100644 --- a/taglib/mpeg/id3v2/id3v2tag.cpp +++ b/taglib/mpeg/id3v2/id3v2tag.cpp @@ -24,7 +24,6 @@ ***************************************************************************/ #include <algorithm> -#include <array> #include <tfile.h> #include <tbytevector.h> @@ -55,6 +54,16 @@ namespace const long MinPaddingSize = 1024; const long MaxPaddingSize = 1024 * 1024; + + bool contains(const char **a, const ByteVector &v) + { + for(int i = 0; a[i]; i++) + { + if(v == a[i]) + return true; + } + return false; + } } class ID3v2::Tag::TagPrivate @@ -480,15 +489,15 @@ ByteVector ID3v2::Tag::render() const void ID3v2::Tag::downgradeFrames(FrameList *frames, FrameList *newFrames) const { #ifdef NO_ITUNES_HACKS - static const std::array<ByteVector, 13> unsupportedFrames = { + static const char *unsupportedFrames[] = { "ASPI", "EQU2", "RVA2", "SEEK", "SIGN", "TDRL", "TDTG", - "TMOO", "TPRO", "TSOA", "TSOT", "TSST", "TSOP" + "TMOO", "TPRO", "TSOA", "TSOT", "TSST", "TSOP", 0 }; #else // iTunes writes and reads TSOA, TSOT, TSOP to ID3v2.3. - static const std::array<ByteVector, 10> unsupportedFrames = { + static const char *unsupportedFrames[] = { "ASPI", "EQU2", "RVA2", "SEEK", "SIGN", "TDRL", "TDTG", - "TMOO", "TPRO", "TSST" + "TMOO", "TPRO", "TSST", 0 }; #endif ID3v2::TextIdentificationFrame *frameTDOR = 0; @@ -501,8 +510,7 @@ void ID3v2::Tag::downgradeFrames(FrameList *frames, FrameList *newFrames) const ID3v2::Frame *frame = *it; ByteVector frameID = frame->header()->frameID(); - if(std::find(unsupportedFrames.begin(), unsupportedFrames.end(), frameID) != - unsupportedFrames.end()) + if(contains(unsupportedFrames, frameID)) { debug("A frame that is not supported in ID3v2.3 \'" + String(frameID) + "\' has been discarded"); diff --git a/taglib/mpeg/mpegproperties.cpp b/taglib/mpeg/mpegproperties.cpp index d8b7bd50..5eec84f7 100644 --- a/taglib/mpeg/mpegproperties.cpp +++ b/taglib/mpeg/mpegproperties.cpp @@ -200,13 +200,14 @@ void MPEG::Properties::read(File *file) const long lastFrameOffset = file->lastFrameOffset(); if(lastFrameOffset < 0) { debug("MPEG::Properties::read() -- Could not find an MPEG frame in the stream."); - return; } - - const Header lastHeader(file, lastFrameOffset, false); - const long streamLength = lastFrameOffset - firstFrameOffset + lastHeader.frameLength(); - if(streamLength > 0) - d->length = static_cast<int>(streamLength * 8.0 / d->bitrate + 0.5); + else + { + const Header lastHeader(file, lastFrameOffset, false); + const long streamLength = lastFrameOffset - firstFrameOffset + lastHeader.frameLength(); + if (streamLength > 0) + d->length = static_cast<int>(streamLength * 8.0 / d->bitrate + 0.5); + } } d->sampleRate = firstHeader.sampleRate(); diff --git a/taglib/riff/rifffile.cpp b/taglib/riff/rifffile.cpp index d3e1aa21..005551f4 100644 --- a/taglib/riff/rifffile.cpp +++ b/taglib/riff/rifffile.cpp @@ -302,13 +302,11 @@ void RIFF::File::read() if(!isValidChunkName(chunkName)) { debug("RIFF::File::read() -- Chunk '" + chunkName + "' has invalid ID"); - setValid(false); break; } if(static_cast<long long>(offset) + 8 + chunkSize > length()) { debug("RIFF::File::read() -- Chunk '" + chunkName + "' has invalid size (larger than the file size)"); - setValid(false); break; } diff --git a/taglib/riff/wav/wavproperties.cpp b/taglib/riff/wav/wavproperties.cpp index 4174db46..812da7d2 100644 --- a/taglib/riff/wav/wavproperties.cpp +++ b/taglib/riff/wav/wavproperties.cpp @@ -35,7 +35,8 @@ namespace enum WaveFormat { FORMAT_UNKNOWN = 0x0000, - FORMAT_PCM = 0x0001 + FORMAT_PCM = 0x0001, + FORMAT_IEEE_FLOAT = 0x0003 }; } @@ -191,7 +192,7 @@ void RIFF::WAV::Properties::read(File *file) } d->format = data.toShort(24, false); } - if(d->format != FORMAT_PCM && totalSamples == 0) { + if(d->format != FORMAT_PCM && d->format != FORMAT_IEEE_FLOAT && totalSamples == 0) { debug("RIFF::WAV::Properties::read() - Non-PCM format, but 'fact' chunk not found."); return; } @@ -200,7 +201,7 @@ void RIFF::WAV::Properties::read(File *file) d->sampleRate = data.toUInt(4, false); d->bitsPerSample = data.toShort(14, false); - if(d->format != FORMAT_PCM) + if(d->format != FORMAT_PCM && !(d->format == FORMAT_IEEE_FLOAT && totalSamples == 0)) d->sampleFrames = totalSamples; else if(d->channels > 0 && d->bitsPerSample > 0) d->sampleFrames = streamLength / (d->channels * ((d->bitsPerSample + 7) / 8)); diff --git a/taglib/toolkit/trefcounter.cpp b/taglib/toolkit/trefcounter.cpp index 6638fcaa..18cb596c 100644 --- a/taglib/toolkit/trefcounter.cpp +++ b/taglib/toolkit/trefcounter.cpp @@ -29,12 +29,7 @@ #include "trefcounter.h" -#if defined(HAVE_STD_ATOMIC) -# include <atomic> -# define ATOMIC_INT std::atomic_int -# define ATOMIC_INC(x) (++x) -# define ATOMIC_DEC(x) (--x) -#elif defined(HAVE_GCC_ATOMIC) +#if defined(HAVE_GCC_ATOMIC) # define ATOMIC_INT int # define ATOMIC_INC(x) __sync_add_and_fetch(&x, 1) # define ATOMIC_DEC(x) __sync_sub_and_fetch(&x, 1) @@ -57,7 +52,7 @@ # define ATOMIC_INC(x) __sync_add_and_fetch(&x, 1) # define ATOMIC_DEC(x) __sync_sub_and_fetch(&x, 1) #else -# define ATOMIC_INT int +# define ATOMIC_INT volatile int # define ATOMIC_INC(x) (++x) # define ATOMIC_DEC(x) (--x) #endif diff --git a/taglib/wavpack/wavpackproperties.cpp b/taglib/wavpack/wavpackproperties.cpp index c1d04fd2..d5808be5 100644 --- a/taglib/wavpack/wavpackproperties.cpp +++ b/taglib/wavpack/wavpackproperties.cpp @@ -27,6 +27,7 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ +#include <stdint.h> #include <tstring.h> #include <tdebug.h> @@ -138,16 +139,10 @@ unsigned int WavPack::Properties::sampleFrames() const // private members //////////////////////////////////////////////////////////////////////////////// -namespace -{ - const unsigned int sample_rates[] = { - 6000, 8000, 9600, 11025, 12000, 16000, 22050, 24000, - 32000, 44100, 48000, 64000, 88200, 96000, 192000, 0 }; -} - #define BYTES_STORED 3 #define MONO_FLAG 4 -#define LOSSLESS_FLAG 8 +#define HYBRID_FLAG 8 +#define DSD_FLAG 0x80000000 // block is encoded DSD (1-bit PCM) #define SHIFT_LSB 13 #define SHIFT_MASK (0x1fL << SHIFT_LSB) @@ -158,8 +153,110 @@ namespace #define MIN_STREAM_VERS 0x402 #define MAX_STREAM_VERS 0x410 +#define INITIAL_BLOCK 0x800 #define FINAL_BLOCK 0x1000 +#define ID_DSD_BLOCK 0x0e +#define ID_OPTIONAL_DATA 0x20 +#define ID_UNIQUE 0x3f +#define ID_ODD_SIZE 0x40 +#define ID_LARGE 0x80 +#define ID_SAMPLE_RATE (ID_OPTIONAL_DATA | 0x7) + +namespace +{ + const unsigned int sampleRates[] = { + 6000, 8000, 9600, 11025, 12000, 16000, 22050, 24000, + 32000, 44100, 48000, 64000, 88200, 96000, 192000, 0 }; + + /*! + * Given a WavPack \a block (complete, but not including the 32-byte header), + * parse the metadata blocks until an \a id block is found and return the + * contained data, or zero if no such block is found. + * Supported values for \a id are ID_SAMPLE_RATE and ID_DSD_BLOCK. + */ + int getMetaDataChunk(const ByteVector &block, unsigned char id) + { + if(id != ID_SAMPLE_RATE && id != ID_DSD_BLOCK) + return 0; + + const int blockSize = static_cast<int>(block.size()); + int index = 0; + + while(index + 1 < blockSize) { + const unsigned char metaId = static_cast<unsigned char>(block[index]); + int metaBc = static_cast<unsigned char>(block[index + 1]) << 1; + index += 2; + + if(metaId & ID_LARGE) { + if(index + 2 > blockSize) + return 0; + + metaBc += (static_cast<uint32_t>(static_cast<unsigned char>(block[index])) << 9) + + (static_cast<uint32_t>(static_cast<unsigned char>(block[index + 1])) << 17); + index += 2; + } + + if(index + metaBc > blockSize) + return 0; + + // if we got a sample rate, return it + + if(id == ID_SAMPLE_RATE && (metaId & ID_UNIQUE) == ID_SAMPLE_RATE && metaBc == 4) { + int sampleRate = static_cast<int32_t>(static_cast<unsigned char>(block[index])); + sampleRate |= static_cast<int32_t>(static_cast<unsigned char>(block[index + 1])) << 8; + sampleRate |= static_cast<int32_t>(static_cast<unsigned char>(block[index + 2])) << 16; + + // only use 4th byte if it's really there + + if(!(metaId & ID_ODD_SIZE)) + sampleRate |= static_cast<int32_t>(static_cast<unsigned char>(block[index + 3]) & 0x7f) << 24; + + return sampleRate; + } + + // if we got DSD block, return the specified rate shift amount + + if(id == ID_DSD_BLOCK && (metaId & ID_UNIQUE) == ID_DSD_BLOCK && metaBc > 0) { + const unsigned char rateShift = static_cast<unsigned char>(block[index]); + if(rateShift <= 31) + return rateShift; + } + + index += metaBc; + } + + return 0; + } + + /*! + * Given a WavPack block (complete, but not including the 32-byte header), + * parse the metadata blocks until an ID_SAMPLE_RATE block is found and + * return the non-standard sample rate contained there, or zero if no such + * block is found. + */ + int getNonStandardRate(const ByteVector &block) + { + return getMetaDataChunk(block, ID_SAMPLE_RATE); + } + + /*! + * Given a WavPack block (complete, but not including the 32-byte header), + * parse the metadata blocks until a DSD audio data block is found and return + * the sample-rate shift value contained there, or zero if no such block is + * found. The nominal sample rate of DSD audio files (found in the header) + * must be left-shifted by this amount to get the actual "byte" sample rate. + * Note that 8-bit bytes are the "atoms" of the DSD audio coding (for + * decoding, seeking, etc), so the shifted rate must be further multiplied by + * 8 to get the actual DSD bit sample rate. + */ + int getDsdRateShifter(const ByteVector &block) + { + return getMetaDataChunk(block, ID_DSD_BLOCK); + } + +} + void WavPack::Properties::read(File *file, long streamLength) { long offset = 0; @@ -178,17 +275,50 @@ void WavPack::Properties::read(File *file, long streamLength) break; } + const unsigned int blockSize = data.toUInt(4, false); + const unsigned int sampleFrames = data.toUInt(12, false); + const unsigned int blockSamples = data.toUInt(20, false); const unsigned int flags = data.toUInt(24, false); + unsigned int sampleRate = sampleRates[(flags & SRATE_MASK) >> SRATE_LSB]; + + if(!blockSamples) { // ignore blocks with no samples + offset += blockSize + 8; + continue; + } + + if(blockSize < 24 || blockSize > 1048576) { + debug("WavPack::Properties::read() -- Invalid block header found."); + break; + } + + // For non-standard sample rates or DSD audio files, we must read and parse the block + // to actually determine the sample rate. + + if(!sampleRate || (flags & DSD_FLAG)) { + const unsigned int adjustedBlockSize = blockSize - 24; + const ByteVector block = file->readBlock(adjustedBlockSize); + + if(block.size() < adjustedBlockSize) { + debug("WavPack::Properties::read() -- block is too short."); + break; + } + + if(!sampleRate) + sampleRate = static_cast<unsigned int>(getNonStandardRate(block)); + + if(sampleRate && (flags & DSD_FLAG)) + sampleRate <<= getDsdRateShifter(block); + } - if(offset == 0) { + if(flags & INITIAL_BLOCK) { d->version = data.toShort(8, false); if(d->version < MIN_STREAM_VERS || d->version > MAX_STREAM_VERS) break; d->bitsPerSample = ((flags & BYTES_STORED) + 1) * 8 - ((flags & SHIFT_MASK) >> SHIFT_LSB); - d->sampleRate = sample_rates[(flags & SRATE_MASK) >> SRATE_LSB]; - d->lossless = !(flags & LOSSLESS_FLAG); - d->sampleFrames = data.toUInt(12, false); + d->sampleRate = static_cast<int>(sampleRate); + d->lossless = !(flags & HYBRID_FLAG); + d->sampleFrames = sampleFrames; } d->channels += (flags & MONO_FLAG) ? 1 : 2; @@ -196,7 +326,6 @@ void WavPack::Properties::read(File *file, long streamLength) if(flags & FINAL_BLOCK) break; - const unsigned int blockSize = data.toUInt(4, false); offset += blockSize + 8; } @@ -212,25 +341,34 @@ void WavPack::Properties::read(File *file, long streamLength) unsigned int WavPack::Properties::seekFinalIndex(File *file, long streamLength) { - const long offset = file->rfind("wvpk", streamLength); - if(offset == -1) - return 0; + long offset = streamLength; - file->seek(offset); - const ByteVector data = file->readBlock(32); - if(data.size() < 32) - return 0; + while (offset >= 32) { + offset = file->rfind("wvpk", offset - 4); - const int version = data.toShort(8, false); - if(version < MIN_STREAM_VERS || version > MAX_STREAM_VERS) - return 0; + if(offset == -1) + return 0; - const unsigned int flags = data.toUInt(24, false); - if(!(flags & FINAL_BLOCK)) - return 0; + file->seek(offset); + const ByteVector data = file->readBlock(32); + if(data.size() < 32) + return 0; + + const unsigned int blockSize = data.toUInt(4, false); + const unsigned int blockIndex = data.toUInt(16, false); + const unsigned int blockSamples = data.toUInt(20, false); + const unsigned int flags = data.toUInt(24, false); + const int version = data.toShort(8, false); - const unsigned int blockIndex = data.toUInt(16, false); - const unsigned int blockSamples = data.toUInt(20, false); + // try not to trigger on a spurious "wvpk" in WavPack binary block data + + if(version < MIN_STREAM_VERS || version > MAX_STREAM_VERS || (blockSize & 1) || + blockSize < 24 || blockSize >= 1048576 || blockSamples > 131072) + continue; + + if (blockSamples && (flags & FINAL_BLOCK)) + return blockIndex + blockSamples; + } - return blockIndex + blockSamples; + return 0; } diff --git a/tests/data/dsd_stereo.wv b/tests/data/dsd_stereo.wv Binary files differnew file mode 100644 index 00000000..80619270 --- /dev/null +++ b/tests/data/dsd_stereo.wv diff --git a/tests/data/non_standard_rate.wv b/tests/data/non_standard_rate.wv Binary files differnew file mode 100644 index 00000000..ccc90277 --- /dev/null +++ b/tests/data/non_standard_rate.wv diff --git a/tests/plainfile.h b/tests/plainfile.h new file mode 100644 index 00000000..6147b56b --- /dev/null +++ b/tests/plainfile.h @@ -0,0 +1,50 @@ +/*************************************************************************** + copyright : (C) 2015 by Tsuda Kageyu + email : tsuda.kageyu@gmail.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_PLAINFILE_H +#define TAGLIB_PLAINFILE_H + +#include <tfile.h> + +using namespace TagLib; + +//! File subclass that gives tests access to filesystem operations +class PlainFile : public File { +public: + explicit PlainFile(FileName name) : File(name) { } + Tag *tag() const { return NULL; } + AudioProperties *audioProperties() const { return NULL; } + bool save() { return false; } + void truncate(long length) { File::truncate(length); } + + ByteVector readAll() { + seek(0, End); + long end = tell(); + seek(0); + return readBlock(end); + } +}; + +#endif diff --git a/tests/test_aiff.cpp b/tests/test_aiff.cpp index 116c13a6..0337729f 100644 --- a/tests/test_aiff.cpp +++ b/tests/test_aiff.cpp @@ -148,13 +148,13 @@ public: void testFuzzedFile1() { RIFF::AIFF::File f(TEST_FILE_PATH_C("segfault.aif")); - CPPUNIT_ASSERT(!f.isValid()); + CPPUNIT_ASSERT(f.isValid()); } void testFuzzedFile2() { RIFF::AIFF::File f(TEST_FILE_PATH_C("excessive_alloc.aif")); - CPPUNIT_ASSERT(!f.isValid()); + CPPUNIT_ASSERT(f.isValid()); } }; diff --git a/tests/test_file.cpp b/tests/test_file.cpp index 9aae07fe..ef8c1b10 100644 --- a/tests/test_file.cpp +++ b/tests/test_file.cpp @@ -25,20 +25,11 @@ #include <tfile.h> #include <cppunit/extensions/HelperMacros.h> +#include "plainfile.h" #include "utils.h" using namespace TagLib; -// File subclass that gives tests access to filesystem operations -class PlainFile : public File { -public: - PlainFile(FileName name) : File(name) { } - Tag *tag() const { return NULL; } - AudioProperties *audioProperties() const { return NULL; } - bool save(){ return false; } - void truncate(long length) { File::truncate(length); } -}; - class TestFile : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(TestFile); diff --git a/tests/test_flac.cpp b/tests/test_flac.cpp index 0cc2b7ec..7c25a83b 100644 --- a/tests/test_flac.cpp +++ b/tests/test_flac.cpp @@ -34,6 +34,7 @@ #include <id3v1tag.h> #include <id3v2tag.h> #include <cppunit/extensions/HelperMacros.h> +#include "plainfile.h" #include "utils.h" using namespace std; @@ -64,6 +65,7 @@ class TestFLAC : public CppUnit::TestFixture CPPUNIT_TEST(testStripTags); CPPUNIT_TEST(testRemoveXiphField); CPPUNIT_TEST(testEmptySeekTable); + CPPUNIT_TEST(testPictureStoredAfterComment); CPPUNIT_TEST_SUITE_END(); public: @@ -533,6 +535,83 @@ public: } } + void testPictureStoredAfterComment() + { + // Blank.png from https://commons.wikimedia.org/wiki/File:Blank.png + const unsigned char blankPngData[] = { + 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, + 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, + 0x08, 0x06, 0x00, 0x00, 0x00, 0x9d, 0x74, 0x66, 0x1a, 0x00, 0x00, 0x00, + 0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xae, 0xce, 0x1c, 0xe9, 0x00, 0x00, + 0x00, 0x04, 0x67, 0x41, 0x4d, 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, + 0x61, 0x05, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, + 0x0e, 0xc3, 0x00, 0x00, 0x0e, 0xc3, 0x01, 0xc7, 0x6f, 0xa8, 0x64, 0x00, + 0x00, 0x00, 0x0c, 0x49, 0x44, 0x41, 0x54, 0x18, 0x57, 0x63, 0xc0, 0x01, + 0x18, 0x18, 0x00, 0x00, 0x1a, 0x00, 0x01, 0x82, 0x92, 0x4d, 0x60, 0x00, + 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 + }; + const ByteVector picData(reinterpret_cast<const char *>(blankPngData), + sizeof(blankPngData)); + + ScopedFileCopy copy("no-tags", ".flac"); + { + FLAC::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT(!f.hasID3v1Tag()); + CPPUNIT_ASSERT(!f.hasID3v2Tag()); + CPPUNIT_ASSERT(!f.hasXiphComment()); + CPPUNIT_ASSERT(f.pictureList().isEmpty()); + + FLAC::Picture *pic = new FLAC::Picture; + pic->setData(picData); + pic->setType(FLAC::Picture::FrontCover); + pic->setMimeType("image/png"); + pic->setDescription("blank.png"); + pic->setWidth(3); + pic->setHeight(2); + pic->setColorDepth(32); + pic->setNumColors(0); + f.addPicture(pic); + f.xiphComment(true)->setTitle("Title"); + f.save(); + } + { + FLAC::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT(!f.hasID3v1Tag()); + CPPUNIT_ASSERT(!f.hasID3v2Tag()); + CPPUNIT_ASSERT(f.hasXiphComment()); + const List<FLAC::Picture *> pictures = f.pictureList(); + CPPUNIT_ASSERT_EQUAL(1U, pictures.size()); + CPPUNIT_ASSERT_EQUAL(picData, pictures[0]->data()); + CPPUNIT_ASSERT_EQUAL(FLAC::Picture::FrontCover, pictures[0]->type()); + CPPUNIT_ASSERT_EQUAL(String("image/png"), pictures[0]->mimeType()); + CPPUNIT_ASSERT_EQUAL(String("blank.png"), pictures[0]->description()); + CPPUNIT_ASSERT_EQUAL(3, pictures[0]->width()); + CPPUNIT_ASSERT_EQUAL(2, pictures[0]->height()); + CPPUNIT_ASSERT_EQUAL(32, pictures[0]->colorDepth()); + CPPUNIT_ASSERT_EQUAL(0, pictures[0]->numColors()); + CPPUNIT_ASSERT_EQUAL(String("Title"), f.xiphComment(false)->title()); + } + + const unsigned char expectedHeadData[] = { + 'f', 'L', 'a', 'C', 0x00, 0x00, 0x00, 0x22, 0x12, 0x00, 0x12, 0x00, + 0x00, 0x00, 0x0e, 0x00, 0x00, 0x10, 0x0a, 0xc4, 0x42, 0xf0, 0x00, 0x02, + 0x7a, 0xc0, 0xa1, 0xb1, 0x41, 0xf7, 0x66, 0xe9, 0x84, 0x9a, 0xc3, 0xdb, + 0x10, 0x30, 0xa2, 0x0a, 0x3c, 0x77, 0x04, 0x00, 0x00, 0x17, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 'T', 'I', + 'T', 'L', 'E', '=', 'T', 'i', 't', 'l', 'e', 0x06, 0x00, 0x00, + 0xa9, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x09, 'i', 'm', 'a', + 'g', 'e', '/', 'p', 'n', 'g', 0x00, 0x00, 0x00, 0x09, 'b', 'l', + 'a', 'n', 'k', '.', 'p', 'n', 'g', 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x77 + }; + ByteVector expectedData(reinterpret_cast<const char *>(expectedHeadData), + sizeof(expectedHeadData)); + expectedData.append(picData); + const ByteVector fileData = PlainFile(copy.fileName().c_str()).readAll(); + CPPUNIT_ASSERT(fileData.startsWith(expectedData)); + } + }; CPPUNIT_TEST_SUITE_REGISTRATION(TestFLAC); diff --git a/tests/test_id3v1.cpp b/tests/test_id3v1.cpp index 3358aead..c50c3428 100644 --- a/tests/test_id3v1.cpp +++ b/tests/test_id3v1.cpp @@ -40,6 +40,7 @@ class TestID3v1 : public CppUnit::TestFixture CPPUNIT_TEST_SUITE(TestID3v1); CPPUNIT_TEST(testStripWhiteSpace); CPPUNIT_TEST(testGenres); + CPPUNIT_TEST(testRenamedGenres); CPPUNIT_TEST_SUITE_END(); public: @@ -68,6 +69,18 @@ public: CPPUNIT_ASSERT_EQUAL(100, ID3v1::genreIndex("Humour")); } + void testRenamedGenres() + { + CPPUNIT_ASSERT_EQUAL(String("Bebop"), ID3v1::genre(85)); + CPPUNIT_ASSERT_EQUAL(85, ID3v1::genreIndex("Bebop")); + CPPUNIT_ASSERT_EQUAL(85, ID3v1::genreIndex("Bebob")); + + ID3v1::Tag tag; + tag.setGenre("Hardcore"); + CPPUNIT_ASSERT_EQUAL(String("Hardcore Techno"), tag.genre()); + CPPUNIT_ASSERT_EQUAL(129U, tag.genreNumber()); + } + }; CPPUNIT_TEST_SUITE_REGISTRATION(TestID3v1); diff --git a/tests/test_mp4.cpp b/tests/test_mp4.cpp index 05bf6bd3..ba38c0f0 100644 --- a/tests/test_mp4.cpp +++ b/tests/test_mp4.cpp @@ -28,10 +28,12 @@ #include <tag.h> #include <mp4tag.h> #include <tbytevectorlist.h> +#include <tbytevectorstream.h> #include <tpropertymap.h> #include <mp4atom.h> #include <mp4file.h> #include <cppunit/extensions/HelperMacros.h> +#include "plainfile.h" #include "utils.h" using namespace std; @@ -41,7 +43,9 @@ class TestMP4 : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(TestMP4); CPPUNIT_TEST(testPropertiesAAC); + CPPUNIT_TEST(testPropertiesAACWithoutBitrate); CPPUNIT_TEST(testPropertiesALAC); + CPPUNIT_TEST(testPropertiesALACWithoutBitrate); CPPUNIT_TEST(testPropertiesM4V); CPPUNIT_TEST(testFreeForm); CPPUNIT_TEST(testCheckValid); @@ -59,6 +63,7 @@ class TestMP4 : public CppUnit::TestFixture CPPUNIT_TEST(testFuzzedFile); CPPUNIT_TEST(testRepeatedSave); CPPUNIT_TEST(testWithZeroLengthAtom); + CPPUNIT_TEST(testEmptyValuesRemoveItems); CPPUNIT_TEST_SUITE_END(); public: @@ -77,6 +82,28 @@ public: CPPUNIT_ASSERT_EQUAL(MP4::Properties::AAC, f.audioProperties()->codec()); } + void testPropertiesAACWithoutBitrate() + { + ByteVector aacData = PlainFile(TEST_FILE_PATH_C("has-tags.m4a")).readAll(); + CPPUNIT_ASSERT_GREATER(1960U, aacData.size()); + CPPUNIT_ASSERT_EQUAL(ByteVector("mp4a"), aacData.mid(1890, 4)); + // Set the bitrate to zero + for (int offset = 1956; offset < 1960; ++offset) { + aacData[offset] = 0; + } + ByteVectorStream aacStream(aacData); + MP4::File f(&aacStream); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3708, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(false, f.audioProperties()->isEncrypted()); + CPPUNIT_ASSERT_EQUAL(MP4::Properties::AAC, f.audioProperties()->codec()); + } + void testPropertiesALAC() { MP4::File f(TEST_FILE_PATH_C("empty_alac.m4a")); @@ -91,6 +118,28 @@ public: CPPUNIT_ASSERT_EQUAL(MP4::Properties::ALAC, f.audioProperties()->codec()); } + void testPropertiesALACWithoutBitrate() + { + ByteVector alacData = PlainFile(TEST_FILE_PATH_C("empty_alac.m4a")).readAll(); + CPPUNIT_ASSERT_GREATER(474U, alacData.size()); + CPPUNIT_ASSERT_EQUAL(ByteVector("alac"), alacData.mid(446, 4)); + // Set the bitrate to zero + for (int offset = 470; offset < 474; ++offset) { + alacData[offset] = 0; + } + ByteVectorStream alacStream(alacData); + MP4::File f(&alacStream); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3705, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(false, f.audioProperties()->isEncrypted()); + CPPUNIT_ASSERT_EQUAL(MP4::Properties::ALAC, f.audioProperties()->codec()); + } + void testPropertiesM4V() { MP4::File f(TEST_FILE_PATH_C("blank_video.m4v")); @@ -455,6 +504,64 @@ public: CPPUNIT_ASSERT_EQUAL(22050, f.audioProperties()->sampleRate()); } + void testEmptyValuesRemoveItems() + { + const MP4::File f(TEST_FILE_PATH_C("has-tags.m4a")); + MP4::Tag *tag = f.tag(); + const String testTitle("Title"); + const String testArtist("Artist"); + const String testAlbum("Album"); + const String testComment("Comment"); + const String testGenre("Genre"); + const String nullString; + const unsigned int testYear = 2020; + const unsigned int testTrack = 1; + const unsigned int zeroUInt = 0; + + tag->setTitle(testTitle); + CPPUNIT_ASSERT_EQUAL(testTitle, tag->title()); + CPPUNIT_ASSERT(tag->contains("\251nam")); + tag->setArtist(testArtist); + CPPUNIT_ASSERT_EQUAL(testArtist, tag->artist()); + CPPUNIT_ASSERT(tag->contains("\251ART")); + tag->setAlbum(testAlbum); + CPPUNIT_ASSERT_EQUAL(testAlbum, tag->album()); + CPPUNIT_ASSERT(tag->contains("\251alb")); + tag->setComment(testComment); + CPPUNIT_ASSERT_EQUAL(testComment, tag->comment()); + CPPUNIT_ASSERT(tag->contains("\251cmt")); + tag->setGenre(testGenre); + CPPUNIT_ASSERT_EQUAL(testGenre, tag->genre()); + CPPUNIT_ASSERT(tag->contains("\251gen")); + tag->setYear(testYear); + CPPUNIT_ASSERT_EQUAL(testYear, tag->year()); + CPPUNIT_ASSERT(tag->contains("\251day")); + tag->setTrack(testTrack); + CPPUNIT_ASSERT_EQUAL(testTrack, tag->track()); + CPPUNIT_ASSERT(tag->contains("trkn")); + + tag->setTitle(nullString); + CPPUNIT_ASSERT_EQUAL(nullString, tag->title()); + CPPUNIT_ASSERT(!tag->contains("\251nam")); + tag->setArtist(nullString); + CPPUNIT_ASSERT_EQUAL(nullString, tag->artist()); + CPPUNIT_ASSERT(!tag->contains("\251ART")); + tag->setAlbum(nullString); + CPPUNIT_ASSERT_EQUAL(nullString, tag->album()); + CPPUNIT_ASSERT(!tag->contains("\251alb")); + tag->setComment(nullString); + CPPUNIT_ASSERT_EQUAL(nullString, tag->comment()); + CPPUNIT_ASSERT(!tag->contains("\251cmt")); + tag->setGenre(nullString); + CPPUNIT_ASSERT_EQUAL(nullString, tag->genre()); + CPPUNIT_ASSERT(!tag->contains("\251gen")); + tag->setYear(zeroUInt); + CPPUNIT_ASSERT_EQUAL(zeroUInt, tag->year()); + CPPUNIT_ASSERT(!tag->contains("\251day")); + tag->setTrack(zeroUInt); + CPPUNIT_ASSERT_EQUAL(zeroUInt, tag->track()); + CPPUNIT_ASSERT(!tag->contains("trkn")); + } }; CPPUNIT_TEST_SUITE_REGISTRATION(TestMP4); diff --git a/tests/test_wav.cpp b/tests/test_wav.cpp index e94864ba..61081393 100644 --- a/tests/test_wav.cpp +++ b/tests/test_wav.cpp @@ -28,9 +28,12 @@ #include <id3v2tag.h> #include <infotag.h> #include <tbytevectorlist.h> +#include <tbytevectorstream.h> +#include <tfilestream.h> #include <tpropertymap.h> #include <wavfile.h> #include <cppunit/extensions/HelperMacros.h> +#include "plainfile.h" #include "utils.h" using namespace std; @@ -42,6 +45,7 @@ class TestWAV : public CppUnit::TestFixture CPPUNIT_TEST(testPCMProperties); CPPUNIT_TEST(testALAWProperties); CPPUNIT_TEST(testFloatProperties); + CPPUNIT_TEST(testFloatWithoutFactChunkProperties); CPPUNIT_TEST(testZeroSizeDataChunk); CPPUNIT_TEST(testID3v2Tag); CPPUNIT_TEST(testSaveID3v23); @@ -50,6 +54,7 @@ class TestWAV : public CppUnit::TestFixture CPPUNIT_TEST(testDuplicateTags); CPPUNIT_TEST(testFuzzedFile1); CPPUNIT_TEST(testFuzzedFile2); + CPPUNIT_TEST(testFileWithGarbageAppended); CPPUNIT_TEST(testStripAndProperties); CPPUNIT_TEST(testPCMWithFactChunk); CPPUNIT_TEST(testWaveFormatExtensible); @@ -99,10 +104,29 @@ public: CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->format()); } + void testFloatWithoutFactChunkProperties() + { + ByteVector wavData = PlainFile(TEST_FILE_PATH_C("float64.wav")).readAll(); + CPPUNIT_ASSERT_EQUAL(ByteVector("fact"), wavData.mid(36, 4)); + // Remove the fact chunk by renaming it to fakt + wavData[38] = 'k'; + ByteVectorStream wavStream(wavData); + RIFF::WAV::File f(&wavStream); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(97, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(5645, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(64, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(4281U, f.audioProperties()->sampleFrames()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->format()); + } + void testZeroSizeDataChunk() { RIFF::WAV::File f(TEST_FILE_PATH_C("zero-size-chunk.wav")); - CPPUNIT_ASSERT(!f.isValid()); + CPPUNIT_ASSERT(f.isValid()); } void testID3v2Tag() @@ -263,7 +287,17 @@ public: void testFuzzedFile1() { RIFF::WAV::File f1(TEST_FILE_PATH_C("infloop.wav")); - CPPUNIT_ASSERT(!f1.isValid()); + CPPUNIT_ASSERT(f1.isValid()); + // The file has problems: + // Chunk 'ISTt' has invalid size (larger than the file size). + // Its properties can nevertheless be read. + RIFF::WAV::Properties* properties = f1.audioProperties(); + CPPUNIT_ASSERT_EQUAL(1, properties->channels()); + CPPUNIT_ASSERT_EQUAL(88, properties->bitrate()); + CPPUNIT_ASSERT_EQUAL(8, properties->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(11025, properties->sampleRate()); + CPPUNIT_ASSERT(!f1.hasInfoTag()); + CPPUNIT_ASSERT(!f1.hasID3v2Tag()); } void testFuzzedFile2() @@ -272,6 +306,36 @@ public: CPPUNIT_ASSERT(f2.isValid()); } + void testFileWithGarbageAppended() + { + ScopedFileCopy copy("empty", ".wav"); + ByteVector contentsBeforeModification; + { + FileStream stream(copy.fileName().c_str()); + stream.seek(0, IOStream::End); + const char garbage[] = "12345678"; + stream.writeBlock(ByteVector(garbage, sizeof(garbage) - 1)); + stream.seek(0); + contentsBeforeModification = stream.readBlock(stream.length()); + } + { + RIFF::WAV::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT(f.isValid()); + f.ID3v2Tag()->setTitle("ID3v2 Title"); + f.InfoTag()->setTitle("INFO Title"); + CPPUNIT_ASSERT(f.save()); + } + { + RIFF::WAV::File f(copy.fileName().c_str()); + f.strip(); + } + { + FileStream stream(copy.fileName().c_str()); + ByteVector contentsAfterModification = stream.readBlock(stream.length()); + CPPUNIT_ASSERT_EQUAL(contentsBeforeModification, contentsAfterModification); + } + } + void testStripAndProperties() { ScopedFileCopy copy("empty", ".wav"); diff --git a/tests/test_wavpack.cpp b/tests/test_wavpack.cpp index 6c64f08d..591529fb 100644 --- a/tests/test_wavpack.cpp +++ b/tests/test_wavpack.cpp @@ -41,6 +41,8 @@ class TestWavPack : public CppUnit::TestFixture CPPUNIT_TEST_SUITE(TestWavPack); CPPUNIT_TEST(testNoLengthProperties); CPPUNIT_TEST(testMultiChannelProperties); + CPPUNIT_TEST(testDsdStereoProperties); + CPPUNIT_TEST(testNonStandardRateProperties); CPPUNIT_TEST(testTaggedProperties); CPPUNIT_TEST(testFuzzedFile); CPPUNIT_TEST(testStripAndProperties); @@ -79,6 +81,36 @@ public: CPPUNIT_ASSERT_EQUAL(1031, f.audioProperties()->version()); } + void testDsdStereoProperties() + { + WavPack::File f(TEST_FILE_PATH_C("dsd_stereo.wv")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(200, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(2096, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(8, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(true, f.audioProperties()->isLossless()); + CPPUNIT_ASSERT_EQUAL(352800, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(70560U, f.audioProperties()->sampleFrames()); + CPPUNIT_ASSERT_EQUAL(1040, f.audioProperties()->version()); + } + + void testNonStandardRateProperties() + { + WavPack::File f(TEST_FILE_PATH_C("non_standard_rate.wv")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3675, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(true, f.audioProperties()->isLossless()); + CPPUNIT_ASSERT_EQUAL(1000, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(3675U, f.audioProperties()->sampleFrames()); + CPPUNIT_ASSERT_EQUAL(1040, f.audioProperties()->version()); + } + void testTaggedProperties() { WavPack::File f(TEST_FILE_PATH_C("tagged.wv")); |