aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.travis.yml3
-rw-r--r--ConfigureChecks.cmake24
-rw-r--r--config.h.cmake1
-rw-r--r--taglib-config.cmake6
-rw-r--r--taglib-config.cmd.cmake2
-rw-r--r--taglib.pc.cmake2
-rw-r--r--taglib/CMakeLists.txt1
-rw-r--r--taglib/fileref.cpp10
-rw-r--r--taglib/flac/flacfile.cpp20
-rw-r--r--taglib/mp4/mp4properties.cpp37
-rw-r--r--taglib/mp4/mp4tag.cpp20
-rw-r--r--taglib/mp4/mp4tag.h7
-rw-r--r--taglib/mpeg/id3v1/id3v1genres.cpp39
-rw-r--r--taglib/mpeg/id3v2/id3v2tag.cpp22
-rw-r--r--taglib/mpeg/mpegproperties.cpp13
-rw-r--r--taglib/riff/rifffile.cpp2
-rw-r--r--taglib/riff/wav/wavproperties.cpp7
-rw-r--r--taglib/toolkit/trefcounter.cpp9
-rw-r--r--taglib/wavpack/wavpackproperties.cpp196
-rw-r--r--tests/data/dsd_stereo.wvbin0 -> 52595 bytes
-rw-r--r--tests/data/non_standard_rate.wvbin0 -> 132 bytes
-rw-r--r--tests/plainfile.h50
-rw-r--r--tests/test_aiff.cpp4
-rw-r--r--tests/test_file.cpp11
-rw-r--r--tests/test_flac.cpp79
-rw-r--r--tests/test_id3v1.cpp13
-rw-r--r--tests/test_mp4.cpp107
-rw-r--r--tests/test_wav.cpp68
-rw-r--r--tests/test_wavpack.cpp32
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
new file mode 100644
index 00000000..80619270
--- /dev/null
+++ b/tests/data/dsd_stereo.wv
Binary files differ
diff --git a/tests/data/non_standard_rate.wv b/tests/data/non_standard_rate.wv
new file mode 100644
index 00000000..ccc90277
--- /dev/null
+++ b/tests/data/non_standard_rate.wv
Binary files differ
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"));