diff --git a/.astylerc b/.astylerc new file mode 100644 index 00000000..f9fde098 --- /dev/null +++ b/.astylerc @@ -0,0 +1,18 @@ +--suffix=none +--style=kr +--indent=spaces=2 +--indent-col1-comments +--min-conditional-indent=0 +--attach-extern-c +--attach-namespaces +--indent-namespaces +--pad-oper +--unpad-paren +--align-pointer=name +--align-reference=name +--max-instatement-indent=40 +--break-closing-brackets +--remove-brackets +--convert-tabs +--max-code-length=100 +--break-after-logical diff --git a/.travis.yml b/.travis.yml index 76e58336..b526e274 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,12 @@ language: cpp +sudo: false compiler: - gcc - clang -install: sudo apt-get install libcppunit-dev zlib1g-dev +addons: + apt: + packages: + - libcppunit-dev + - zlib1g-dev script: cmake -DBUILD_TESTS=ON -DBUILD_EXAMPLES=ON . && make && make check diff --git a/CMakeLists.txt b/CMakeLists.txt index 6b018ff2..fe5f2855 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ if(NOT ${CMAKE_VERSION} VERSION_LESS 2.8.12) cmake_policy(SET CMP0022 OLD) endif() -list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules) +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules") option(ENABLE_STATIC "Make static version of libtag" OFF) if(ENABLE_STATIC) @@ -53,8 +53,8 @@ if (MSVC AND ENABLE_STATIC_RUNTIME) endif() set(TAGLIB_LIB_MAJOR_VERSION "1") -set(TAGLIB_LIB_MINOR_VERSION "9") -set(TAGLIB_LIB_PATCH_VERSION "1") +set(TAGLIB_LIB_MINOR_VERSION "10") +set(TAGLIB_LIB_PATCH_VERSION "0") set(TAGLIB_LIB_VERSION_STRING "${TAGLIB_LIB_MAJOR_VERSION}.${TAGLIB_LIB_MINOR_VERSION}.${TAGLIB_LIB_PATCH_VERSION}") @@ -62,9 +62,9 @@ set(TAGLIB_LIB_VERSION_STRING "${TAGLIB_LIB_MAJOR_VERSION}.${TAGLIB_LIB_MINOR_VE # 2. If any interfaces have been added, removed, or changed since the last update, increment current, and set revision to 0. # 3. If any interfaces have been added since the last public release, then increment age. # 4. If any interfaces have been removed since the last public release, then set age to 0. -set(TAGLIB_SOVERSION_CURRENT 15) +set(TAGLIB_SOVERSION_CURRENT 16) set(TAGLIB_SOVERSION_REVISION 0) -set(TAGLIB_SOVERSION_AGE 14) +set(TAGLIB_SOVERSION_AGE 15) math(EXPR TAGLIB_SOVERSION_MAJOR "${TAGLIB_SOVERSION_CURRENT} - ${TAGLIB_SOVERSION_AGE}") math(EXPR TAGLIB_SOVERSION_MINOR "${TAGLIB_SOVERSION_AGE}") @@ -73,18 +73,18 @@ math(EXPR TAGLIB_SOVERSION_PATCH "${TAGLIB_SOVERSION_REVISION}") include(ConfigureChecks.cmake) if(NOT WIN32) - configure_file(${CMAKE_CURRENT_SOURCE_DIR}/taglib-config.cmake ${CMAKE_CURRENT_BINARY_DIR}/taglib-config ) - install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/taglib-config DESTINATION ${BIN_INSTALL_DIR}) + configure_file("${CMAKE_CURRENT_SOURCE_DIR}/taglib-config.cmake" "${CMAKE_CURRENT_BINARY_DIR}/taglib-config") + install(PROGRAMS "${CMAKE_CURRENT_BINARY_DIR}/taglib-config" DESTINATION "${BIN_INSTALL_DIR}") endif() if(WIN32) - configure_file(${CMAKE_CURRENT_SOURCE_DIR}/taglib-config.cmd.cmake ${CMAKE_CURRENT_BINARY_DIR}/taglib-config.cmd ) - install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/taglib-config.cmd DESTINATION ${BIN_INSTALL_DIR}) + configure_file("${CMAKE_CURRENT_SOURCE_DIR}/taglib-config.cmd.cmake" "${CMAKE_CURRENT_BINARY_DIR}/taglib-config.cmd") + install(PROGRAMS "${CMAKE_CURRENT_BINARY_DIR}/taglib-config.cmd" DESTINATION "${BIN_INSTALL_DIR}") endif() -if(NOT WIN32 AND NOT BUILD_FRAMEWORK) - configure_file(${CMAKE_CURRENT_SOURCE_DIR}/taglib.pc.cmake ${CMAKE_CURRENT_BINARY_DIR}/taglib.pc ) - install(FILES ${CMAKE_CURRENT_BINARY_DIR}/taglib.pc DESTINATION ${LIB_INSTALL_DIR}/pkgconfig) +if(NOT BUILD_FRAMEWORK) + configure_file("${CMAKE_CURRENT_SOURCE_DIR}/taglib.pc.cmake" "${CMAKE_CURRENT_BINARY_DIR}/taglib.pc") + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/taglib.pc" DESTINATION "${LIB_INSTALL_DIR}/pkgconfig") endif() if(NOT HAVE_ZLIB AND ZLIB_SOURCE) @@ -93,7 +93,7 @@ if(NOT HAVE_ZLIB AND ZLIB_SOURCE) endif() include_directories(${CMAKE_CURRENT_BINARY_DIR}) -configure_file(config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config.h) +configure_file(config.h.cmake "${CMAKE_CURRENT_BINARY_DIR}/config.h") option(TRACE_IN_RELEASE "Output debug messages even in release mode" OFF) if(TRACE_IN_RELEASE) @@ -108,7 +108,7 @@ if(BUILD_TESTS) endif(BUILD_TESTS) add_subdirectory(examples) -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.cmake ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile) +configure_file("${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.cmake" "${CMAKE_CURRENT_BINARY_DIR}/Doxyfile") file(COPY doc/taglib.png DESTINATION doc) add_custom_target(docs doxygen) @@ -118,5 +118,7 @@ configure_file( "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" IMMEDIATE @ONLY) -add_custom_target(uninstall - COMMAND ${CMAKE_COMMAND} -P "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake") +if (NOT TARGET uninstall) + add_custom_target(uninstall + COMMAND "${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake") +endif() diff --git a/ConfigureChecks.cmake b/ConfigureChecks.cmake index 79ee60bc..60e6d090 100644 --- a/ConfigureChecks.cmake +++ b/ConfigureChecks.cmake @@ -1,80 +1,39 @@ -include(CheckIncludeFile) -include(CheckIncludeFiles) -include(CheckSymbolExists) -include(CheckFunctionExists) include(CheckLibraryExists) include(CheckTypeSize) include(CheckCXXCompilerFlag) include(CheckCXXSourceCompiles) -include(TestBigEndian) -include(TestFloatFormat) include(TestLargeFiles) -# Determine whether your compiler supports C++0x/C++11 and enable it if possible. -# This check covers GCC, Clang and ICC. - -if(NOT MSVC AND NOT CMAKE_CXX_FLAGS MATCHES "-std=^\\s+") - CHECK_CXX_COMPILER_FLAG("-std=c++11" COMPILER_FLAG_CXX11) - if(COMPILER_FLAG_CXX11) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") - else() - CHECK_CXX_COMPILER_FLAG("-std=c++0x" COMPILER_FLAG_CXX0X) - if(COMPILER_FLAG_CXX0X) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x") - endif() - endif() -endif() - # Check if the size of numeric types are suitable. check_type_size("short" SIZEOF_SHORT) if(NOT ${SIZEOF_SHORT} EQUAL 2) - MESSAGE(FATAL_ERROR "TagLib requires that short is 16-bit wide.") + message(FATAL_ERROR "TagLib requires that short is 16-bit wide.") endif() check_type_size("int" SIZEOF_INT) if(NOT ${SIZEOF_INT} EQUAL 4) - MESSAGE(FATAL_ERROR "TagLib requires that int is 32-bit wide.") + message(FATAL_ERROR "TagLib requires that int is 32-bit wide.") endif() check_type_size("long long" SIZEOF_LONGLONG) if(NOT ${SIZEOF_LONGLONG} EQUAL 8) - MESSAGE(FATAL_ERROR "TagLib requires that long long is 64-bit wide.") + message(FATAL_ERROR "TagLib requires that long long is 64-bit wide.") endif() check_type_size("wchar_t" SIZEOF_WCHAR_T) if(${SIZEOF_WCHAR_T} LESS 2) - MESSAGE(FATAL_ERROR "TagLib requires that wchar_t is sufficient to store a UTF-16 char.") + message(FATAL_ERROR "TagLib requires that wchar_t is sufficient to store a UTF-16 char.") endif() check_type_size("float" SIZEOF_FLOAT) if(NOT ${SIZEOF_FLOAT} EQUAL 4) - MESSAGE(FATAL_ERROR "TagLib requires that float is 32-bit wide.") + message(FATAL_ERROR "TagLib requires that float is 32-bit wide.") endif() check_type_size("double" SIZEOF_DOUBLE) if(NOT ${SIZEOF_DOUBLE} EQUAL 8) - MESSAGE(FATAL_ERROR "TagLib requires that double is 64-bit wide.") -endif() - -# Determine the CPU byte order. - -test_big_endian(IS_BIG_ENDIAN) -if(NOT IS_BIG_ENDIAN) - set(SYSTEM_BYTEORDER 1) -else() - set(SYSTEM_BYTEORDER 2) -endif() - -# Check if the format of floating point types are suitable. - -test_float_format(FP_IEEE754) -if(${FP_IEEE754} EQUAL 1) - set(FLOAT_BYTEORDER 1) -elseif(${FP_IEEE754} EQUAL 2) - set(FLOAT_BYTEORDER 2) -else() - MESSAGE(FATAL_ERROR "TagLib requires that floating point types are IEEE754 compliant.") + message(FATAL_ERROR "TagLib requires that double is 64-bit wide.") endif() # Determine whether your compiler supports large files. @@ -182,30 +141,16 @@ endif() # Determine which kind of byte swap functions your compiler supports. -# GCC's __builtin_bswap* should be checked individually -# because some of them can be missing depends on the GCC version. check_cxx_source_compiles(" int main() { __builtin_bswap16(0); - return 0; - } -" HAVE_GCC_BYTESWAP_16) - -check_cxx_source_compiles(" - int main() { __builtin_bswap32(0); - return 0; - } -" HAVE_GCC_BYTESWAP_32) - -check_cxx_source_compiles(" - int main() { __builtin_bswap64(0); return 0; } -" HAVE_GCC_BYTESWAP_64) +" HAVE_GCC_BYTESWAP) -if(NOT HAVE_GCC_BYTESWAP_16 OR NOT HAVE_GCC_BYTESWAP_32 OR NOT HAVE_GCC_BYTESWAP_64) +if(NOT HAVE_GCC_BYTESWAP) check_cxx_source_compiles(" #include int main() { @@ -253,18 +198,30 @@ if(NOT HAVE_GCC_BYTESWAP_16 OR NOT HAVE_GCC_BYTESWAP_32 OR NOT HAVE_GCC_BYTESWAP endif() endif() -# Determine whether your compiler supports snprintf or sprintf_s. +# Determine whether your compiler supports some safer version of vsprintf. check_cxx_source_compiles(" #include - int main() { char buf[20]; snprintf(buf, 20, \"%d\", 1); return 0; } -" HAVE_SNPRINTF) + #include + int main() { + char buf[20]; + va_list args; + vsnprintf(buf, 20, \"%d\", args); + return 0; + } +" HAVE_VSNPRINTF) -if(NOT HAVE_SNPRINTF) +if(NOT HAVE_VSNPRINTF) check_cxx_source_compiles(" #include - int main() { char buf[20]; sprintf_s(buf, \"%d\", 1); return 0; } - " HAVE_SPRINTF_S) + #include + int main() { + char buf[20]; + va_list args; + vsprintf_s(buf, \"%d\", args); + return 0; + } + " HAVE_VSPRINTF_S) endif() # Check which your compiler supports ISO _strdup. @@ -290,8 +247,10 @@ endif() # Determine whether CppUnit is installed. -find_package(CppUnit) -if(NOT CppUnit_FOUND AND BUILD_TESTS) - message(STATUS "CppUnit not found, disabling tests.") - set(BUILD_TESTS OFF) +if(BUILD_TESTS) + find_package(CppUnit) + if(NOT CppUnit_FOUND) + message(STATUS "CppUnit not found, disabling tests.") + set(BUILD_TESTS OFF) + endif() endif() diff --git a/NEWS b/NEWS index 72d41fba..62015f05 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,49 @@ +TagLib 1.10 (??? ??, 2015) +========================== + +1.10 BETA: + + * Fixed binary incompatible change in TagLib::String. + * Fixed crash when parsing certain FLAC files. + * Fixed crash when encoding empty strings. + * Fixed saving of certain XM files on OS X. + * Allowed Xiph and APE generic getters to return space-concatenated values. + * Added support for ID3v2 ETCO and SYLT frames. + * Added support for album artist in PropertyMap API of MP4 files. + * Added support for embedded frames in ID3v2 CHAP and CTOC frames. + * Added ZLIB_SOURCE build option. + * Fixed possible file corruptions when removing tags from WAV files. + * Added support for MP4 files with 64-bit atoms in certain 64-bit environments. + * Better handling of duplicate ID3v2 tags in MPEG files. + * Prevented ID3v2 padding from being too large. + * Fixed crash when parsing corrupted APE files. + * Added support for AIFF-C files. + * Fixed crash when parsing corrupted WAV files. + * Fixed crash when parsing corrupted Ogg FLAC files. + * Fixed crash when parsing corrupted MPEG files. + * Fixed saving empty tags in WAV files. + * Fixed crash when parsing corrupted Musepack files. + * Fixed possible memory leaks when parsing AIFF and WAV files. + * Fixed crash when parsing corrupted MP4 files. + * Stopped writing empty ID3v2 frames. + * Fixed possible file corruptions when saving WMA files. + * Added TagLib::MP4::Tag::isEmpty(). + * Added accessors to manipulate MP4 tags . + * Fixed crash when parsing corrupted WavPack files. + * Fixed seeking MPEG frames. + * Allowed generating taglib.pc on Windows. + * Fixed reading FLAC files with zero-sized padding blocks. + * New API for the audio length in milliseconds. + * Added support for reading the encoder information of WMA files. + * Added support for reading the codec of WAV files. + * Added support for multi channel WavPack files. + * Added support for reading the nominal bitrate of Ogg Speex files. + * Added support for VBR headers in MPEG files. + * Marked FLAC::File::streamInfoData() deprecated. It returns an empty ByteVector. + * Marked FLAC::File::streamLength() deprecated. It returns zero. + * Fixed possible file corruptions when adding an ID3v1 tag to FLAC files. + * Many smaller bug fixes and performance improvements. + TagLib 1.9.1 (Oct 8, 2013) ========================== @@ -5,7 +51,7 @@ TagLib 1.9.1 (Oct 8, 2013) * Fixed constructing String from ByteVector. * Fixed compilation on MSVC with the /Zc:wchar_t- option. * Fixed detecting of RIFF files with invalid chunk sizes. - * Added TagLib::MP4::PropertyMap::codec(). + * Added TagLib::MP4::Properties::codec(). TagLib 1.9 (Oct 6, 2013) ======================== diff --git a/cmake/TestFloatFormat.c b/cmake/TestFloatFormat.c deleted file mode 100644 index 4a7b32e4..00000000 --- a/cmake/TestFloatFormat.c +++ /dev/null @@ -1,17 +0,0 @@ -int main(int argc, char **argv) -{ - int ret = 0; - - double bin1[] = { - // "*TAGLIB*" encoded as a little-endian floating-point number - (double)3.9865557444897601e-105, (double)0.0 - }; - float bin2[] = { - // "*TL*" encoded as a little-endian floating-point number - (float)1.81480400e-013, (float)0.0 - }; - ret += ((int*)bin1)[argc]; - ret += ((int*)bin2)[argc]; - - return ret; -} diff --git a/cmake/modules/TestFloatFormat.cmake b/cmake/modules/TestFloatFormat.cmake deleted file mode 100644 index c1bc568f..00000000 --- a/cmake/modules/TestFloatFormat.cmake +++ /dev/null @@ -1,61 +0,0 @@ -# Returns 1 if IEEE754 little-endian, 2 if IEEE754 big-endian, otherwise 0. - -MACRO(TEST_FLOAT_FORMAT FP_IEEE754) - IF(NOT FP_IEEE754) - TRY_COMPILE(HAVE_FLOAT_FORMAT_BIN - "${CMAKE_BINARY_DIR}" "${CMAKE_SOURCE_DIR}/cmake/TestFloatFormat.c" - COPY_FILE "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/TestFloatFormat.bin") - - SET(FP_IEEE754 0) - - IF(HAVE_FLOAT_FORMAT_BIN) - - # dont match first/last letter because of string rounding errors :-) - FILE(STRINGS "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/TestFloatFormat.bin" - DOUBLE_IEEE754_LE LIMIT_COUNT 1 REGEX "TAGLIB") - FILE(STRINGS "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/TestFloatFormat.bin" - DOUBLE_IEEE754_BE LIMIT_COUNT 1 REGEX "BILGAT") - FILE(STRINGS "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/TestFloatFormat.bin" - FLOAT_IEEE754_LE LIMIT_COUNT 1 REGEX "TL") - FILE(STRINGS "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/TestFloatFormat.bin" - FLOAT_IEEE754_BE LIMIT_COUNT 1 REGEX "LT") - - IF(DOUBLE_IEEE754_LE AND FLOAT_IEEE754_LE) - SET(FP_IEEE754_LE 1) - ENDIF() - - IF(DOUBLE_IEEE754_BE AND FLOAT_IEEE754_BE) - SET(FP_IEEE754_BE 1) - ENDIF() - - # OS X Universal binaries will contain both strings, set it to the host - IF(FP_IEEE754_LE AND FP_IEEE754_BE) - IF(CMAKE_SYSTEM_PROCESSOR MATCHES powerpc) - SET(FP_IEEE754_LE FALSE) - SET(FP_IEEE754_BE TRUE) - ELSE() - SET(FP_IEEE754_LE TRUE) - SET(FP_IEEE754_BE FALSE) - ENDIF() - ENDIF() - - IF(FP_IEEE754_LE) - SET(FP_IEEE754 1) - ELSEIF(FP_IEEE754_BE) - SET(FP_IEEE754 2) - ENDIF() - ENDIF() - - # just some informational output for the user - IF(FP_IEEE754_LE) - MESSAGE(STATUS "Checking the floating point format - IEEE754 (LittleEndian)") - ELSEIF(FP_IEEE754_BE) - MESSAGE(STATUS "Checking the floating point format - IEEE754 (BigEndian)") - ELSE() - MESSAGE(STATUS "Checking the floating point format - Not IEEE754 or failed to detect.") - ENDIF() - - SET(FP_IEEE754 "${${FP_IEEE754}}" CACHE INTERNAL "Result of TEST_FLOAT_FORMAT" FORCE) - ENDIF() -ENDMACRO(TEST_FLOAT_FORMAT FP_IEEE754) - diff --git a/config.h.cmake b/config.h.cmake index dfe97036..b48f8e6b 100644 --- a/config.h.cmake +++ b/config.h.cmake @@ -1,22 +1,12 @@ /* config.h. Generated by cmake from config.h.cmake */ -/* Integer byte order of your target system */ -/* 1 if little-endian, 2 if big-endian. */ -#cmakedefine SYSTEM_BYTEORDER ${SYSTEM_BYTEORDER} - -/* IEEE754 byte order of your target system. */ -/* 1 if little-endian, 2 if big-endian. */ -#cmakedefine FLOAT_BYTEORDER ${FLOAT_BYTEORDER} - /* Defined if required for large files support */ #cmakedefine _LARGE_FILES ${_LARGE_FILES} #cmakedefine _LARGEFILE_SOURCE ${_LARGEFILE_SOURCE} #cmakedefine _FILE_OFFSET_BITS ${_FILE_OFFSET_BITS} /* Defined if your compiler supports some byte swap functions */ -#cmakedefine HAVE_GCC_BYTESWAP_16 1 -#cmakedefine HAVE_GCC_BYTESWAP_32 1 -#cmakedefine HAVE_GCC_BYTESWAP_64 1 +#cmakedefine HAVE_GCC_BYTESWAP 1 #cmakedefine HAVE_GLIBC_BYTESWAP 1 #cmakedefine HAVE_MSC_BYTESWAP 1 #cmakedefine HAVE_MAC_BYTESWAP 1 @@ -34,9 +24,9 @@ #cmakedefine HAVE_STD_SMART_PTR 1 #cmakedefine HAVE_BOOST_SMART_PTR 1 -/* Defined if your compiler supports snprintf or sprintf_s. */ -#cmakedefine HAVE_SNPRINTF 1 -#cmakedefine HAVE_SPRINTF_S 1 +/* Defined if your compiler supports some safer version of vsprintf */ +#cmakedefine HAVE_VSNPRINTF 1 +#cmakedefine HAVE_VSPRINTF_S 1 /* Defined if your compiler supports ISO _strdup. */ #cmakedefine HAVE_ISO_STRDUP 1 diff --git a/taglib/ape/apefile.cpp b/taglib/ape/apefile.cpp index 22c49356..d4df9cb9 100644 --- a/taglib/ape/apefile.cpp +++ b/taglib/ape/apefile.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #include #include "apefile.h" @@ -57,12 +58,17 @@ public: APELocation(-1), APESize(0), ID3v1Location(-1), + ID3v2Header(0), + ID3v2Location(-1), + ID3v2Size(0), properties(0), hasAPE(false), - hasID3v1(false) {} + hasID3v1(false), + hasID3v2(false) {} ~FilePrivate() { + delete ID3v2Header; delete properties; } @@ -71,6 +77,10 @@ public: offset_t ID3v1Location; + ID3v2::Header *ID3v2Header; + offset_t ID3v2Location; + uint ID3v2Size; + DoubleTagUnion tag; AudioProperties *properties; @@ -80,26 +90,27 @@ public: bool hasAPE; bool hasID3v1; + bool hasID3v2; }; //////////////////////////////////////////////////////////////////////////////// // public members //////////////////////////////////////////////////////////////////////////////// -APE::File::File(FileName file, bool readProperties, - AudioProperties::ReadStyle propertiesStyle) : TagLib::File(file) +APE::File::File(FileName file, bool readProperties, AudioProperties::ReadStyle) : + TagLib::File(file), + d(new FilePrivate()) { - d = new FilePrivate; if(isOpen()) - read(readProperties, propertiesStyle); + read(readProperties); } -APE::File::File(IOStream *stream, bool readProperties, - AudioProperties::ReadStyle propertiesStyle) : TagLib::File(stream) +APE::File::File(IOStream *stream, bool readProperties, AudioProperties::ReadStyle) : + TagLib::File(stream), + d(new FilePrivate()) { - d = new FilePrivate; if(isOpen()) - read(readProperties, propertiesStyle); + read(readProperties); } APE::File::~File() @@ -232,8 +243,19 @@ bool APE::File::hasID3v1Tag() const // private members //////////////////////////////////////////////////////////////////////////////// -void APE::File::read(bool readProperties, AudioProperties::ReadStyle /* propertiesStyle */) +void APE::File::read(bool readProperties) { + // Look for an ID3v2 tag + + d->ID3v2Location = findID3v2(); + + if(d->ID3v2Location >= 0) { + seek(d->ID3v2Location); + d->ID3v2Header = new ID3v2::Header(readBlock(ID3v2::Header::size())); + d->ID3v2Size = d->ID3v2Header->completeTagSize(); + d->hasID3v2 = true; + } + // Look for an ID3v1 tag d->ID3v1Location = findID3v1(); @@ -260,7 +282,25 @@ void APE::File::read(bool readProperties, AudioProperties::ReadStyle /* properti // Look for APE audio properties if(readProperties) { - d->properties = new AudioProperties(this); + + offset_t streamLength; + + if(d->hasAPE) + streamLength = d->APELocation; + else if(d->hasID3v1) + streamLength = d->ID3v1Location; + else + streamLength = length(); + + if(d->hasID3v2) { + seek(d->ID3v2Location + d->ID3v2Size); + streamLength -= (d->ID3v2Location + d->ID3v2Size); + } + else { + seek(0); + } + + d->properties = new AudioProperties(this, streamLength); } } @@ -295,3 +335,16 @@ offset_t APE::File::findID3v1() return -1; } + +offset_t APE::File::findID3v2() +{ + if(!isValid()) + return -1; + + seek(0); + + if(readBlock(3) == ID3v2::Header::fileIdentifier()) + return 0; + + return -1; +} diff --git a/taglib/ape/apefile.h b/taglib/ape/apefile.h index 62b328be..0fc7ff51 100644 --- a/taglib/ape/apefile.h +++ b/taglib/ape/apefile.h @@ -117,7 +117,7 @@ namespace TagLib { /*! * Implements the unified property interface -- import function. - * Creates an APEv2 tag if necessary. A pontentially existing ID3v1 + * Creates an APEv2 tag if necessary. A potentially existing ID3v1 * tag will be updated as well. */ PropertyMap setProperties(const PropertyMap &); @@ -133,6 +133,9 @@ namespace TagLib { * * \note According to the official Monkey's Audio SDK, an APE file * can only have either ID3V1 or APE tags, so a parameter is used here. + * + * \warning In the current implementation, it's dangerous to call save() + * repeatedly. At worst it will corrupt the file. */ virtual bool save(); @@ -143,8 +146,8 @@ namespace TagLib { * if there is no valid ID3v1 tag. If \a create is true it will create * an ID3v1 tag if one does not exist and returns a valid pointer. * - * \note This may return a valid pointer regardless of whether or not the - * file on disk has an ID3v1 tag. Use hasID3v1Tag() to check if the file + * \note This may return a valid pointer regardless of whether or not the + * file on disk has an ID3v1 tag. Use hasID3v1Tag() to check if the file * on disk actually has an ID3v1 tag. * * \note The Tag is still owned by the MPEG::File and should not be @@ -162,8 +165,8 @@ namespace TagLib { * if there is no valid APE tag. If \a create is true it will create * an APE tag if one does not exist and returns a valid pointer. * - * \note This may return a valid pointer regardless of whether or not the - * file on disk has an APE tag. Use hasAPETag() to check if the file + * \note This may return a valid pointer regardless of whether or not the + * file on disk has an APE tag. Use hasAPETag() to check if the file * on disk actually has an APE tag. * * \note The Tag is still owned by the MPEG::File and should not be @@ -202,10 +205,10 @@ namespace TagLib { File(const File &); File &operator=(const File &); - void read(bool readProperties, AudioProperties::ReadStyle propertiesStyle); - void scan(); - offset_t findID3v1(); + void read(bool readProperties); offset_t findAPE(); + offset_t findID3v1(); + offset_t findID3v2(); class FilePrivate; FilePrivate *d; diff --git a/taglib/ape/apefooter.h b/taglib/ape/apefooter.h index 080f9300..683af12f 100644 --- a/taglib/ape/apefooter.h +++ b/taglib/ape/apefooter.h @@ -37,7 +37,7 @@ namespace TagLib { /*! * This class implements APE footers (and headers). It attempts to follow, both - * semantically and programatically, the structure specified in + * semantically and programmatically, the structure specified in * the APE v2.0 standard. The API is based on the properties of APE footer and * headers specified there. */ diff --git a/taglib/ape/apeitem.h b/taglib/ape/apeitem.h index d2286153..d75692ff 100644 --- a/taglib/ape/apeitem.h +++ b/taglib/ape/apeitem.h @@ -147,13 +147,13 @@ namespace TagLib { /*! * Returns the value as a single string. In case of multiple strings, - * the first is returned. If the data type is not \a Text, always returns + * the first is returned. If the data type is not \a Text, always returns * an empty String. */ String toString() const; /*! - * Returns the list of text values. If the data type is not \a Text, always + * Returns the list of text values. If the data type is not \a Text, always * returns an empty StringList. */ StringList values() const; diff --git a/taglib/ape/apeproperties.cpp b/taglib/ape/apeproperties.cpp index dfd72483..219b2333 100644 --- a/taglib/ape/apeproperties.cpp +++ b/taglib/ape/apeproperties.cpp @@ -33,6 +33,8 @@ #include "id3v2tag.h" #include "apeproperties.h" #include "apefile.h" +#include "apetag.h" +#include "apefooter.h" using namespace TagLib; @@ -61,10 +63,18 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -APE::AudioProperties::AudioProperties(File *file, ReadStyle style) : +APE::AudioProperties::AudioProperties(File *, ReadStyle) : + TagLib::AudioProperties(), d(new PropertiesPrivate()) { - read(file); + debug("APE::Properties::Properties() -- This constructor is no longer used."); +} + +APE::AudioProperties::AudioProperties(File *file, offset_t streamLength, ReadStyle) : + TagLib::AudioProperties(), + d(new PropertiesPrivate()) +{ + read(file, streamLength); } APE::AudioProperties::~AudioProperties() @@ -73,6 +83,16 @@ APE::AudioProperties::~AudioProperties() } int APE::AudioProperties::length() const +{ + return lengthInSeconds(); +} + +int APE::AudioProperties::lengthInSeconds() const +{ + return d->length / 1000; +} + +int APE::AudioProperties::lengthInMilliseconds() const { return d->length; } @@ -111,99 +131,93 @@ TagLib::uint APE::AudioProperties::sampleFrames() const // private members //////////////////////////////////////////////////////////////////////////////// - -void APE::AudioProperties::read(File *file) +namespace { - // First we are searching the descriptor - offset_t offset = findDescriptor(file); - if(offset < 0) - return; + inline int headerVersion(const ByteVector &header) + { + if(header.size() < 6 || !header.startsWith("MAC ")) + return -1; - // Then we read the header common for all versions of APE - file->seek(offset); - ByteVector commonHeader = file->readBlock(6); - if(!commonHeader.startsWith("MAC ")) - return; - d->version = commonHeader.toUInt16LE(4); + return header.toUInt16LE(4); + } +} - if(d->version >= 3980) { +void APE::AudioProperties::read(File *file, offset_t streamLength) +{ + // First, we assume that the file pointer is set at the first descriptor. + offset_t offset = file->tell(); + int version = headerVersion(file->readBlock(6)); + + // Next, we look for the descriptor. + if(version < 0) { + offset = file->find("MAC ", offset); + file->seek(offset); + version = headerVersion(file->readBlock(6)); + } + + if(version < 0) { + debug("APE::Properties::read() -- APE descriptor not found"); + return; + } + + d->version = version; + + if(d->version >= 3980) analyzeCurrent(file); - } - else { - analyzeOld(file); - } -} - -offset_t APE::AudioProperties::findDescriptor(File *file) -{ - offset_t ID3v2Location = findID3v2(file); - long ID3v2OriginalSize = 0; - bool hasID3v2 = false; - if(ID3v2Location >= 0) { - ID3v2::Tag tag(file, ID3v2Location); - ID3v2OriginalSize = tag.header()->completeTagSize(); - if(tag.header()->tagSize() > 0) - hasID3v2 = true; - } - - offset_t offset = 0; - if(hasID3v2) - offset = file->find("MAC ", ID3v2Location + ID3v2OriginalSize); else - offset = file->find("MAC "); + analyzeOld(file); - if(offset < 0) { - debug("APE::Properties::findDescriptor() -- APE descriptor not found"); - return -1; + if(d->sampleFrames > 0 && d->sampleRate > 0) { + const double length = d->sampleFrames * 1000.0 / d->sampleRate; + d->length = static_cast(length + 0.5); + d->bitrate = static_cast(streamLength * 8.0 / length + 0.5); } - - return offset; -} - -offset_t APE::AudioProperties::findID3v2(File *file) -{ - if(!file->isValid()) - return -1; - - file->seek(0); - - if(file->readBlock(3) == ID3v2::Header::fileIdentifier()) - return 0; - - return -1; } void APE::AudioProperties::analyzeCurrent(File *file) { // Read the descriptor file->seek(2, File::Current); - ByteVector descriptor = file->readBlock(44); + const ByteVector descriptor = file->readBlock(44); + if(descriptor.size() < 44) { + debug("APE::Properties::analyzeCurrent() -- descriptor is too short."); + return; + } + const uint descriptorBytes = descriptor.toUInt32LE(0); - if ((descriptorBytes - 52) > 0) + if((descriptorBytes - 52) > 0) file->seek(descriptorBytes - 52, File::Current); // Read the header - ByteVector header = file->readBlock(24); + const ByteVector header = file->readBlock(24); + if(header.size() < 24) { + debug("APE::Properties::analyzeCurrent() -- MAC header is too short."); + return; + } // Get the APE info - d->channels = header.toInt16LE(18); - d->sampleRate = header.toUInt32LE(20); - d->bitsPerSample = header.toInt16LE(16); - //d->compressionLevel = + d->channels = header.toUInt16LE(18); + d->sampleRate = header.toUInt32LE(20); + d->bitsPerSample = header.toUInt16LE(16); const uint totalFrames = header.toUInt32LE(12); - const uint blocksPerFrame = header.toUInt32LE(4); - const uint finalFrameBlocks = header.toUInt32LE(8); + if(totalFrames == 0) + return; - d->sampleFrames = totalFrames > 0 ? (totalFrames - 1) * blocksPerFrame + finalFrameBlocks : 0; - d->length = d->sampleRate > 0 ? d->sampleFrames / d->sampleRate : 0; - d->bitrate = d->length > 0 ? static_cast(file->length() * 8L / d->length / 1000) : 0; + const uint blocksPerFrame = header.toUInt32LE(4); + const uint finalFrameBlocks = header.toUInt32LE(8); + d->sampleFrames = (totalFrames - 1) * blocksPerFrame + finalFrameBlocks; } void APE::AudioProperties::analyzeOld(File *file) { - ByteVector header = file->readBlock(26); + const ByteVector header = file->readBlock(26); + if(header.size() < 26) { + debug("APE::Properties::analyzeOld() -- MAC header is too short."); + return; + } + const uint totalFrames = header.toUInt32LE(18); // Fail on 0 length APE files (catches non-finalized APE files) @@ -219,19 +233,20 @@ void APE::AudioProperties::analyzeOld(File *file) else blocksPerFrame = 9216; + // Get the APE info d->channels = header.toUInt16LE(4); d->sampleRate = header.toUInt32LE(6); const uint finalFrameBlocks = header.toUInt32LE(22); + d->sampleFrames = (totalFrames - 1) * blocksPerFrame + finalFrameBlocks; - uint totalBlocks = 0; - if(totalFrames > 0) - totalBlocks = (totalFrames - 1) * blocksPerFrame + finalFrameBlocks; + // Get the bit depth from the RIFF-fmt chunk. + file->seek(16, File::Current); + const ByteVector fmt = file->readBlock(28); + if(fmt.size() < 28 || !fmt.startsWith("WAVEfmt ")) { + debug("APE::Properties::analyzeOld() -- fmt header is too short."); + return; + } - if(d->sampleRate > 0) - d->length = totalBlocks / d->sampleRate; - - if(d->length > 0) - d->bitrate = ((file->length() * 8L) / d->length) / 1000; + d->bitsPerSample = fmt.toUInt16LE(26); } - diff --git a/taglib/ape/apeproperties.h b/taglib/ape/apeproperties.h index 64bfd0ee..a7ed4887 100644 --- a/taglib/ape/apeproperties.h +++ b/taglib/ape/apeproperties.h @@ -50,27 +50,74 @@ namespace TagLib { { public: /*! - * Creates an instance of APE::AudioProperties with the data read from - * the ByteVector \a data. + * Create an instance of APE::Properties with the data read from the + * APE::File \a file. + * + * \deprecated */ AudioProperties(File *file, ReadStyle style = Average); + /*! + * Create an instance of APE::Properties with the data read from the + * APE::File \a file. + */ + AudioProperties(File *file, offset_t streamLength, ReadStyle style = Average); + /*! * Destroys this APE::AudioProperties instance. */ virtual ~AudioProperties(); - // Reimplementations. - + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \note This method is just an alias of lengthInSeconds(). + * + * \deprecated + */ virtual int length() const; + + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \see lengthInMilliseconds() + */ + // BIC: make virtual + int lengthInSeconds() const; + + /*! + * Returns the length of the file in milliseconds. + * + * \see lengthInSeconds() + */ + // BIC: make virtual + int lengthInMilliseconds() const; + + /*! + * Returns the average bit rate of the file in kb/s. + */ virtual int bitrate() const; + + /*! + * Returns the sample rate in Hz. + */ virtual int sampleRate() const; + + /*! + * Returns the number of audio channels. + */ virtual int channels() const; /*! - * Returns number of bits per sample. + * Returns the number of bits per audio sample. */ int bitsPerSample() const; + + /*! + * Returns the total number of audio samples in file. + */ uint sampleFrames() const; /*! @@ -79,11 +126,7 @@ namespace TagLib { int version() const; private: - void read(File *file); - - offset_t findDescriptor(File *file); - offset_t findID3v2(File *file); - + void read(File *file, offset_t streamLength); void analyzeCurrent(File *file); void analyzeOld(File *file); diff --git a/taglib/ape/apetag.cpp b/taglib/ape/apetag.cpp index 2112d01e..de268053 100644 --- a/taglib/ape/apetag.cpp +++ b/taglib/ape/apetag.cpp @@ -46,14 +46,7 @@ using namespace APE; class APE::Tag::TagPrivate { public: - TagPrivate() : file(0), footerLocation(-1), tagLength(0) {} - - TagLib::File *file; - offset_t footerLocation; - long tagLength; - Footer footer; - ItemListMap itemListMap; }; @@ -61,18 +54,17 @@ public: // public methods //////////////////////////////////////////////////////////////////////////////// -APE::Tag::Tag() : TagLib::Tag() +APE::Tag::Tag() : + TagLib::Tag(), + d(new TagPrivate()) { - d = new TagPrivate; } -APE::Tag::Tag(TagLib::File *file, offset_t footerLocation) : TagLib::Tag() +APE::Tag::Tag(TagLib::File *file, offset_t footerLocation) : + TagLib::Tag(), + d(new TagPrivate()) { - d = new TagPrivate; - d->file = file; - d->footerLocation = footerLocation; - - read(); + read(file, footerLocation); } APE::Tag::~Tag() @@ -287,9 +279,7 @@ const APE::ItemListMap& APE::Tag::itemListMap() const void APE::Tag::removeItem(const String &key) { - Map::Iterator it = d->itemListMap.find(key.upper()); - if(it != d->itemListMap.end()) - d->itemListMap.erase(it); + d->itemListMap.erase(key.upper()); } void APE::Tag::addValue(const String &key, const String &value, bool replace) @@ -332,19 +322,19 @@ bool APE::Tag::isEmpty() const // protected methods //////////////////////////////////////////////////////////////////////////////// -void APE::Tag::read() +void APE::Tag::read(TagLib::File *file, offset_t footerLocation) { - if(d->file && d->file->isValid()) { + if(file && file->isValid()) { - d->file->seek(d->footerLocation); - d->footer.setData(d->file->readBlock(Footer::size())); + file->seek(footerLocation); + d->footer.setData(file->readBlock(Footer::size())); if(d->footer.tagSize() <= Footer::size() || - d->footer.tagSize() > uint(d->file->length())) + d->footer.tagSize() > uint(file->length())) return; - d->file->seek(d->footerLocation + Footer::size() - d->footer.tagSize()); - parse(d->file->readBlock(d->footer.tagSize() - Footer::size())); + file->seek(footerLocation + Footer::size() - d->footer.tagSize()); + parse(file->readBlock(d->footer.tagSize() - Footer::size())); } } diff --git a/taglib/ape/apetag.h b/taglib/ape/apetag.h index 9b78d5dd..ab7744ca 100644 --- a/taglib/ape/apetag.h +++ b/taglib/ape/apetag.h @@ -143,7 +143,7 @@ namespace TagLib { * Returns a reference to the item list map. This is an ItemListMap of * all of the items in the tag. * - * This is the most powerfull structure for accessing the items of the tag. + * This is the most powerful structure for accessing the items of the tag. * * APE tags are case-insensitive, all keys in this map have been converted * to upper case. @@ -188,7 +188,7 @@ namespace TagLib { /*! * Reads from the file specified in the constructor. */ - void read(); + void read(TagLib::File *file, offset_t footerLocation); /*! * Parses the body of the tag in \a data. diff --git a/taglib/asf/asfattribute.cpp b/taglib/asf/asfattribute.cpp index 8abea065..dfceb1ef 100644 --- a/taglib/asf/asfattribute.cpp +++ b/taglib/asf/asfattribute.cpp @@ -23,11 +23,13 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ -#include "taglib.h" -#include "tdebug.h" -#include "tsmartptr.h" +#include +#include +#include + #include "asfattribute.h" #include "asffile.h" +#include "asfutils.h" using namespace TagLib; @@ -53,8 +55,8 @@ namespace class ASF::Attribute::AttributePrivate { public: - AttributePrivate() - : data(new AttributeData()) + AttributePrivate() : + data(new AttributeData()) { data->pictureValue = ASF::Picture::fromInvalid(); data->stream = 0; @@ -153,7 +155,7 @@ ByteVector ASF::Attribute::toByteVector() const { if(d->data->pictureValue.isValid()) return d->data->pictureValue.render(); - + return d->data->byteVectorValue; } @@ -189,23 +191,23 @@ String ASF::Attribute::parse(ASF::File &f, int kind) d->data->pictureValue = Picture::fromInvalid(); // extended content descriptor if(kind == 0) { - nameLength = f.readWORD(); - name = f.readString(nameLength); - d->data->type = ASF::Attribute::AttributeTypes(f.readWORD()); - size = f.readWORD(); + nameLength = readWORD(&f); + name = readString(&f, nameLength); + d->data->type = ASF::Attribute::AttributeTypes(readWORD(&f)); + size = readWORD(&f); } // metadata & metadata library else { - int temp = f.readWORD(); + int temp = readWORD(&f); // metadata library if(kind == 2) { d->data->language = temp; } - d->data->stream = f.readWORD(); - nameLength = f.readWORD(); - d->data->type = ASF::Attribute::AttributeTypes(f.readWORD()); - size = f.readDWORD(); - name = f.readString(nameLength); + d->data->stream = readWORD(&f); + nameLength = readWORD(&f); + d->data->type = ASF::Attribute::AttributeTypes(readWORD(&f)); + size = readDWORD(&f); + name = readString(&f, nameLength); } if(kind != 2 && size > 65535) { @@ -214,28 +216,28 @@ String ASF::Attribute::parse(ASF::File &f, int kind) switch(d->data->type) { case WordType: - d->data->shortValue = f.readWORD(); + d->data->shortValue = readWORD(&f); break; case BoolType: if(kind == 0) { - d->data->boolValue = f.readDWORD() == 1; + d->data->boolValue = (readDWORD(&f) == 1); } else { - d->data->boolValue = f.readWORD() == 1; + d->data->boolValue = (readWORD(&f) == 1); } break; case DWordType: - d->data->intValue = f.readDWORD(); + d->data->intValue = readDWORD(&f); break; case QWordType: - d->data->longLongValue = f.readQWORD(); + d->data->longLongValue = readQWORD(&f); break; case UnicodeType: - d->data->stringValue = f.readString(size); + d->data->stringValue = readString(&f, size); break; case BytesType: @@ -303,7 +305,7 @@ ByteVector ASF::Attribute::render(const String &name, int kind) const break; case UnicodeType: - data.append(File::renderString(d->data->stringValue)); + data.append(renderString(d->data->stringValue)); break; case BytesType: @@ -317,13 +319,13 @@ ByteVector ASF::Attribute::render(const String &name, int kind) const } if(kind == 0) { - data = File::renderString(name, true) + + data = renderString(name, true) + ByteVector::fromUInt16LE((int)d->data->type) + ByteVector::fromUInt16LE(data.size()) + data; } else { - ByteVector nameData = File::renderString(name); + ByteVector nameData = renderString(name); data = ByteVector::fromUInt16LE(kind == 2 ? d->data->language : 0) + ByteVector::fromUInt16LE(d->data->stream) + ByteVector::fromUInt16LE(nameData.size()) + diff --git a/taglib/asf/asffile.cpp b/taglib/asf/asffile.cpp index 4ea7822c..f030ae87 100644 --- a/taglib/asf/asffile.cpp +++ b/taglib/asf/asffile.cpp @@ -23,19 +23,35 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ +// The implementation of this class is based on the document found at: +// http://download.microsoft.com/download/8/0/5/80506BEB-C95A-47AE-99CF-0D6D6D028ABA/ASF_Specification.pdf + #include #include #include #include + #include "asffile.h" #include "asftag.h" #include "asfproperties.h" +#include "asfutils.h" using namespace TagLib; class ASF::File::FilePrivate { public: + class BaseObject; + class UnknownObject; + class FilePropertiesObject; + class StreamPropertiesObject; + class ContentDescriptionObject; + class ExtendedContentDescriptionObject; + class HeaderExtensionObject; + class CodecListObject; + class MetadataObject; + class MetadataLibraryObject; + FilePrivate(): size(0), tag(0), @@ -44,16 +60,29 @@ public: extendedContentDescriptionObject(0), headerExtensionObject(0), metadataObject(0), - metadataLibraryObject(0) {} + metadataLibraryObject(0) + { + objects.setAutoDelete(true); + } + + ~FilePrivate() + { + delete tag; + delete properties; + } + unsigned long long size; + ASF::Tag *tag; ASF::AudioProperties *properties; - List objects; - ASF::File::ContentDescriptionObject *contentDescriptionObject; - ASF::File::ExtendedContentDescriptionObject *extendedContentDescriptionObject; - ASF::File::HeaderExtensionObject *headerExtensionObject; - ASF::File::MetadataObject *metadataObject; - ASF::File::MetadataLibraryObject *metadataLibraryObject; + + List objects; + + ContentDescriptionObject *contentDescriptionObject; + ExtendedContentDescriptionObject *extendedContentDescriptionObject; + HeaderExtensionObject *headerExtensionObject; + MetadataObject *metadataObject; + MetadataLibraryObject *metadataLibraryObject; }; namespace @@ -66,169 +95,192 @@ namespace const ByteVector headerExtensionGuid("\xb5\x03\xbf_.\xa9\xcf\x11\x8e\xe3\x00\xc0\x0c Se", 16); const ByteVector metadataGuid("\xEA\xCB\xF8\xC5\xAF[wH\204g\xAA\214D\xFAL\xCA", 16); const ByteVector metadataLibraryGuid("\224\034#D\230\224\321I\241A\x1d\x13NEpT", 16); + const ByteVector codecListGuid("\x40\x52\xd1\x86\x1d\x31\xd0\x11\xa3\xa4\x00\xa0\xc9\x03\x48\xf6", 16); const ByteVector contentEncryptionGuid("\xFB\xB3\x11\x22\x23\xBD\xD2\x11\xB4\xB7\x00\xA0\xC9\x55\xFC\x6E", 16); const ByteVector extendedContentEncryptionGuid("\x14\xE6\x8A\x29\x22\x26 \x17\x4C\xB9\x35\xDA\xE0\x7E\xE9\x28\x9C", 16); const ByteVector advancedContentEncryptionGuid("\xB6\x9B\x07\x7A\xA4\xDA\x12\x4E\xA5\xCA\x91\xD3\x8D\xC1\x1A\x8D", 16); } -class ASF::File::BaseObject +class ASF::File::FilePrivate::BaseObject { public: ByteVector data; virtual ~BaseObject() {} - virtual ByteVector guid() = 0; + virtual ByteVector guid() const = 0; virtual void parse(ASF::File *file, unsigned int size); virtual ByteVector render(ASF::File *file); }; -class ASF::File::UnknownObject : public ASF::File::BaseObject +class ASF::File::FilePrivate::UnknownObject : public ASF::File::FilePrivate::BaseObject { ByteVector myGuid; public: UnknownObject(const ByteVector &guid); - ByteVector guid(); + ByteVector guid() const; }; -class ASF::File::FilePropertiesObject : public ASF::File::BaseObject +class ASF::File::FilePrivate::FilePropertiesObject : public ASF::File::FilePrivate::BaseObject { public: - ByteVector guid(); + ByteVector guid() const; void parse(ASF::File *file, uint size); }; -class ASF::File::StreamPropertiesObject : public ASF::File::BaseObject +class ASF::File::FilePrivate::StreamPropertiesObject : public ASF::File::FilePrivate::BaseObject { public: - ByteVector guid(); + ByteVector guid() const; void parse(ASF::File *file, uint size); }; -class ASF::File::ContentDescriptionObject : public ASF::File::BaseObject +class ASF::File::FilePrivate::ContentDescriptionObject : public ASF::File::FilePrivate::BaseObject { public: - ByteVector guid(); + ByteVector guid() const; void parse(ASF::File *file, uint size); ByteVector render(ASF::File *file); }; -class ASF::File::ExtendedContentDescriptionObject : public ASF::File::BaseObject +class ASF::File::FilePrivate::ExtendedContentDescriptionObject : public ASF::File::FilePrivate::BaseObject { public: ByteVectorList attributeData; - ByteVector guid(); + ByteVector guid() const; void parse(ASF::File *file, uint size); ByteVector render(ASF::File *file); }; -class ASF::File::MetadataObject : public ASF::File::BaseObject +class ASF::File::FilePrivate::MetadataObject : public ASF::File::FilePrivate::BaseObject { public: ByteVectorList attributeData; - ByteVector guid(); + ByteVector guid() const; void parse(ASF::File *file, uint size); ByteVector render(ASF::File *file); }; -class ASF::File::MetadataLibraryObject : public ASF::File::BaseObject +class ASF::File::FilePrivate::MetadataLibraryObject : public ASF::File::FilePrivate::BaseObject { public: ByteVectorList attributeData; - ByteVector guid(); + ByteVector guid() const; void parse(ASF::File *file, uint size); ByteVector render(ASF::File *file); }; -class ASF::File::HeaderExtensionObject : public ASF::File::BaseObject +class ASF::File::FilePrivate::HeaderExtensionObject : public ASF::File::FilePrivate::BaseObject { public: - List objects; - ~HeaderExtensionObject(); - ByteVector guid(); + List objects; + HeaderExtensionObject(); + ByteVector guid() const; void parse(ASF::File *file, uint size); ByteVector render(ASF::File *file); }; -ASF::File::HeaderExtensionObject::~HeaderExtensionObject() +class ASF::File::FilePrivate::CodecListObject : public ASF::File::FilePrivate::BaseObject { - for(unsigned int i = 0; i < objects.size(); i++) { - delete objects[i]; - } -} +public: + ByteVector guid() const; + void parse(ASF::File *file, uint size); -void ASF::File::BaseObject::parse(ASF::File *file, unsigned int size) +private: + enum CodecType + { + Video = 0x0001, + Audio = 0x0002, + Unknown = 0xFFFF + }; +}; + +void ASF::File::FilePrivate::BaseObject::parse(ASF::File *file, unsigned int size) { data.clear(); - if (size > 24 && static_cast(size) <= file->length()) + if(size > 24 && static_cast(size) <= file->length()) data = file->readBlock(size - 24); else data = ByteVector::null; } -ByteVector ASF::File::BaseObject::render(ASF::File * /*file*/) +ByteVector ASF::File::FilePrivate::BaseObject::render(ASF::File * /*file*/) { return guid() + ByteVector::fromUInt64LE(data.size() + 24) + data; } -ASF::File::UnknownObject::UnknownObject(const ByteVector &guid) : myGuid(guid) +ASF::File::FilePrivate::UnknownObject::UnknownObject(const ByteVector &guid) : myGuid(guid) { } -ByteVector ASF::File::UnknownObject::guid() +ByteVector ASF::File::FilePrivate::UnknownObject::guid() const { return myGuid; } -ByteVector ASF::File::FilePropertiesObject::guid() +ByteVector ASF::File::FilePrivate::FilePropertiesObject::guid() const { return filePropertiesGuid; } -void ASF::File::FilePropertiesObject::parse(ASF::File *file, uint size) +void ASF::File::FilePrivate::FilePropertiesObject::parse(ASF::File *file, uint size) { BaseObject::parse(file, size); - file->d->properties->setLength((int)(data.toInt64LE(40) / 10000000L - data.toInt64LE(56) / 1000L)); + if(data.size() < 64) { + debug("ASF::File::FilePrivate::FilePropertiesObject::parse() -- data is too short."); + return; + } + + const long long duration = data.toInt64LE(40); + const long long preroll = data.toInt64LE(56); + file->d->properties->setLengthInMilliseconds(static_cast(duration / 10000.0 - preroll + 0.5)); } -ByteVector ASF::File::StreamPropertiesObject::guid() +ByteVector ASF::File::FilePrivate::StreamPropertiesObject::guid() const { return streamPropertiesGuid; } -void ASF::File::StreamPropertiesObject::parse(ASF::File *file, uint size) +void ASF::File::FilePrivate::StreamPropertiesObject::parse(ASF::File *file, uint size) { BaseObject::parse(file, size); - file->d->properties->setChannels(data.toInt16LE(56)); + if(data.size() < 70) { + debug("ASF::File::FilePrivate::StreamPropertiesObject::parse() -- data is too short."); + return; + } + + file->d->properties->setCodec(data.toUInt16LE(54)); + file->d->properties->setChannels(data.toUInt16LE(56)); file->d->properties->setSampleRate(data.toUInt32LE(58)); - file->d->properties->setBitrate(data.toInt16LE(62) * 8 / 1000); + file->d->properties->setBitrate(static_cast(data.toUInt32LE(62) * 8.0 / 1000.0 + 0.5)); + file->d->properties->setBitsPerSample(data.toUInt16LE(68)); } -ByteVector ASF::File::ContentDescriptionObject::guid() +ByteVector ASF::File::FilePrivate::ContentDescriptionObject::guid() const { return contentDescriptionGuid; } -void ASF::File::ContentDescriptionObject::parse(ASF::File *file, uint /*size*/) +void ASF::File::FilePrivate::ContentDescriptionObject::parse(ASF::File *file, uint /*size*/) { file->d->contentDescriptionObject = this; - int titleLength = file->readWORD(); - int artistLength = file->readWORD(); - int copyrightLength = file->readWORD(); - int commentLength = file->readWORD(); - int ratingLength = file->readWORD(); - file->d->tag->setTitle(file->readString(titleLength)); - file->d->tag->setArtist(file->readString(artistLength)); - file->d->tag->setCopyright(file->readString(copyrightLength)); - file->d->tag->setComment(file->readString(commentLength)); - file->d->tag->setRating(file->readString(ratingLength)); + const int titleLength = readWORD(file); + const int artistLength = readWORD(file); + const int copyrightLength = readWORD(file); + const int commentLength = readWORD(file); + const int ratingLength = readWORD(file); + file->d->tag->setTitle(readString(file,titleLength)); + file->d->tag->setArtist(readString(file,artistLength)); + file->d->tag->setCopyright(readString(file,copyrightLength)); + file->d->tag->setComment(readString(file,commentLength)); + file->d->tag->setRating(readString(file,ratingLength)); } -ByteVector ASF::File::ContentDescriptionObject::render(ASF::File *file) +ByteVector ASF::File::FilePrivate::ContentDescriptionObject::render(ASF::File *file) { - ByteVector v1 = file->renderString(file->d->tag->title()); - ByteVector v2 = file->renderString(file->d->tag->artist()); - ByteVector v3 = file->renderString(file->d->tag->copyright()); - ByteVector v4 = file->renderString(file->d->tag->comment()); - ByteVector v5 = file->renderString(file->d->tag->rating()); + const ByteVector v1 = renderString(file->d->tag->title()); + const ByteVector v2 = renderString(file->d->tag->artist()); + const ByteVector v3 = renderString(file->d->tag->copyright()); + const ByteVector v4 = renderString(file->d->tag->comment()); + const ByteVector v5 = renderString(file->d->tag->rating()); data.clear(); data.append(ByteVector::fromUInt16LE(v1.size())); data.append(ByteVector::fromUInt16LE(v2.size())); @@ -243,15 +295,15 @@ ByteVector ASF::File::ContentDescriptionObject::render(ASF::File *file) return BaseObject::render(file); } -ByteVector ASF::File::ExtendedContentDescriptionObject::guid() +ByteVector ASF::File::FilePrivate::ExtendedContentDescriptionObject::guid() const { return extendedContentDescriptionGuid; } -void ASF::File::ExtendedContentDescriptionObject::parse(ASF::File *file, uint /*size*/) +void ASF::File::FilePrivate::ExtendedContentDescriptionObject::parse(ASF::File *file, uint /*size*/) { file->d->extendedContentDescriptionObject = this; - int count = file->readWORD(); + int count = readWORD(file); while(count--) { ASF::Attribute attribute; String name = attribute.parse(*file); @@ -259,7 +311,7 @@ void ASF::File::ExtendedContentDescriptionObject::parse(ASF::File *file, uint /* } } -ByteVector ASF::File::ExtendedContentDescriptionObject::render(ASF::File *file) +ByteVector ASF::File::FilePrivate::ExtendedContentDescriptionObject::render(ASF::File *file) { data.clear(); data.append(ByteVector::fromUInt16LE(attributeData.size())); @@ -267,15 +319,15 @@ ByteVector ASF::File::ExtendedContentDescriptionObject::render(ASF::File *file) return BaseObject::render(file); } -ByteVector ASF::File::MetadataObject::guid() +ByteVector ASF::File::FilePrivate::MetadataObject::guid() const { return metadataGuid; } -void ASF::File::MetadataObject::parse(ASF::File *file, uint /*size*/) +void ASF::File::FilePrivate::MetadataObject::parse(ASF::File *file, uint /*size*/) { file->d->metadataObject = this; - int count = file->readWORD(); + int count = readWORD(file); while(count--) { ASF::Attribute attribute; String name = attribute.parse(*file, 1); @@ -283,7 +335,7 @@ void ASF::File::MetadataObject::parse(ASF::File *file, uint /*size*/) } } -ByteVector ASF::File::MetadataObject::render(ASF::File *file) +ByteVector ASF::File::FilePrivate::MetadataObject::render(ASF::File *file) { data.clear(); data.append(ByteVector::fromUInt16LE(attributeData.size())); @@ -291,15 +343,15 @@ ByteVector ASF::File::MetadataObject::render(ASF::File *file) return BaseObject::render(file); } -ByteVector ASF::File::MetadataLibraryObject::guid() +ByteVector ASF::File::FilePrivate::MetadataLibraryObject::guid() const { return metadataLibraryGuid; } -void ASF::File::MetadataLibraryObject::parse(ASF::File *file, uint /*size*/) +void ASF::File::FilePrivate::MetadataLibraryObject::parse(ASF::File *file, uint /*size*/) { file->d->metadataLibraryObject = this; - int count = file->readWORD(); + int count = readWORD(file); while(count--) { ASF::Attribute attribute; String name = attribute.parse(*file, 2); @@ -307,7 +359,7 @@ void ASF::File::MetadataLibraryObject::parse(ASF::File *file, uint /*size*/) } } -ByteVector ASF::File::MetadataLibraryObject::render(ASF::File *file) +ByteVector ASF::File::FilePrivate::MetadataLibraryObject::render(ASF::File *file) { data.clear(); data.append(ByteVector::fromUInt16LE(attributeData.size())); @@ -315,16 +367,21 @@ ByteVector ASF::File::MetadataLibraryObject::render(ASF::File *file) return BaseObject::render(file); } -ByteVector ASF::File::HeaderExtensionObject::guid() +ASF::File::FilePrivate::HeaderExtensionObject::HeaderExtensionObject() +{ + objects.setAutoDelete(true); +} + +ByteVector ASF::File::FilePrivate::HeaderExtensionObject::guid() const { return headerExtensionGuid; } -void ASF::File::HeaderExtensionObject::parse(ASF::File *file, uint /*size*/) +void ASF::File::FilePrivate::HeaderExtensionObject::parse(ASF::File *file, uint /*size*/) { file->d->headerExtensionObject = this; file->seek(18, File::Current); - long long dataSize = file->readDWORD(); + long long dataSize = readDWORD(file); long long dataPos = 0; while(dataPos < dataSize) { ByteVector guid = file->readBlock(16); @@ -333,7 +390,7 @@ void ASF::File::HeaderExtensionObject::parse(ASF::File *file, uint /*size*/) break; } bool ok; - long long size = file->readQWORD(&ok); + long long size = readQWORD(file, &ok); if(!ok) { file->setValid(false); break; @@ -354,47 +411,93 @@ void ASF::File::HeaderExtensionObject::parse(ASF::File *file, uint /*size*/) } } -ByteVector ASF::File::HeaderExtensionObject::render(ASF::File *file) +ByteVector ASF::File::FilePrivate::HeaderExtensionObject::render(ASF::File *file) { data.clear(); - for(unsigned int i = 0; i < objects.size(); i++) { - data.append(objects[i]->render(file)); + for(List::ConstIterator it = objects.begin(); it != objects.end(); ++it) { + data.append((*it)->render(file)); } data = ByteVector("\x11\xD2\xD3\xAB\xBA\xA9\xcf\x11\x8E\xE6\x00\xC0\x0C\x20\x53\x65\x06\x00", 18) + ByteVector::fromUInt32LE(data.size()) + data; return BaseObject::render(file); } +ByteVector ASF::File::FilePrivate::CodecListObject::guid() const +{ + return codecListGuid; +} + +void ASF::File::FilePrivate::CodecListObject::parse(ASF::File *file, uint size) +{ + BaseObject::parse(file, size); + if(data.size() <= 20) { + debug("ASF::File::FilePrivate::CodecListObject::parse() -- data is too short."); + return; + } + + uint pos = 16; + + const int count = data.toUInt32LE(pos); + pos += 4; + + for(int i = 0; i < count; ++i) { + + if(pos >= data.size()) + break; + + const CodecType type = static_cast(data.toUInt16LE(pos)); + pos += 2; + + int nameLength = data.toUInt16LE(pos); + pos += 2; + + const uint namePos = pos; + pos += nameLength * 2; + + const int descLength = data.toUInt16LE(pos); + pos += 2; + + const uint descPos = pos; + pos += descLength * 2; + + const int infoLength = data.toUInt16LE(pos); + pos += 2 + infoLength * 2; + + if(type == CodecListObject::Audio) { + // First audio codec found. + + const String name(data.mid(namePos, nameLength * 2), String::UTF16LE); + file->d->properties->setCodecName(name.stripWhiteSpace()); + + const String desc(data.mid(descPos, descLength * 2), String::UTF16LE); + file->d->properties->setCodecDescription(desc.stripWhiteSpace()); + + break; + } + } +} + //////////////////////////////////////////////////////////////////////////////// // public members //////////////////////////////////////////////////////////////////////////////// -ASF::File::File(FileName file, bool readProperties, AudioProperties::ReadStyle propertiesStyle) - : TagLib::File(file) +ASF::File::File(FileName file, bool, AudioProperties::ReadStyle) : + TagLib::File(file), + d(new FilePrivate()) { - d = new FilePrivate; if(isOpen()) - read(readProperties, propertiesStyle); + read(); } -ASF::File::File(IOStream *stream, bool readProperties, AudioProperties::ReadStyle propertiesStyle) - : TagLib::File(stream) +ASF::File::File(IOStream *stream, bool, AudioProperties::ReadStyle) : + TagLib::File(stream), + d(new FilePrivate()) { - d = new FilePrivate; if(isOpen()) - read(readProperties, propertiesStyle); + read(); } ASF::File::~File() { - for(unsigned int i = 0; i < d->objects.size(); i++) { - delete d->objects[i]; - } - if(d->tag) { - delete d->tag; - } - if(d->properties) { - delete d->properties; - } delete d; } @@ -423,7 +526,84 @@ ASF::AudioProperties *ASF::File::audioProperties() const return d->properties; } -void ASF::File::read(bool /*readProperties*/, AudioProperties::ReadStyle /*propertiesStyle*/) +bool ASF::File::save() +{ + if(readOnly()) { + debug("ASF::File::save() -- File is read only."); + return false; + } + + if(!isValid()) { + debug("ASF::File::save() -- Trying to save invalid file."); + return false; + } + + if(!d->contentDescriptionObject) { + d->contentDescriptionObject = new FilePrivate::ContentDescriptionObject(); + d->objects.append(d->contentDescriptionObject); + } + if(!d->extendedContentDescriptionObject) { + d->extendedContentDescriptionObject = new FilePrivate::ExtendedContentDescriptionObject(); + d->objects.append(d->extendedContentDescriptionObject); + } + if(!d->headerExtensionObject) { + d->headerExtensionObject = new FilePrivate::HeaderExtensionObject(); + d->objects.append(d->headerExtensionObject); + } + if(!d->metadataObject) { + d->metadataObject = new FilePrivate::MetadataObject(); + d->headerExtensionObject->objects.append(d->metadataObject); + } + if(!d->metadataLibraryObject) { + d->metadataLibraryObject = new FilePrivate::MetadataLibraryObject(); + d->headerExtensionObject->objects.append(d->metadataLibraryObject); + } + + const AttributeListMap allAttributes = d->tag->attributeListMap(); + + for(AttributeListMap::ConstIterator it = allAttributes.begin(); it != allAttributes.end(); ++it) { + + const String &name = it->first; + const AttributeList &attributes = it->second; + + bool inExtendedContentDescriptionObject = false; + bool inMetadataObject = false; + + for(AttributeList::ConstIterator jt = attributes.begin(); jt != attributes.end(); ++jt) { + + const Attribute &attribute = *jt; + const bool largeValue = (attribute.dataSize() > 65535); + const bool guid = (attribute.type() == Attribute::GuidType); + + if(!inExtendedContentDescriptionObject && !guid && !largeValue && attribute.language() == 0 && attribute.stream() == 0) { + d->extendedContentDescriptionObject->attributeData.append(attribute.render(name)); + inExtendedContentDescriptionObject = true; + } + else if(!inMetadataObject && !guid && !largeValue && attribute.language() == 0 && attribute.stream() != 0) { + d->metadataObject->attributeData.append(attribute.render(name, 1)); + inMetadataObject = true; + } + else { + d->metadataLibraryObject->attributeData.append(attribute.render(name, 2)); + } + } + } + + ByteVector data; + for(List::ConstIterator it = d->objects.begin(); it != d->objects.end(); ++it) { + data.append((*it)->render(this)); + } + data = headerGuid + ByteVector::fromUInt64LE(data.size() + 30) + ByteVector::fromUInt32LE(d->objects.size()) + ByteVector("\x01\x02", 2) + data; + insert(data, 0, static_cast(d->size)); + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +// private members +//////////////////////////////////////////////////////////////////////////////// + +void ASF::File::read() { if(!isValid()) return; @@ -439,12 +619,12 @@ void ASF::File::read(bool /*readProperties*/, AudioProperties::ReadStyle /*prope d->properties = new ASF::AudioProperties(); bool ok; - d->size = readQWORD(&ok); + d->size = readQWORD(this, &ok); if(!ok) { setValid(false); return; } - int numObjects = readDWORD(&ok); + int numObjects = readDWORD(this, &ok); if(!ok) { setValid(false); return; @@ -452,31 +632,34 @@ void ASF::File::read(bool /*readProperties*/, AudioProperties::ReadStyle /*prope seek(2, Current); for(int i = 0; i < numObjects; i++) { - ByteVector guid = readBlock(16); + guid = readBlock(16); if(guid.size() != 16) { setValid(false); break; } - long size = (long)readQWORD(&ok); + long size = (long)readQWORD(this, &ok); if(!ok) { setValid(false); break; } - BaseObject *obj; + FilePrivate::BaseObject *obj; if(guid == filePropertiesGuid) { - obj = new FilePropertiesObject(); + obj = new FilePrivate::FilePropertiesObject(); } else if(guid == streamPropertiesGuid) { - obj = new StreamPropertiesObject(); + obj = new FilePrivate::StreamPropertiesObject(); } else if(guid == contentDescriptionGuid) { - obj = new ContentDescriptionObject(); + obj = new FilePrivate::ContentDescriptionObject(); } else if(guid == extendedContentDescriptionGuid) { - obj = new ExtendedContentDescriptionObject(); + obj = new FilePrivate::ExtendedContentDescriptionObject(); } else if(guid == headerExtensionGuid) { - obj = new HeaderExtensionObject(); + obj = new FilePrivate::HeaderExtensionObject(); + } + else if(guid == codecListGuid) { + obj = new FilePrivate::CodecListObject(); } else { if(guid == contentEncryptionGuid || @@ -484,149 +667,9 @@ void ASF::File::read(bool /*readProperties*/, AudioProperties::ReadStyle /*prope guid == advancedContentEncryptionGuid) { d->properties->setEncrypted(true); } - obj = new UnknownObject(guid); + obj = new FilePrivate::UnknownObject(guid); } obj->parse(this, size); d->objects.append(obj); } } - -bool ASF::File::save() -{ - if(readOnly()) { - debug("ASF::File::save() -- File is read only."); - return false; - } - - if(!isValid()) { - debug("ASF::File::save() -- Trying to save invalid file."); - return false; - } - - if(!d->contentDescriptionObject) { - d->contentDescriptionObject = new ContentDescriptionObject(); - d->objects.append(d->contentDescriptionObject); - } - if(!d->extendedContentDescriptionObject) { - d->extendedContentDescriptionObject = new ExtendedContentDescriptionObject(); - d->objects.append(d->extendedContentDescriptionObject); - } - if(!d->headerExtensionObject) { - d->headerExtensionObject = new HeaderExtensionObject(); - d->objects.append(d->headerExtensionObject); - } - if(!d->metadataObject) { - d->metadataObject = new MetadataObject(); - d->headerExtensionObject->objects.append(d->metadataObject); - } - if(!d->metadataLibraryObject) { - d->metadataLibraryObject = new MetadataLibraryObject(); - d->headerExtensionObject->objects.append(d->metadataLibraryObject); - } - - ASF::AttributeListMap::ConstIterator it = d->tag->attributeListMap().begin(); - for(; it != d->tag->attributeListMap().end(); it++) { - const String &name = it->first; - const AttributeList &attributes = it->second; - bool inExtendedContentDescriptionObject = false; - bool inMetadataObject = false; - for(unsigned int j = 0; j < attributes.size(); j++) { - const Attribute &attribute = attributes[j]; - bool largeValue = attribute.dataSize() > 65535; - if(!inExtendedContentDescriptionObject && !largeValue && attribute.language() == 0 && attribute.stream() == 0) { - d->extendedContentDescriptionObject->attributeData.append(attribute.render(name)); - inExtendedContentDescriptionObject = true; - } - else if(!inMetadataObject && !largeValue && attribute.language() == 0 && attribute.stream() != 0) { - d->metadataObject->attributeData.append(attribute.render(name, 1)); - inMetadataObject = true; - } - else { - d->metadataLibraryObject->attributeData.append(attribute.render(name, 2)); - } - } - } - - ByteVector data; - for(unsigned int i = 0; i < d->objects.size(); i++) { - data.append(d->objects[i]->render(this)); - } - data = headerGuid + ByteVector::fromUInt64LE(data.size() + 30) + ByteVector::fromUInt32LE(d->objects.size()) + ByteVector("\x01\x02", 2) + data; - insert(data, 0, (uint)d->size); - - return true; -} - -//////////////////////////////////////////////////////////////////////////////// -// protected members -//////////////////////////////////////////////////////////////////////////////// - -int ASF::File::readBYTE(bool *ok) -{ - ByteVector v = readBlock(1); - if(v.size() != 1) { - if(ok) *ok = false; - return 0; - } - if(ok) *ok = true; - return v[0]; -} - -int ASF::File::readWORD(bool *ok) -{ - ByteVector v = readBlock(2); - if(v.size() != 2) { - if(ok) *ok = false; - return 0; - } - if(ok) *ok = true; - return v.toUInt16LE(0); -} - -unsigned int ASF::File::readDWORD(bool *ok) -{ - ByteVector v = readBlock(4); - if(v.size() != 4) { - if(ok) *ok = false; - return 0; - } - if(ok) *ok = true; - return v.toUInt32LE(0); -} - -long long ASF::File::readQWORD(bool *ok) -{ - ByteVector v = readBlock(8); - if(v.size() != 8) { - if(ok) *ok = false; - return 0; - } - if(ok) *ok = true; - return v.toInt64LE(0); -} - -String ASF::File::readString(int length) -{ - ByteVector data = readBlock(length); - size_t size = data.size(); - while (size >= 2) { - if(data[size - 1] != '\0' || data[size - 2] != '\0') { - break; - } - size -= 2; - } - if(size != data.size()) { - data.resize(size); - } - return String(data, String::UTF16LE); -} - -ByteVector ASF::File::renderString(const String &str, bool includeLength) -{ - ByteVector data = str.data(String::UTF16LE) + ByteVector::fromUInt16LE(0); - if(includeLength) { - data = ByteVector::fromUInt16LE(data.size()) + data; - } - return data; -} - diff --git a/taglib/asf/asffile.h b/taglib/asf/asffile.h index a3c618ed..1722e86c 100644 --- a/taglib/asf/asffile.h +++ b/taglib/asf/asffile.h @@ -56,7 +56,7 @@ namespace TagLib { * \a propertiesStyle are ignored. The audio properties are always * read. */ - File(FileName file, bool readProperties = true, + File(FileName file, bool readProperties = true, AudioProperties::ReadStyle propertiesStyle = AudioProperties::Average); /*! @@ -71,7 +71,7 @@ namespace TagLib { * \note TagLib will *not* take ownership of the stream, the caller is * responsible for deleting it after the File object. */ - File(IOStream *stream, bool readProperties = true, + File(IOStream *stream, bool readProperties = true, AudioProperties::ReadStyle propertiesStyle = AudioProperties::Average); /*! @@ -116,30 +116,14 @@ namespace TagLib { * Save the file. * * This returns true if the save was successful. + * + * \warning In the current implementation, it's dangerous to call save() + * repeatedly. At worst it will corrupt the file. */ virtual bool save(); private: - int readBYTE(bool *ok = 0); - int readWORD(bool *ok = 0); - unsigned int readDWORD(bool *ok = 0); - long long readQWORD(bool *ok = 0); - static ByteVector renderString(const String &str, bool includeLength = false); - String readString(int len); - void read(bool readProperties, AudioProperties::ReadStyle propertiesStyle); - - friend class Attribute; - friend class Picture; - - class BaseObject; - class UnknownObject; - class FilePropertiesObject; - class StreamPropertiesObject; - class ContentDescriptionObject; - class ExtendedContentDescriptionObject; - class HeaderExtensionObject; - class MetadataObject; - class MetadataLibraryObject; + void read(); class FilePrivate; FilePrivate *d; diff --git a/taglib/asf/asfpicture.cpp b/taglib/asf/asfpicture.cpp index b4f10ea8..e3fa8928 100644 --- a/taglib/asf/asfpicture.cpp +++ b/taglib/asf/asfpicture.cpp @@ -23,12 +23,14 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ -#include "taglib.h" -#include "tdebug.h" -#include "tsmartptr.h" +#include +#include +#include + #include "asfattribute.h" #include "asffile.h" #include "asfpicture.h" +#include "asfutils.h" using namespace TagLib; @@ -44,13 +46,11 @@ namespace }; } -class ASF::Picture::PicturePrivate +class ASF::Picture::PicturePrivate { public: - PicturePrivate() - : data(new PictureData()) - { - } + PicturePrivate() : + data(new PictureData()) {} SHARED_PTR data; }; @@ -141,8 +141,8 @@ ByteVector ASF::Picture::render() const return ByteVector((char)d->data->type) + ByteVector::fromUInt32LE(d->data->picture.size()) + - ASF::File::renderString(d->data->mimeType) + - ASF::File::renderString(d->data->description) + + renderString(d->data->mimeType) + + renderString(d->data->description) + d->data->picture; } diff --git a/taglib/asf/asfpicture.h b/taglib/asf/asfpicture.h index 2c07cb63..13866f2c 100644 --- a/taglib/asf/asfpicture.h +++ b/taglib/asf/asfpicture.h @@ -47,7 +47,7 @@ namespace TagLib * \see Attribute::toPicture() * \see Attribute::Attribute(const Picture& picture) */ - class TAGLIB_EXPORT Picture + class TAGLIB_EXPORT Picture { public: diff --git a/taglib/asf/asfproperties.cpp b/taglib/asf/asfproperties.cpp index 0174f659..669f5a5a 100644 --- a/taglib/asf/asfproperties.cpp +++ b/taglib/asf/asfproperties.cpp @@ -32,17 +32,23 @@ using namespace TagLib; class ASF::AudioProperties::PropertiesPrivate { public: - PropertiesPrivate() : - length(0), - bitrate(0), - sampleRate(0), - channels(0), + PropertiesPrivate() : + length(0), + bitrate(0), + sampleRate(0), + channels(0), + bitsPerSample(0), + codec(ASF::AudioProperties::Unknown), encrypted(false) {} int length; int bitrate; int sampleRate; int channels; + int bitsPerSample; + ASF::AudioProperties::Codec codec; + String codecName; + String codecDescription; bool encrypted; }; @@ -51,6 +57,7 @@ public: //////////////////////////////////////////////////////////////////////////////// ASF::AudioProperties::AudioProperties() : + TagLib::AudioProperties(), d(new PropertiesPrivate()) { } @@ -61,6 +68,16 @@ ASF::AudioProperties::~AudioProperties() } int ASF::AudioProperties::length() const +{ + return lengthInSeconds(); +} + +int ASF::AudioProperties::lengthInSeconds() const +{ + return d->length / 1000; +} + +int ASF::AudioProperties::lengthInMilliseconds() const { return d->length; } @@ -80,6 +97,26 @@ int ASF::AudioProperties::channels() const return d->channels; } +int ASF::AudioProperties::bitsPerSample() const +{ + return d->bitsPerSample; +} + +ASF::AudioProperties::Codec ASF::AudioProperties::codec() const +{ + return d->codec; +} + +String ASF::AudioProperties::codecName() const +{ + return d->codecName; +} + +String ASF::AudioProperties::codecDescription() const +{ + return d->codecDescription; +} + bool ASF::AudioProperties::isEncrypted() const { return d->encrypted; @@ -89,28 +126,65 @@ bool ASF::AudioProperties::isEncrypted() const // private members //////////////////////////////////////////////////////////////////////////////// -void ASF::AudioProperties::setLength(int length) + +void ASF::AudioProperties::setLengthInMilliseconds(int value) { - d->length = length; + d->length = value; } -void ASF::AudioProperties::setBitrate(int length) +void ASF::AudioProperties::setBitrate(int value) { - d->bitrate = length; + d->bitrate = value; } -void ASF::AudioProperties::setSampleRate(int length) +void ASF::AudioProperties::setSampleRate(int value) { - d->sampleRate = length; + d->sampleRate = value; } -void ASF::AudioProperties::setChannels(int length) +void ASF::AudioProperties::setChannels(int value) { - d->channels = length; + d->channels = value; } -void ASF::AudioProperties::setEncrypted(bool encrypted) +void ASF::AudioProperties::setBitsPerSample(int value) { - d->encrypted = encrypted; + d->bitsPerSample = value; } +void ASF::AudioProperties::setCodec(int value) +{ + switch(value) + { + case 0x0160: + d->codec = WMA1; + break; + case 0x0161: + d->codec = WMA2; + break; + case 0x0162: + d->codec = WMA9Pro; + break; + case 0x0163: + d->codec = WMA9Lossless; + break; + default: + d->codec = Unknown; + break; + } +} + +void ASF::AudioProperties::setCodecName(const String &value) +{ + d->codecName = value; +} + +void ASF::AudioProperties::setCodecDescription(const String &value) +{ + d->codecDescription = value; +} + +void ASF::AudioProperties::setEncrypted(bool value) +{ + d->encrypted = value; +} diff --git a/taglib/asf/asfproperties.h b/taglib/asf/asfproperties.h index bacbc9a6..37d0243a 100644 --- a/taglib/asf/asfproperties.h +++ b/taglib/asf/asfproperties.h @@ -42,6 +42,37 @@ namespace TagLib { friend class File; public: + /*! + * Audio codec types can be used in ASF file. + */ + enum Codec + { + /*! + * Couldn't detect the codec. + */ + Unknown = 0, + + /*! + * Windows Media Audio 1 + */ + WMA1, + + /*! + * Windows Media Audio 2 or above + */ + WMA2, + + /*! + * Windows Media Audio 9 Professional + */ + WMA9Pro, + + /*! + * Windows Media Audio 9 Lossless + */ + WMA9Lossless, + }; + /*! * Creates an instance of ASF::AudioProperties. */ @@ -52,19 +83,94 @@ namespace TagLib { */ virtual ~AudioProperties(); - // Reimplementations. - + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \note This method is just an alias of lengthInSeconds(). + * + * \deprecated + */ virtual int length() const; + + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \see lengthInMilliseconds() + */ + // BIC: make virtual + int lengthInSeconds() const; + + /*! + * Returns the length of the file in milliseconds. + * + * \see lengthInSeconds() + */ + // BIC: make virtual + int lengthInMilliseconds() const; + + /*! + * Returns the average bit rate of the file in kb/s. + */ virtual int bitrate() const; + + /*! + * Returns the sample rate in Hz. + */ virtual int sampleRate() const; + + /*! + * Returns the number of audio channels. + */ virtual int channels() const; + + /*! + * Returns the number of bits per audio sample. + */ + int bitsPerSample() const; + + /*! + * Returns the codec used in the file. + * + * \see codecName() + * \see codecDescription() + */ + Codec codec() const; + + /*! + * Returns the concrete codec name, for example "Windows Media Audio 9.1" + * used in the file if available, otherwise an empty string. + * + * \see codec() + * \see codecDescription() + */ + String codecName() const; + + /*! + * Returns the codec description, typically contains the encoder settings, + * for example "VBR Quality 50, 44kHz, stereo 1-pass VBR" if available, + * otherwise an empty string. + * + * \see codec() + * \see codecName() + */ + String codecDescription() const; + + /*! + * Returns whether or not the file is encrypted. + */ bool isEncrypted() const; private: - void setLength(int value); + void setLengthInMilliseconds(int value); void setBitrate(int value); void setSampleRate(int value); void setChannels(int value); + void setBitsPerSample(int value); + void setCodec(int value); + void setCodecName(const String &value); + void setCodecDescription(const String &value); void setEncrypted(bool value); class PropertiesPrivate; diff --git a/taglib/asf/asftag.cpp b/taglib/asf/asftag.cpp index 197b3a4e..b58871c0 100644 --- a/taglib/asf/asftag.cpp +++ b/taglib/asf/asftag.cpp @@ -47,8 +47,7 @@ ASF::Tag::Tag() ASF::Tag::~Tag() { - if(d) - delete d; + delete d; } String ASF::Tag::title() const @@ -161,11 +160,24 @@ ASF::AttributeListMap& ASF::Tag::attributeListMap() return d->attributeListMap; } +const ASF::AttributeListMap &ASF::Tag::attributeListMap() const +{ + return d->attributeListMap; +} + +bool ASF::Tag::contains(const String &key) const +{ + return d->attributeListMap.contains(key); +} + void ASF::Tag::removeItem(const String &key) { - AttributeListMap::Iterator it = d->attributeListMap.find(key); - if(it != d->attributeListMap.end()) - d->attributeListMap.erase(it); + d->attributeListMap.erase(key); +} + +ASF::AttributeList ASF::Tag::attribute(const String &name) const +{ + return d->attributeListMap[name]; } void ASF::Tag::setAttribute(const String &name, const Attribute &attribute) @@ -175,6 +187,11 @@ void ASF::Tag::setAttribute(const String &name, const Attribute &attribute) d->attributeListMap.insert(name, value); } +void ASF::Tag::setAttribute(const String &name, const AttributeList &values) +{ + d->attributeListMap.insert(name, values); +} + void ASF::Tag::addAttribute(const String &name, const Attribute &attribute) { if(d->attributeListMap.contains(name)) { diff --git a/taglib/asf/asftag.h b/taglib/asf/asftag.h index f68579d8..8f322b18 100644 --- a/taglib/asf/asftag.h +++ b/taglib/asf/asftag.h @@ -152,24 +152,43 @@ namespace TagLib { virtual bool isEmpty() const; /*! - * Returns a reference to the item list map. This is an AttributeListMap of - * all of the items in the tag. - * - * This is the most powerfull structure for accessing the items of the tag. + * \deprecated */ AttributeListMap &attributeListMap(); + /*! + * Returns a reference to the item list map. This is an AttributeListMap of + * all of the items in the tag. + */ + const AttributeListMap &attributeListMap() const; + + /*! + * \return True if a value for \a attribute is currently set. + */ + bool contains(const String &name) const; + /*! * Removes the \a key attribute from the tag */ void removeItem(const String &name); + /*! + * \return The list of values for the key \a name, or an empty list if no + * values have been set. + */ + AttributeList attribute(const String &name) const; + /*! * Sets the \a key attribute to the value of \a attribute. If an attribute * with the \a key is already present, it will be replaced. */ void setAttribute(const String &name, const Attribute &attribute); + /*! + * Sets multiple \a values to the key \a name. + */ + void setAttribute(const String &name, const AttributeList &values); + /*! * Sets the \a key attribute to the value of \a attribute. If an attribute * with the \a key is already present, it will be added to the list. diff --git a/taglib/asf/asfutils.h b/taglib/asf/asfutils.h new file mode 100644 index 00000000..02d65f96 --- /dev/null +++ b/taglib/asf/asfutils.h @@ -0,0 +1,101 @@ +/*************************************************************************** + 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_ASFUTILS_H +#define TAGLIB_ASFUTILS_H + +// THIS FILE IS NOT A PART OF THE TAGLIB API + +#ifndef DO_NOT_DOCUMENT // tell Doxygen not to document this header + +namespace TagLib +{ + namespace ASF + { + + inline ushort readWORD(File *file, bool *ok = 0) + { + const ByteVector v = file->readBlock(2); + if(v.size() != 2) { + if(ok) *ok = false; + return 0; + } + if(ok) *ok = true; + return v.toUInt16LE(0); + } + + inline uint readDWORD(File *file, bool *ok = 0) + { + const ByteVector v = file->readBlock(4); + if(v.size() != 4) { + if(ok) *ok = false; + return 0; + } + if(ok) *ok = true; + return v.toUInt32LE(0); + } + + inline long long readQWORD(File *file, bool *ok = 0) + { + const ByteVector v = file->readBlock(8); + if(v.size() != 8) { + if(ok) *ok = false; + return 0; + } + if(ok) *ok = true; + return v.toInt64LE(0); + } + + inline String readString(File *file, int length) + { + ByteVector data = file->readBlock(length); + unsigned int size = data.size(); + while (size >= 2) { + if(data[size - 1] != '\0' || data[size - 2] != '\0') { + break; + } + size -= 2; + } + if(size != data.size()) { + data.resize(size); + } + return String(data, String::UTF16LE); + } + + inline ByteVector renderString(const String &str, bool includeLength = false) + { + ByteVector data = str.data(String::UTF16LE) + ByteVector::fromUInt16LE(0); + if(includeLength) { + data = ByteVector::fromUInt16LE(data.size()) + data; + } + return data; + } + + } +} + +#endif + +#endif diff --git a/taglib/audioproperties.cpp b/taglib/audioproperties.cpp index 231d47b9..fc811e98 100644 --- a/taglib/audioproperties.cpp +++ b/taglib/audioproperties.cpp @@ -23,6 +23,22 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ +#include + +#include "aiffproperties.h" +#include "apeproperties.h" +#include "asfproperties.h" +#include "flacproperties.h" +#include "mp4properties.h" +#include "mpcproperties.h" +#include "mpegproperties.h" +#include "opusproperties.h" +#include "speexproperties.h" +#include "trueaudioproperties.h" +#include "vorbisproperties.h" +#include "wavproperties.h" +#include "wavpackproperties.h" + #include "audioproperties.h" #include "tstringlist.h" @@ -45,10 +61,107 @@ String AudioProperties::toString() const return desc.toString(", "); } +int TagLib::AudioProperties::lengthInSeconds() const +{ + // This is an ugly workaround but we can't add a virtual function. + // Should be virtual in taglib2. + + if(dynamic_cast(this)) + return dynamic_cast(this)->lengthInSeconds(); + + else if(dynamic_cast(this)) + return dynamic_cast(this)->lengthInSeconds(); + + else if(dynamic_cast(this)) + return dynamic_cast(this)->lengthInSeconds(); + + else if(dynamic_cast(this)) + return dynamic_cast(this)->lengthInSeconds(); + + else if(dynamic_cast(this)) + return dynamic_cast(this)->lengthInSeconds(); + + else if(dynamic_cast(this)) + return dynamic_cast(this)->lengthInSeconds(); + + else if(dynamic_cast(this)) + return dynamic_cast(this)->lengthInSeconds(); + + else if(dynamic_cast(this)) + return dynamic_cast(this)->lengthInSeconds(); + + else if(dynamic_cast(this)) + return dynamic_cast(this)->lengthInSeconds(); + + else if (dynamic_cast(this)) + return dynamic_cast(this)->lengthInSeconds(); + + else if(dynamic_cast(this)) + return dynamic_cast(this)->lengthInSeconds(); + + else if(dynamic_cast(this)) + return dynamic_cast(this)->lengthInSeconds(); + + else if(dynamic_cast(this)) + return dynamic_cast(this)->lengthInSeconds(); + + else + return 0; +} + +int TagLib::AudioProperties::lengthInMilliseconds() const +{ + // This is an ugly workaround but we can't add a virtual function. + // Should be virtual in taglib2. + + if(dynamic_cast(this)) + return dynamic_cast(this)->lengthInMilliseconds(); + + else if(dynamic_cast(this)) + return dynamic_cast(this)->lengthInMilliseconds(); + + else if(dynamic_cast(this)) + return dynamic_cast(this)->lengthInMilliseconds(); + + else if(dynamic_cast(this)) + return dynamic_cast(this)->lengthInMilliseconds(); + + else if(dynamic_cast(this)) + return dynamic_cast(this)->lengthInMilliseconds(); + + else if(dynamic_cast(this)) + return dynamic_cast(this)->lengthInMilliseconds(); + + else if(dynamic_cast(this)) + return dynamic_cast(this)->lengthInMilliseconds(); + + else if(dynamic_cast(this)) + return dynamic_cast(this)->lengthInMilliseconds(); + + else if(dynamic_cast(this)) + return dynamic_cast(this)->lengthInMilliseconds(); + + else if(dynamic_cast(this)) + return dynamic_cast(this)->lengthInMilliseconds(); + + else if(dynamic_cast(this)) + return dynamic_cast(this)->lengthInMilliseconds(); + + else if(dynamic_cast(this)) + return dynamic_cast(this)->lengthInMilliseconds(); + + else if(dynamic_cast(this)) + return dynamic_cast(this)->lengthInMilliseconds(); + + else + return 0; +} + //////////////////////////////////////////////////////////////////////////////// // protected methods //////////////////////////////////////////////////////////////////////////////// -AudioProperties::AudioProperties() +AudioProperties::AudioProperties() : + d(0) { } diff --git a/taglib/audioproperties.h b/taglib/audioproperties.h index c7449dd4..86194bb8 100644 --- a/taglib/audioproperties.h +++ b/taglib/audioproperties.h @@ -35,7 +35,7 @@ namespace TagLib { /*! * The values here are common to most audio formats. For more specific, codec - * dependant values, please see see the subclasses APIs. This is meant to + * dependent values, please see see the subclasses APIs. This is meant to * compliment the TagLib::File and TagLib::Tag APIs in providing a simple * interface that is sufficient for most applications. */ @@ -70,6 +70,23 @@ namespace TagLib { */ virtual int length() const = 0; + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \see lengthInMilliseconds() + */ + // BIC: make virtual + int lengthInSeconds() const; + + /*! + * Returns the length of the file in milliseconds. + * + * \see lengthInSeconds() + */ + // BIC: make virtual + int lengthInMilliseconds() const; + /*! * Returns the most appropriate bit rate for the file in kb/s. For constant * bitrate formats this is simply the bitrate of the file. For variable @@ -105,6 +122,9 @@ namespace TagLib { // Noncopyable. Derived classes as well. AudioProperties(const AudioProperties &); AudioProperties &operator=(const AudioProperties &); + + class PropertiesPrivate; + PropertiesPrivate *d; }; } diff --git a/taglib/fileref.h b/taglib/fileref.h index d1fab51c..0b149924 100644 --- a/taglib/fileref.h +++ b/taglib/fileref.h @@ -72,7 +72,7 @@ namespace TagLib { * * class MyFileTypeResolver : FileTypeResolver * { - * TagLib::File *createFile(TagLib::FileName *fileName, bool, AudioProperties::ReadStyle) + * TagLib::File *createFile(TagLib::FileName *fileName, bool, AudioProperties::ReadStyle) const * { * if(someCheckForAnMP3File(fileName)) * return new TagLib::MPEG::File(fileName); @@ -129,7 +129,7 @@ namespace TagLib { audioPropertiesStyle = AudioProperties::Average); /*! - * Contruct a FileRef using \a file. The FileRef now takes ownership of the + * Construct a FileRef using \a file. The FileRef now takes ownership of the * pointer and will delete the File when it passes out of scope. */ explicit FileRef(File *file); @@ -227,7 +227,7 @@ namespace TagLib { * is tried. * * Returns a pointer to the added resolver (the same one that's passed in -- - * this is mostly so that static inialializers have something to use for + * this is mostly so that static initializers have something to use for * assignment). * * \see FileTypeResolver @@ -245,7 +245,7 @@ namespace TagLib { * by TagLib for resolution is case-insensitive. * * \note This does not account for any additional file type resolvers that - * are plugged in. Also note that this is not intended to replace a propper + * are plugged in. Also note that this is not intended to replace a proper * mime-type resolution system, but is just here for reference. * * \see FileTypeResolver diff --git a/taglib/flac/flacfile.cpp b/taglib/flac/flacfile.cpp index be9d77c8..4578999e 100644 --- a/taglib/flac/flacfile.cpp +++ b/taglib/flac/flacfile.cpp @@ -60,7 +60,6 @@ public: properties(0), flacStart(0), streamStart(0), - streamLength(0), scanned(false), hasXiphComment(false), hasID3v2(false), @@ -86,13 +85,11 @@ public: TripleTagUnion tag; AudioProperties *properties; - ByteVector streamInfoData; ByteVector xiphCommentData; List blocks; offset_t flacStart; offset_t streamStart; - offset_t streamLength; bool scanned; bool hasXiphComment; @@ -104,28 +101,26 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -FLAC::File::File(FileName file, - bool readProperties, AudioProperties::ReadStyle propertiesStyle, +FLAC::File::File(FileName file, bool readProperties, AudioProperties::ReadStyle, ID3v2::FrameFactory *frameFactory) : - TagLib::File(file) + TagLib::File(file), + d(new FilePrivate()) { - d = new FilePrivate; if(frameFactory) d->ID3v2FrameFactory = frameFactory; if(isOpen()) - read(readProperties, propertiesStyle); + read(readProperties); } -FLAC::File::File(IOStream *stream, - bool readProperties, AudioProperties::ReadStyle propertiesStyle, +FLAC::File::File(IOStream *stream, bool readProperties, AudioProperties::ReadStyle, ID3v2::FrameFactory *frameFactory) : - TagLib::File(stream) + TagLib::File(stream), + d(new FilePrivate()) { - d = new FilePrivate; if(frameFactory) d->ID3v2FrameFactory = frameFactory; if(isOpen()) - read(readProperties, propertiesStyle); + read(readProperties); } FLAC::File::~File() @@ -148,7 +143,6 @@ FLAC::AudioProperties *FLAC::File::audioProperties() const return d->properties; } - bool FLAC::File::save() { if(readOnly()) { @@ -205,9 +199,9 @@ bool FLAC::File::save() // Adjust the padding block(s) - uint originalLength = static_cast(d->streamStart - d->flacStart); - int paddingLength = static_cast(originalLength - data.size() - 4); - if (paddingLength < 0) { + long originalLength = static_cast(d->streamStart - d->flacStart); + int paddingLength = originalLength - data.size() - 4; + if(paddingLength <= 0) { paddingLength = MinPaddingLength; } ByteVector padding = ByteVector::fromUInt32BE(paddingLength); @@ -235,8 +229,16 @@ bool FLAC::File::save() } if(ID3v1Tag()) { - seek(-128, End); + if(d->hasID3v1) { + seek(d->ID3v1Location); + } + else { + seek(0, End); + d->ID3v1Location = tell(); + } + writeBlock(ID3v1Tag()->render()); + d->hasID3v1 = true; } return true; @@ -262,211 +264,6 @@ void FLAC::File::setID3v2FrameFactory(const ID3v2::FrameFactory *factory) d->ID3v2FrameFactory = factory; } - -//////////////////////////////////////////////////////////////////////////////// -// private members -//////////////////////////////////////////////////////////////////////////////// - -void FLAC::File::read(bool readProperties, AudioProperties::ReadStyle propertiesStyle) -{ - // Look for an ID3v2 tag - - d->ID3v2Location = findID3v2(); - - if(d->ID3v2Location >= 0) { - - d->tag.set(FlacID3v2Index, new ID3v2::Tag(this, d->ID3v2Location, d->ID3v2FrameFactory)); - - d->ID3v2OriginalSize = ID3v2Tag()->header()->completeTagSize(); - - if(ID3v2Tag()->header()->tagSize() <= 0) - d->tag.set(FlacID3v2Index, 0); - else - d->hasID3v2 = true; - } - - // Look for an ID3v1 tag - - d->ID3v1Location = findID3v1(); - - if(d->ID3v1Location >= 0) { - d->tag.set(FlacID3v1Index, new ID3v1::Tag(this, d->ID3v1Location)); - d->hasID3v1 = true; - } - - // Look for FLAC metadata, including vorbis comments - - scan(); - - if(!isValid()) - return; - - if(d->hasXiphComment) - d->tag.set(FlacXiphIndex, new Ogg::XiphComment(xiphCommentData())); - else - d->tag.set(FlacXiphIndex, new Ogg::XiphComment); - - if(readProperties) - d->properties = new AudioProperties(d->streamInfoData, d->streamLength, propertiesStyle); -} - -ByteVector FLAC::File::xiphCommentData() const -{ - return (isValid() && d->hasXiphComment) ? d->xiphCommentData : ByteVector(); -} - -void FLAC::File::scan() -{ - // Scan the metadata pages - - if(d->scanned) - return; - - if(!isValid()) - return; - - offset_t nextBlockOffset; - - if(d->hasID3v2) - nextBlockOffset = find("fLaC", d->ID3v2Location + d->ID3v2OriginalSize); - else - nextBlockOffset = find("fLaC"); - - if(nextBlockOffset < 0) { - debug("FLAC::File::scan() -- FLAC stream not found"); - setValid(false); - return; - } - - nextBlockOffset += 4; - d->flacStart = nextBlockOffset; - - seek(nextBlockOffset); - - ByteVector header = readBlock(4); - - // Header format (from spec): - // <1> Last-metadata-block flag - // <7> BLOCK_TYPE - // 0 : STREAMINFO - // 1 : PADDING - // .. - // 4 : VORBIS_COMMENT - // .. - // <24> Length of metadata to follow - - char blockType = header[0] & 0x7f; - bool isLastBlock = (header[0] & 0x80) != 0; - uint length = header.toUInt24BE(1); - - // First block should be the stream_info metadata - - if(blockType != MetadataBlock::StreamInfo) { - debug("FLAC::File::scan() -- invalid FLAC stream"); - setValid(false); - return; - } - - d->streamInfoData = readBlock(length); - d->blocks.append(new UnknownMetadataBlock(blockType, d->streamInfoData)); - nextBlockOffset += length + 4; - - // Search through the remaining metadata - while(!isLastBlock) { - - header = readBlock(4); - blockType = header[0] & 0x7f; - isLastBlock = (header[0] & 0x80) != 0; - length = header.toUInt24BE(1); - - ByteVector data = readBlock(length); - if(data.size() != length || length == 0) { - debug("FLAC::File::scan() -- FLAC stream corrupted"); - setValid(false); - return; - } - - MetadataBlock *block = 0; - - // Found the vorbis-comment - if(blockType == MetadataBlock::VorbisComment) { - if(!d->hasXiphComment) { - d->xiphCommentData = data; - d->hasXiphComment = true; - } - else { - debug("FLAC::File::scan() -- multiple Vorbis Comment blocks found, using the first one"); - } - } - else if(blockType == MetadataBlock::Picture) { - FLAC::Picture *picture = new FLAC::Picture(); - if(picture->parse(data)) { - block = picture; - } - else { - debug("FLAC::File::scan() -- invalid picture found, discarting"); - delete picture; - } - } - - if(!block) { - block = new UnknownMetadataBlock(blockType, data); - } - if(block->code() != MetadataBlock::Padding) { - d->blocks.append(block); - } - else { - delete block; - } - - nextBlockOffset += length + 4; - - if(nextBlockOffset >= File::length()) { - debug("FLAC::File::scan() -- FLAC stream corrupted"); - setValid(false); - return; - } - seek(nextBlockOffset); - } - - // End of metadata, now comes the datastream - - d->streamStart = nextBlockOffset; - d->streamLength = File::length() - d->streamStart; - - if(d->hasID3v1) - d->streamLength -= 128; - - d->scanned = true; -} - -offset_t FLAC::File::findID3v1() -{ - if(!isValid()) - return -1; - - seek(-128, End); - offset_t p = tell(); - - if(readBlock(3) == ID3v1::Tag::fileIdentifier()) - return p; - - return -1; -} - -offset_t FLAC::File::findID3v2() -{ - if(!isValid()) - return -1; - - seek(0); - - if(readBlock(3) == ID3v2::Header::fileIdentifier()) - return 0; - - return -1; -} - List FLAC::File::pictureList() { List pictures; @@ -524,3 +321,216 @@ bool FLAC::File::hasID3v2Tag() const { return d->hasID3v2; } + +//////////////////////////////////////////////////////////////////////////////// +// private members +//////////////////////////////////////////////////////////////////////////////// + +void FLAC::File::read(bool readProperties) +{ + // Look for an ID3v2 tag + + d->ID3v2Location = findID3v2(); + + if(d->ID3v2Location >= 0) { + + d->tag.set(FlacID3v2Index, new ID3v2::Tag(this, d->ID3v2Location, d->ID3v2FrameFactory)); + + d->ID3v2OriginalSize = ID3v2Tag()->header()->completeTagSize(); + + if(ID3v2Tag()->header()->tagSize() <= 0) + d->tag.set(FlacID3v2Index, 0); + else + d->hasID3v2 = true; + } + + // Look for an ID3v1 tag + + d->ID3v1Location = findID3v1(); + + if(d->ID3v1Location >= 0) { + d->tag.set(FlacID3v1Index, new ID3v1::Tag(this, d->ID3v1Location)); + d->hasID3v1 = true; + } + + // Look for FLAC metadata, including vorbis comments + + scan(); + + if(!isValid()) + return; + + if(d->hasXiphComment) + d->tag.set(FlacXiphIndex, new Ogg::XiphComment(d->xiphCommentData)); + else + d->tag.set(FlacXiphIndex, new Ogg::XiphComment); + + if(readProperties) { + + // First block should be the stream_info metadata + + const ByteVector infoData = d->blocks.front()->render(); + + offset_t streamLength; + + if(d->hasID3v1) + streamLength = d->ID3v1Location - d->streamStart; + else + streamLength = File::length() - d->streamStart; + + d->properties = new AudioProperties(infoData, streamLength); + } +} + +void FLAC::File::scan() +{ + // Scan the metadata pages + + if(d->scanned) + return; + + if(!isValid()) + return; + + offset_t nextBlockOffset; + + if(d->hasID3v2) + nextBlockOffset = find("fLaC", d->ID3v2Location + d->ID3v2OriginalSize); + else + nextBlockOffset = find("fLaC"); + + if(nextBlockOffset < 0) { + debug("FLAC::File::scan() -- FLAC stream not found"); + setValid(false); + return; + } + + nextBlockOffset += 4; + d->flacStart = nextBlockOffset; + + seek(nextBlockOffset); + + ByteVector header = readBlock(4); + + // Header format (from spec): + // <1> Last-metadata-block flag + // <7> BLOCK_TYPE + // 0 : STREAMINFO + // 1 : PADDING + // .. + // 4 : VORBIS_COMMENT + // .. + // <24> Length of metadata to follow + + char blockType = header[0] & 0x7f; + bool isLastBlock = (header[0] & 0x80) != 0; + uint length = header.toUInt24BE(1); + + // First block should be the stream_info metadata + + if(blockType != MetadataBlock::StreamInfo) { + debug("FLAC::File::scan() -- invalid FLAC stream"); + setValid(false); + return; + } + + d->blocks.append(new UnknownMetadataBlock(blockType, readBlock(length))); + nextBlockOffset += length + 4; + + // Search through the remaining metadata + while(!isLastBlock) { + + header = readBlock(4); + blockType = header[0] & 0x7f; + isLastBlock = (header[0] & 0x80) != 0; + length = header.toUInt24BE(1); + + if(length == 0 && blockType != MetadataBlock::Padding) { + debug("FLAC::File::scan() -- Zero-sized metadata block found"); + setValid(false); + return; + } + + const ByteVector data = readBlock(length); + if(data.size() != length) { + debug("FLAC::File::scan() -- Failed to read a metadata block"); + setValid(false); + return; + } + + MetadataBlock *block = 0; + + // Found the vorbis-comment + if(blockType == MetadataBlock::VorbisComment) { + if(!d->hasXiphComment) { + d->xiphCommentData = data; + d->hasXiphComment = true; + } + else { + debug("FLAC::File::scan() -- multiple Vorbis Comment blocks found, using the first one"); + } + } + else if(blockType == MetadataBlock::Picture) { + FLAC::Picture *picture = new FLAC::Picture(); + if(picture->parse(data)) { + block = picture; + } + else { + debug("FLAC::File::scan() -- invalid picture found, discarding"); + delete picture; + } + } + + if(!block) { + block = new UnknownMetadataBlock(blockType, data); + } + if(block->code() != MetadataBlock::Padding) { + d->blocks.append(block); + } + else { + delete block; + } + + nextBlockOffset += length + 4; + + if(nextBlockOffset >= File::length()) { + debug("FLAC::File::scan() -- FLAC stream corrupted"); + setValid(false); + return; + } + seek(nextBlockOffset); + } + + // End of metadata, now comes the datastream + + d->streamStart = nextBlockOffset; + + d->scanned = true; +} + +offset_t FLAC::File::findID3v1() +{ + if(!isValid()) + return -1; + + seek(-128, End); + offset_t p = tell(); + + if(readBlock(3) == ID3v1::Tag::fileIdentifier()) + return p; + + return -1; +} + +offset_t FLAC::File::findID3v2() +{ + if(!isValid()) + return -1; + + seek(0); + + if(readBlock(3) == ID3v2::Header::fileIdentifier()) + return 0; + + return -1; +} diff --git a/taglib/flac/flacfile.h b/taglib/flac/flacfile.h index 740a2cfc..0372f502 100644 --- a/taglib/flac/flacfile.h +++ b/taglib/flac/flacfile.h @@ -46,7 +46,7 @@ namespace TagLib { /*! * This is implementation of FLAC metadata for non-Ogg FLAC files. At some * point when Ogg / FLAC is more common there will be a similar implementation - * under the Ogg hiearchy. + * under the Ogg hierarchy. * * This supports ID3v1, ID3v2 and Xiph style comments as well as reading stream * properties from the file. @@ -67,7 +67,7 @@ namespace TagLib { { public: /*! - * Constructs a FLAC file from \a file. If \a readProperties is true the + * Constructs an FLAC file from \a file. If \a readProperties is true the * file's audio properties will also be read. * * If this file contains and ID3v2 tag the frames will be created using @@ -133,6 +133,9 @@ namespace TagLib { * has no XiphComment, one will be constructed from the ID3-tags. * * This returns true if the save was successful. + * + * \warning In the current implementation, it's dangerous to call save() + * repeatedly. At worst it will corrupt the file. */ virtual bool save(); @@ -143,8 +146,8 @@ namespace TagLib { * if there is no valid ID3v2 tag. If \a create is true it will create * an ID3v2 tag if one does not exist and returns a valid pointer. * - * \note This may return a valid pointer regardless of whether or not the - * file on disk has an ID3v2 tag. Use hasID3v2Tag() to check if the file + * \note This may return 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. * * \note The Tag is still owned by the MPEG::File and should not be @@ -162,8 +165,8 @@ namespace TagLib { * if there is no valid APE tag. If \a create is true it will create * an APE tag if one does not exist and returns a valid pointer. * - * \note This may return a valid pointer regardless of whether or not the - * file on disk has an ID3v1 tag. Use hasID3v1Tag() to check if the file + * \note This may return a valid pointer regardless of whether or not the + * file on disk has an ID3v1 tag. Use hasID3v1Tag() to check if the file * on disk actually has an ID3v1 tag. * * \note The Tag is still owned by the MPEG::File and should not be @@ -181,10 +184,10 @@ namespace TagLib { * if there is no valid XiphComment. If \a create is true it will create * a XiphComment if one does not exist and returns a valid pointer. * - * \note This may return a valid pointer regardless of whether or not the - * file on disk has a XiphComment. Use hasXiphComment() to check if the + * \note This may return a valid pointer regardless of whether or not the + * file on disk has a XiphComment. Use hasXiphComment() to check if the * file on disk actually has a XiphComment. - * + * * \note The Tag is still owned by the FLAC::File and should not be * deleted by the user. It will be deleted when the file (object) is * destroyed. @@ -199,6 +202,7 @@ namespace TagLib { * when * * \see ID3v2FrameFactory + * \deprecated This value should be passed in via the constructor */ void setID3v2FrameFactory(const ID3v2::FrameFactory *factory); @@ -251,12 +255,10 @@ namespace TagLib { File(const File &); File &operator=(const File &); - void read(bool readProperties, AudioProperties::ReadStyle propertiesStyle); + void read(bool readProperties); void scan(); offset_t findID3v2(); offset_t findID3v1(); - ByteVector xiphCommentData() const; - offset_t findPaddingBreak(long nextPageOffset, long targetOffset, bool *isLast); class FilePrivate; FilePrivate *d; diff --git a/taglib/flac/flacproperties.cpp b/taglib/flac/flacproperties.cpp index 6af8f11a..55b1a7ee 100644 --- a/taglib/flac/flacproperties.cpp +++ b/taglib/flac/flacproperties.cpp @@ -38,14 +38,14 @@ public: length(0), bitrate(0), sampleRate(0), - sampleWidth(0), + bitsPerSample(0), channels(0), sampleFrames(0) {} int length; int bitrate; int sampleRate; - int sampleWidth; + int bitsPerSample; int channels; unsigned long long sampleFrames; ByteVector signature; @@ -55,8 +55,8 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -FLAC::AudioProperties::AudioProperties(const ByteVector &data, offset_t streamLength, - ReadStyle style) : +FLAC::AudioProperties::AudioProperties(const ByteVector &data, offset_t streamLength, ReadStyle) : + TagLib::AudioProperties(), d(new PropertiesPrivate()) { read(data, streamLength); @@ -68,6 +68,16 @@ FLAC::AudioProperties::~AudioProperties() } int FLAC::AudioProperties::length() const +{ + return lengthInSeconds(); +} + +int FLAC::AudioProperties::lengthInSeconds() const +{ + return d->length / 1000; +} + +int FLAC::AudioProperties::lengthInMilliseconds() const { return d->length; } @@ -82,9 +92,14 @@ int FLAC::AudioProperties::sampleRate() const return d->sampleRate; } +int FLAC::AudioProperties::bitsPerSample() const +{ + return d->bitsPerSample; +} + int FLAC::AudioProperties::sampleWidth() const { - return d->sampleWidth; + return bitsPerSample(); } int FLAC::AudioProperties::channels() const @@ -130,9 +145,9 @@ void FLAC::AudioProperties::read(const ByteVector &data, offset_t streamLength) const uint flags = data.toUInt32BE(pos); pos += 4; - d->sampleRate = flags >> 12; - d->channels = ((flags >> 9) & 7) + 1; - d->sampleWidth = ((flags >> 4) & 31) + 1; + d->sampleRate = flags >> 12; + d->channels = ((flags >> 9) & 7) + 1; + d->bitsPerSample = ((flags >> 4) & 31) + 1; // The last 4 bits are the most significant 4 bits for the 36 bit // stream length in samples. (Audio files measured in days) @@ -143,16 +158,12 @@ void FLAC::AudioProperties::read(const ByteVector &data, offset_t streamLength) d->sampleFrames = (hi << 32) | lo; - if(d->sampleRate > 0) - d->length = static_cast(d->sampleFrames / d->sampleRate); + if(d->sampleFrames > 0 && d->sampleRate > 0) { + const double length = d->sampleFrames * 1000.0 / d->sampleRate; + d->length = static_cast(length + 0.5); + d->bitrate = static_cast(streamLength * 8.0 / length + 0.5); + } - // Uncompressed bitrate: - - //d->bitrate = ((d->sampleRate * d->channels) / 1000) * d->sampleWidth; - - // Real bitrate: - - d->bitrate = d->length > 0 ? static_cast(streamLength * 8L / d->length / 1000) : 0; - - d->signature = data.mid(pos, 32); + if(data.size() >= pos + 16) + d->signature = data.mid(pos, 16); } diff --git a/taglib/flac/flacproperties.h b/taglib/flac/flacproperties.h index 15df42e6..a36c4477 100644 --- a/taglib/flac/flacproperties.h +++ b/taglib/flac/flacproperties.h @@ -46,7 +46,7 @@ namespace TagLib { { public: /*! - * Creates an instance of FLAC::AudioProperties with the data read from + * Creates an instance of FLAC::AudioProperties with the data read from * the ByteVector \a data. */ AudioProperties(const ByteVector &data, offset_t streamLength, ReadStyle style = Average); @@ -56,27 +56,72 @@ namespace TagLib { */ virtual ~AudioProperties(); - // Reimplementations. - + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \note This method is just an alias of lengthInSeconds(). + * + * \deprecated + */ virtual int length() const; + + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \see lengthInMilliseconds() + */ + // BIC: make virtual + int lengthInSeconds() const; + + /*! + * Returns the length of the file in milliseconds. + * + * \see lengthInSeconds() + */ + // BIC: make virtual + int lengthInMilliseconds() const; + + /*! + * Returns the average bit rate of the file in kb/s. + */ virtual int bitrate() const; + + /*! + * Returns the sample rate in Hz. + */ virtual int sampleRate() const; + + /*! + * Returns the number of audio channels. + */ virtual int channels() const; + /*! + * Returns the number of bits per audio sample as read from the FLAC + * identification header. + */ + int bitsPerSample() const; + /*! * Returns the sample width as read from the FLAC identification * header. + * + * \note This method is just an alias of bitsPerSample(). + * + * \deprecated */ int sampleWidth() const; /*! - * Return the number of sample frames + * Return the number of sample frames. */ unsigned long long sampleFrames() const; /*! * Returns the MD5 signature of the uncompressed audio stream as read - * from the stream info header header. + * from the stream info header. */ ByteVector signature() const; diff --git a/taglib/it/itproperties.cpp b/taglib/it/itproperties.cpp index 912c0733..7a26fda0 100644 --- a/taglib/it/itproperties.cpp +++ b/taglib/it/itproperties.cpp @@ -65,7 +65,8 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -IT::AudioProperties::AudioProperties(AudioProperties::ReadStyle propertiesStyle) : +IT::AudioProperties::AudioProperties(AudioProperties::ReadStyle) : + TagLib::AudioProperties(), d(new PropertiesPrivate()) { } @@ -80,6 +81,16 @@ int IT::AudioProperties::length() const return 0; } +int IT::AudioProperties::lengthInSeconds() const +{ + return 0; +} + +int IT::AudioProperties::lengthInMilliseconds() const +{ + return 0; +} + int IT::AudioProperties::bitrate() const { return 0; diff --git a/taglib/it/itproperties.h b/taglib/it/itproperties.h index 08fbf3a5..9eb138aa 100644 --- a/taglib/it/itproperties.h +++ b/taglib/it/itproperties.h @@ -57,10 +57,12 @@ namespace TagLib { AudioProperties(AudioProperties::ReadStyle propertiesStyle); virtual ~AudioProperties(); - int length() const; - int bitrate() const; - int sampleRate() const; - int channels() const; + int length() const; + int lengthInSeconds() const; + int lengthInMilliseconds() const; + int bitrate() const; + int sampleRate() const; + int channels() const; ushort lengthInPatterns() const; bool stereo() const; diff --git a/taglib/mod/modproperties.cpp b/taglib/mod/modproperties.cpp index 20510c76..c1f0752c 100644 --- a/taglib/mod/modproperties.cpp +++ b/taglib/mod/modproperties.cpp @@ -41,7 +41,8 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -Mod::AudioProperties::AudioProperties(AudioProperties::ReadStyle propertiesStyle) : +Mod::AudioProperties::AudioProperties(AudioProperties::ReadStyle) : + TagLib::AudioProperties(), d(new PropertiesPrivate) { } @@ -56,6 +57,16 @@ int Mod::AudioProperties::length() const return 0; } +int Mod::AudioProperties::lengthInSeconds() const +{ + return 0; +} + +int Mod::AudioProperties::lengthInMilliseconds() const +{ + return 0; +} + int Mod::AudioProperties::bitrate() const { return 0; diff --git a/taglib/mod/modproperties.h b/taglib/mod/modproperties.h index 18a47208..0098521e 100644 --- a/taglib/mod/modproperties.h +++ b/taglib/mod/modproperties.h @@ -39,12 +39,14 @@ namespace TagLib { AudioProperties(AudioProperties::ReadStyle propertiesStyle); virtual ~AudioProperties(); - int length() const; - int bitrate() const; - int sampleRate() const; - int channels() const; + int length() const; + int lengthInSeconds() const; + int lengthInMilliseconds() const; + int bitrate() const; + int sampleRate() const; + int channels() const; - uint instrumentCount() const; + uint instrumentCount() const; uchar lengthInPatterns() const; private: diff --git a/taglib/mod/modtag.h b/taglib/mod/modtag.h index f33e33f2..5b660359 100644 --- a/taglib/mod/modtag.h +++ b/taglib/mod/modtag.h @@ -97,7 +97,7 @@ namespace TagLib { * Sets the title to \a title. If \a title is String::null then this * value will be cleared. * - * The length limits per file type are (1 characetr = 1 byte): + * The length limits per file type are (1 character = 1 byte): * Mod 20 characters, S3M 27 characters, IT 25 characters and XM 20 * characters. */ @@ -126,7 +126,7 @@ namespace TagLib { * an thus the line length in comments are limited. Too big comments * will be truncated. * - * The line length limits per file type are (1 characetr = 1 byte): + * The line length limits per file type are (1 character = 1 byte): * Mod 22 characters, S3M 27 characters, IT 25 characters and XM 22 * characters. */ @@ -169,7 +169,7 @@ namespace TagLib { * Implements the unified property interface -- import function. * Because of the limitations of the module file tag, any tags besides * COMMENT, TITLE and, if it is an XM file, TRACKERNAME, will be - * returened. Additionally, if the map contains tags with multiple values, + * returned. Additionally, if the map contains tags with multiple values, * all but the first will be contained in the returned map of unsupported * properties. */ diff --git a/taglib/mp4/mp4atom.h b/taglib/mp4/mp4atom.h index 33bba830..81ac35db 100644 --- a/taglib/mp4/mp4atom.h +++ b/taglib/mp4/mp4atom.h @@ -59,7 +59,7 @@ namespace TagLib { TypeDateTime = 17, // in UTC, counting seconds since midnight, January 1, 1904; 32 or 64-bits TypeGenred = 18, // a list of enumerated values TypeInteger = 21, // a signed big-endian integer with length one of { 1,2,3,4,8 } bytes - TypeRIAAPA = 24, // RIAA parental advisory; { -1=no, 1=yes, 0=unspecified }, 8-bit ingteger + TypeRIAAPA = 24, // RIAA parental advisory; { -1=no, 1=yes, 0=unspecified }, 8-bit integer TypeUPC = 25, // Universal Product Code, in text UTF-8 format (valid as an ID) TypeBMP = 27, // Windows bitmap image TypeUndefined = 255 // undefined diff --git a/taglib/mp4/mp4file.cpp b/taglib/mp4/mp4file.cpp index e6900b09..c19fee1c 100644 --- a/taglib/mp4/mp4file.cpp +++ b/taglib/mp4/mp4file.cpp @@ -32,48 +32,57 @@ using namespace TagLib; +namespace +{ + bool checkValid(const MP4::AtomList &list) + { + for(MP4::AtomList::ConstIterator it = list.begin(); it != list.end(); ++it) { + + if((*it)->length == 0) + return false; + + if(!checkValid((*it)->children)) + return false; + } + + return true; + } +} + class MP4::File::FilePrivate { public: - FilePrivate() : tag(0), atoms(0), properties(0) - { - } + FilePrivate() : + tag(0), + atoms(0), + properties(0) {} ~FilePrivate() { - if(atoms) { - delete atoms; - atoms = 0; - } - if(tag) { - delete tag; - tag = 0; - } - if(properties) { - delete properties; - properties = 0; - } + delete atoms; + delete tag; + delete properties; } - MP4::Tag *tag; - MP4::Atoms *atoms; + MP4::Tag *tag; + MP4::Atoms *atoms; MP4::AudioProperties *properties; }; -MP4::File::File(FileName file, bool readProperties, AudioProperties::ReadStyle audioPropertiesStyle) - : TagLib::File(file) +MP4::File::File(FileName file, bool readProperties, AudioProperties::ReadStyle) : + TagLib::File(file), + d(new FilePrivate()) { - d = new FilePrivate; if(isOpen()) - read(readProperties, audioPropertiesStyle); + read(readProperties); } -MP4::File::File(IOStream *stream, bool readProperties, AudioProperties::ReadStyle audioPropertiesStyle) - : TagLib::File(stream) +MP4::File::File(IOStream *stream, bool readProperties, AudioProperties::ReadStyle) : + TagLib::File(stream), + d(new FilePrivate()) { - d = new FilePrivate; if(isOpen()) - read(readProperties, audioPropertiesStyle); + read(readProperties); } MP4::File::~File() @@ -93,26 +102,14 @@ MP4::File::audioProperties() const return d->properties; } -bool -MP4::File::checkValid(const MP4::AtomList &list) -{ - for(uint i = 0; i < list.size(); i++) { - if(list[i]->length == 0) - return false; - if(!checkValid(list[i]->children)) - return false; - } - return true; -} - void -MP4::File::read(bool readProperties, AudioProperties::ReadStyle audioPropertiesStyle) +MP4::File::read(bool readProperties) { if(!isValid()) return; d->atoms = new Atoms(this); - if (!checkValid(d->atoms->atoms)) { + if(!checkValid(d->atoms->atoms)) { setValid(false); return; } @@ -126,7 +123,7 @@ MP4::File::read(bool readProperties, AudioProperties::ReadStyle audioPropertiesS d->tag = new Tag(this, d->atoms); if(readProperties) { - d->properties = new AudioProperties(this, d->atoms, audioPropertiesStyle); + d->properties = new AudioProperties(this, d->atoms); } } diff --git a/taglib/mp4/mp4file.h b/taglib/mp4/mp4file.h index 09374bf6..2d39df40 100644 --- a/taglib/mp4/mp4file.h +++ b/taglib/mp4/mp4file.h @@ -54,7 +54,7 @@ namespace TagLib { * * \note In the current implementation, \a propertiesStyle is ignored. */ - File(FileName file, bool readProperties = true, + File(FileName file, bool readProperties = true, AudioProperties::ReadStyle audioPropertiesStyle = AudioProperties::Average); /*! @@ -66,7 +66,7 @@ namespace TagLib { * \note TagLib will *not* take ownership of the stream, the caller is * responsible for deleting it after the File object. */ - File(IOStream *stream, bool readProperties = true, + File(IOStream *stream, bool readProperties = true, AudioProperties::ReadStyle audioPropertiesStyle = AudioProperties::Average); /*! @@ -95,13 +95,14 @@ namespace TagLib { * Save the file. * * This returns true if the save was successful. + * + * \warning In the current implementation, it's dangerous to call save() + * repeatedly. At worst it will corrupt the file. */ bool save(); private: - - void read(bool readProperties, AudioProperties::ReadStyle audioPropertiesStyle); - bool checkValid(const MP4::AtomList &list); + void read(bool readProperties); class FilePrivate; FilePrivate *d; diff --git a/taglib/mp4/mp4properties.cpp b/taglib/mp4/mp4properties.cpp index 2eef4ec2..c674b4fb 100644 --- a/taglib/mp4/mp4properties.cpp +++ b/taglib/mp4/mp4properties.cpp @@ -34,14 +34,14 @@ using namespace TagLib; class MP4::AudioProperties::PropertiesPrivate { public: - PropertiesPrivate() : - length(0), - bitrate(0), - sampleRate(0), - channels(0), - bitsPerSample(0), - encrypted(false), - codec(Unknown) {} + PropertiesPrivate() : + length(0), + bitrate(0), + sampleRate(0), + channels(0), + bitsPerSample(0), + encrypted(false), + codec(MP4::AudioProperties::Unknown) {} int length; int bitrate; @@ -56,7 +56,8 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -MP4::AudioProperties::AudioProperties(File *file, MP4::Atoms *atoms, ReadStyle style) : +MP4::AudioProperties::AudioProperties(File *file, MP4::Atoms *atoms, ReadStyle) : + TagLib::AudioProperties(), d(new PropertiesPrivate()) { read(file, atoms); @@ -81,6 +82,18 @@ MP4::AudioProperties::sampleRate() const int MP4::AudioProperties::length() const +{ + return lengthInSeconds(); +} + +int +MP4::AudioProperties::lengthInSeconds() const +{ + return d->length / 1000; +} + +int +MP4::AudioProperties::lengthInMilliseconds() const { return d->length; } @@ -103,7 +116,7 @@ MP4::AudioProperties::isEncrypted() const return d->encrypted; } -MP4::AudioProperties::Codec +MP4::AudioProperties::Codec MP4::AudioProperties::codec() const { return d->codec; @@ -133,7 +146,7 @@ MP4::AudioProperties::toString() const // private members //////////////////////////////////////////////////////////////////////////////// -void +void MP4::AudioProperties::read(File *file, Atoms *atoms) { MP4::Atom *moov = atoms->find("moov"); @@ -145,22 +158,22 @@ MP4::AudioProperties::read(File *file, Atoms *atoms) MP4::Atom *trak = 0; ByteVector data; - MP4::AtomList trakList = moov->findall("trak"); - for (unsigned int i = 0; i < trakList.size(); i++) { - trak = trakList[i]; + const MP4::AtomList trakList = moov->findall("trak"); + for(MP4::AtomList::ConstIterator it = trakList.begin(); it != trakList.end(); ++it) { + trak = *it; MP4::Atom *hdlr = trak->find("mdia", "hdlr"); if(!hdlr) { debug("MP4: Atom 'trak.mdia.hdlr' not found"); return; } file->seek(hdlr->offset); - data = file->readBlock(hdlr->length); - if(data.mid(16, 4) == "soun") { + data = file->readBlock(static_cast(hdlr->length)); + if(data.containsAt("soun", 16)) { break; } trak = 0; } - if (!trak) { + if(!trak) { debug("MP4: No audio tracks"); return; } @@ -172,26 +185,29 @@ MP4::AudioProperties::read(File *file, Atoms *atoms) } file->seek(mdhd->offset); - data = file->readBlock(mdhd->length); - uint version = data[8]; + data = file->readBlock(static_cast(mdhd->length)); + + const uint version = data[8]; + long long unit; + long long length; if(version == 1) { - if (data.size() < 36 + 8) { + if(data.size() < 36 + 8) { debug("MP4: Atom 'trak.mdia.mdhd' is smaller than expected"); return; } - const long long unit = data.toInt64BE(28); - const long long length = data.toInt64BE(36); - d->length = unit ? int(length / unit) : 0; + unit = data.toInt64BE(28); + length = data.toInt64BE(36); } else { - if (data.size() < 24 + 4) { + if(data.size() < 24 + 4) { debug("MP4: Atom 'trak.mdia.mdhd' is smaller than expected"); return; } - const unsigned int unit = data.toUInt32BE(20); - const unsigned int length = data.toUInt32BE(24); - d->length = unit ? length / unit : 0; + unit = data.toUInt32BE(20); + length = data.toUInt32BE(24); } + if(unit > 0 && length > 0) + d->length = static_cast(length * 1000.0 / unit); MP4::Atom *atom = trak->find("mdia", "minf", "stbl", "stsd"); if(!atom) { @@ -199,34 +215,34 @@ MP4::AudioProperties::read(File *file, Atoms *atoms) } file->seek(atom->offset); - data = file->readBlock(atom->length); - if(data.mid(20, 4) == "mp4a") { - d->codec = AAC; - d->channels = data.toInt16BE(40); - d->bitsPerSample = data.toInt16BE(42); + data = file->readBlock(static_cast(atom->length)); + if(data.containsAt("mp4a", 20)) { + d->codec = AAC; + d->channels = data.toUInt16BE(40); + d->bitsPerSample = data.toUInt16BE(42); d->sampleRate = data.toUInt32BE(46); - if(data.mid(56, 4) == "esds" && data[64] == 0x03) { + if(data.containsAt("esds", 56) && data[64] == 0x03) { uint pos = 65; - if(data.mid(pos, 3) == "\x80\x80\x80") { + if(data.containsAt("\x80\x80\x80", pos)) { pos += 3; } pos += 4; if(data[pos] == 0x04) { pos += 1; - if(data.mid(pos, 3) == "\x80\x80\x80") { + if(data.containsAt("\x80\x80\x80", pos)) { pos += 3; } pos += 10; - d->bitrate = (data.toUInt32BE(pos) + 500) / 1000; + d->bitrate = static_cast((data.toUInt32BE(pos) + 500) / 1000.0 + 0.5); } } } - else if (data.mid(20, 4) == "alac") { - d->codec = ALAC; - if (atom->length == 88 && data.mid(56, 4) == "alac") { + else if(data.containsAt("alac", 20)) { + if(atom->length == 88 && data.containsAt("alac", 56)) { + d->codec = ALAC; d->bitsPerSample = data.at(69); d->channels = data.at(73); - d->bitrate = data.toUInt32BE(80) / 1000; + d->bitrate = static_cast(data.toUInt32BE(80) / 1000.0 + 0.5); d->sampleRate = data.toUInt32BE(84); } } diff --git a/taglib/mp4/mp4properties.h b/taglib/mp4/mp4properties.h index 53f6c01f..955d8a2c 100644 --- a/taglib/mp4/mp4properties.h +++ b/taglib/mp4/mp4properties.h @@ -49,14 +49,61 @@ namespace TagLib { AudioProperties(File *file, Atoms *atoms, ReadStyle style = Average); virtual ~AudioProperties(); + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \note This method is just an alias of lengthInSeconds(). + * + * \deprecated + */ virtual int length() const; + + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \see lengthInMilliseconds() + */ + // BIC: make virtual + int lengthInSeconds() const; + + /*! + * Returns the length of the file in milliseconds. + * + * \see lengthInSeconds() + */ + // BIC: make virtual + int lengthInMilliseconds() const; + + /*! + * Returns the average bit rate of the file in kb/s. + */ virtual int bitrate() const; + + /*! + * Returns the sample rate in Hz. + */ virtual int sampleRate() const; + + /*! + * Returns the number of audio channels. + */ virtual int channels() const; + + /*! + * Returns the number of bits per audio sample. + */ virtual int bitsPerSample() const; + + /*! + * Returns whether or not the file is encrypted. + */ bool isEncrypted() const; - //! Audio codec used in the MP4 file + /*! + * Returns the codec used in the file. + */ Codec codec() const; String toString() const; diff --git a/taglib/mp4/mp4tag.cpp b/taglib/mp4/mp4tag.cpp index ac92b508..71f6b66a 100644 --- a/taglib/mp4/mp4tag.cpp +++ b/taglib/mp4/mp4tag.cpp @@ -39,7 +39,7 @@ public: ~TagPrivate() {} TagLib::File *file; Atoms *atoms; - ItemListMap items; + ItemMap items; }; MP4::Tag::Tag() @@ -63,36 +63,36 @@ MP4::Tag::Tag(TagLib::File *file, MP4::Atoms *atoms) MP4::Atom *atom = ilst->children[i]; file->seek(atom->offset + 8); if(atom->name == "----") { - parseFreeForm(atom, file); + parseFreeForm(atom); } else if(atom->name == "trkn" || atom->name == "disk") { - parseIntPair(atom, file); + parseIntPair(atom); } else if(atom->name == "cpil" || atom->name == "pgap" || atom->name == "pcst" || atom->name == "hdvd") { - parseBool(atom, file); + parseBool(atom); } else if(atom->name == "tmpo") { - parseInt(atom, file); + parseInt(atom); } else if(atom->name == "tvsn" || atom->name == "tves" || atom->name == "cnID" || atom->name == "sfID" || atom->name == "atID" || atom->name == "geID") { - parseUInt(atom, file); + parseUInt(atom); } else if(atom->name == "plID") { - parseLongLong(atom, file); + parseLongLong(atom); } else if(atom->name == "stik" || atom->name == "rtng" || atom->name == "akID") { - parseByte(atom, file); + parseByte(atom); } else if(atom->name == "gnre") { - parseGnre(atom, file); + parseGnre(atom); } else if(atom->name == "covr") { - parseCovr(atom, file); + parseCovr(atom); } else { - parseText(atom, file); + parseText(atom); } } } @@ -103,16 +103,21 @@ MP4::Tag::~Tag() } MP4::AtomDataList -MP4::Tag::parseData2(MP4::Atom *atom, TagLib::File *file, int expectedFlags, bool freeForm) +MP4::Tag::parseData2(const MP4::Atom *atom, int expectedFlags, bool freeForm) { AtomDataList result; - ByteVector data = file->readBlock(atom->length - 8); + ByteVector data = d->file->readBlock(static_cast(atom->length - 8)); int i = 0; size_t pos = 0; while(pos < data.size()) { - const int length = data.toUInt32BE(pos); + const int length = static_cast(data.toUInt32BE(pos)); + if(length < 12) { + debug("MP4: Too short atom"); + return result; + } + const ByteVector name = data.mid(pos + 4, 4); - const int flags = data.toUInt32BE(pos + 8); + const int flags = static_cast(data.toUInt32BE(pos + 8)); if(freeForm && i < 2) { if(i == 0 && name != "mean") { debug("MP4: Unexpected atom \"" + name + "\", expecting \"mean\""); @@ -140,9 +145,9 @@ MP4::Tag::parseData2(MP4::Atom *atom, TagLib::File *file, int expectedFlags, boo } ByteVectorList -MP4::Tag::parseData(MP4::Atom *atom, TagLib::File *file, int expectedFlags, bool freeForm) +MP4::Tag::parseData(const MP4::Atom *atom, int expectedFlags, bool freeForm) { - AtomDataList data = parseData2(atom, file, expectedFlags, freeForm); + AtomDataList data = parseData2(atom, expectedFlags, freeForm); ByteVectorList result; for(uint i = 0; i < data.size(); i++) { result.append(data[i].data); @@ -151,45 +156,45 @@ MP4::Tag::parseData(MP4::Atom *atom, TagLib::File *file, int expectedFlags, bool } void -MP4::Tag::parseInt(MP4::Atom *atom, TagLib::File *file) +MP4::Tag::parseInt(const MP4::Atom *atom) { - ByteVectorList data = parseData(atom, file); + ByteVectorList data = parseData(atom); if(data.size()) { d->items.insert(atom->name, (int)data[0].toInt16BE(0)); } } void -MP4::Tag::parseUInt(MP4::Atom *atom, TagLib::File *file) +MP4::Tag::parseUInt(const MP4::Atom *atom) { - ByteVectorList data = parseData(atom, file); + ByteVectorList data = parseData(atom); if(data.size()) { d->items.insert(atom->name, data[0].toUInt32BE(0)); } } void -MP4::Tag::parseLongLong(MP4::Atom *atom, TagLib::File *file) +MP4::Tag::parseLongLong(const MP4::Atom *atom) { - ByteVectorList data = parseData(atom, file); + ByteVectorList data = parseData(atom); if(data.size()) { d->items.insert(atom->name, data[0].toInt64BE(0)); } } void -MP4::Tag::parseByte(MP4::Atom *atom, TagLib::File *file) +MP4::Tag::parseByte(const MP4::Atom *atom) { - ByteVectorList data = parseData(atom, file); + ByteVectorList data = parseData(atom); if(data.size()) { addItem(atom->name, (uchar)data[0].at(0)); } } void -MP4::Tag::parseGnre(MP4::Atom *atom, TagLib::File *file) +MP4::Tag::parseGnre(const MP4::Atom *atom) { - ByteVectorList data = parseData(atom, file); + ByteVectorList data = parseData(atom); if(data.size()) { const int idx = (int)data[0].toInt16BE(0); if(!d->items.contains("\251gen") && idx > 0) { @@ -199,9 +204,9 @@ MP4::Tag::parseGnre(MP4::Atom *atom, TagLib::File *file) } void -MP4::Tag::parseIntPair(MP4::Atom *atom, TagLib::File *file) +MP4::Tag::parseIntPair(const MP4::Atom *atom) { - ByteVectorList data = parseData(atom, file); + ByteVectorList data = parseData(atom); if(data.size()) { const int a = data[0].toInt16BE(2); const int b = data[0].toInt16BE(4); @@ -210,9 +215,9 @@ MP4::Tag::parseIntPair(MP4::Atom *atom, TagLib::File *file) } void -MP4::Tag::parseBool(MP4::Atom *atom, TagLib::File *file) +MP4::Tag::parseBool(const MP4::Atom *atom) { - ByteVectorList data = parseData(atom, file); + ByteVectorList data = parseData(atom); if(data.size()) { bool value = data[0].size() ? data[0][0] != '\0' : false; addItem(atom->name, value); @@ -220,9 +225,9 @@ MP4::Tag::parseBool(MP4::Atom *atom, TagLib::File *file) } void -MP4::Tag::parseText(MP4::Atom *atom, TagLib::File *file, int expectedFlags) +MP4::Tag::parseText(const MP4::Atom *atom, int expectedFlags) { - ByteVectorList data = parseData(atom, file, expectedFlags); + ByteVectorList data = parseData(atom, expectedFlags); if(data.size()) { StringList value; for(unsigned int i = 0; i < data.size(); i++) { @@ -233,9 +238,9 @@ MP4::Tag::parseText(MP4::Atom *atom, TagLib::File *file, int expectedFlags) } void -MP4::Tag::parseFreeForm(MP4::Atom *atom, TagLib::File *file) +MP4::Tag::parseFreeForm(const MP4::Atom *atom) { - AtomDataList data = parseData2(atom, file, -1, true); + AtomDataList data = parseData2(atom, -1, true); if(data.size() > 2) { String name = "----:" + String(data[0].data, String::UTF8) + ':' + String(data[1].data, String::UTF8); AtomDataType type = data[2].type; @@ -267,20 +272,26 @@ MP4::Tag::parseFreeForm(MP4::Atom *atom, TagLib::File *file) } void -MP4::Tag::parseCovr(MP4::Atom *atom, TagLib::File *file) +MP4::Tag::parseCovr(const MP4::Atom *atom) { MP4::CoverArtList value; - ByteVector data = file->readBlock(atom->length - 8); - size_t pos = 0; + ByteVector data = d->file->readBlock(static_cast(atom->length - 8)); + unsigned int pos = 0; while(pos < data.size()) { - const int length = data.toUInt32BE(pos); + const int length = static_cast(data.toUInt32BE(pos)); + if(length < 12) { + debug("MP4: Too short atom"); + break;; + } + const ByteVector name = data.mid(pos + 4, 4); - const int flags = data.toUInt32BE(pos + 8); + const int flags = static_cast(data.toUInt32BE(pos + 8)); if(name != "data") { debug("MP4: Unexpected atom \"" + name + "\", expecting \"data\""); break; } - if(flags == TypeJPEG || flags == TypePNG || flags == TypeBMP || flags == TypeGIF || flags == TypeImplicit) { + if(flags == TypeJPEG || flags == TypePNG || flags == TypeBMP || + flags == TypeGIF || flags == TypeImplicit) { value.append(MP4::CoverArt(MP4::CoverArt::Format(flags), data.mid(pos + 16, length - 16))); } @@ -294,22 +305,22 @@ MP4::Tag::parseCovr(MP4::Atom *atom, TagLib::File *file) } ByteVector -MP4::Tag::padIlst(const ByteVector &data, int length) +MP4::Tag::padIlst(const ByteVector &data, int length) const { - if (length == -1) { + if(length == -1) { length = static_cast(((data.size() + 1023) & ~1023) - data.size()); } return renderAtom("free", ByteVector(length, '\1')); } ByteVector -MP4::Tag::renderAtom(const ByteVector &name, const ByteVector &data) +MP4::Tag::renderAtom(const ByteVector &name, const ByteVector &data) const { return ByteVector::fromUInt32BE(data.size() + 8) + name + data; } ByteVector -MP4::Tag::renderData(const ByteVector &name, int flags, const ByteVectorList &data) +MP4::Tag::renderData(const ByteVector &name, int flags, const ByteVectorList &data) const { ByteVector result; for(unsigned int i = 0; i < data.size(); i++) { @@ -319,7 +330,7 @@ MP4::Tag::renderData(const ByteVector &name, int flags, const ByteVectorList &da } ByteVector -MP4::Tag::renderBool(const ByteVector &name, MP4::Item &item) +MP4::Tag::renderBool(const ByteVector &name, const MP4::Item &item) const { ByteVectorList data; data.append(ByteVector(1, item.toBool() ? '\1' : '\0')); @@ -327,7 +338,7 @@ MP4::Tag::renderBool(const ByteVector &name, MP4::Item &item) } ByteVector -MP4::Tag::renderInt(const ByteVector &name, MP4::Item &item) +MP4::Tag::renderInt(const ByteVector &name, const MP4::Item &item) const { ByteVectorList data; data.append(ByteVector::fromUInt16BE(item.toInt())); @@ -335,7 +346,7 @@ MP4::Tag::renderInt(const ByteVector &name, MP4::Item &item) } ByteVector -MP4::Tag::renderUInt(const ByteVector &name, MP4::Item &item) +MP4::Tag::renderUInt(const ByteVector &name, const MP4::Item &item) const { ByteVectorList data; data.append(ByteVector::fromUInt32BE(item.toUInt())); @@ -343,7 +354,7 @@ MP4::Tag::renderUInt(const ByteVector &name, MP4::Item &item) } ByteVector -MP4::Tag::renderLongLong(const ByteVector &name, MP4::Item &item) +MP4::Tag::renderLongLong(const ByteVector &name, const MP4::Item &item) const { ByteVectorList data; data.append(ByteVector::fromUInt64BE(item.toLongLong())); @@ -351,7 +362,7 @@ MP4::Tag::renderLongLong(const ByteVector &name, MP4::Item &item) } ByteVector -MP4::Tag::renderByte(const ByteVector &name, MP4::Item &item) +MP4::Tag::renderByte(const ByteVector &name, const MP4::Item &item) const { ByteVectorList data; data.append(ByteVector(1, item.toByte())); @@ -359,7 +370,7 @@ MP4::Tag::renderByte(const ByteVector &name, MP4::Item &item) } ByteVector -MP4::Tag::renderIntPair(const ByteVector &name, MP4::Item &item) +MP4::Tag::renderIntPair(const ByteVector &name, const MP4::Item &item) const { ByteVectorList data; data.append(ByteVector(2, '\0') + @@ -370,7 +381,7 @@ MP4::Tag::renderIntPair(const ByteVector &name, MP4::Item &item) } ByteVector -MP4::Tag::renderIntPairNoTrailing(const ByteVector &name, MP4::Item &item) +MP4::Tag::renderIntPairNoTrailing(const ByteVector &name, const MP4::Item &item) const { ByteVectorList data; data.append(ByteVector(2, '\0') + @@ -380,7 +391,7 @@ MP4::Tag::renderIntPairNoTrailing(const ByteVector &name, MP4::Item &item) } ByteVector -MP4::Tag::renderText(const ByteVector &name, MP4::Item &item, int flags) +MP4::Tag::renderText(const ByteVector &name, const MP4::Item &item, int flags) const { ByteVectorList data; StringList value = item.toStringList(); @@ -391,7 +402,7 @@ MP4::Tag::renderText(const ByteVector &name, MP4::Item &item, int flags) } ByteVector -MP4::Tag::renderCovr(const ByteVector &name, MP4::Item &item) +MP4::Tag::renderCovr(const ByteVector &name, const MP4::Item &item) const { ByteVector data; MP4::CoverArtList value = item.toCoverArtList(); @@ -403,7 +414,7 @@ MP4::Tag::renderCovr(const ByteVector &name, MP4::Item &item) } ByteVector -MP4::Tag::renderFreeForm(const String &name, MP4::Item &item) +MP4::Tag::renderFreeForm(const String &name, const MP4::Item &item) const { StringList header = StringList::split(name, ":"); if (header.size() != 3) { @@ -441,38 +452,38 @@ bool MP4::Tag::save() { ByteVector data; - for(MP4::ItemListMap::Iterator i = d->items.begin(); i != d->items.end(); i++) { - const String name = i->first; + for(MP4::ItemMap::ConstIterator it = d->items.begin(); it != d->items.end(); ++it) { + const String name = it->first; if(name.startsWith("----")) { - data.append(renderFreeForm(name, i->second)); + data.append(renderFreeForm(name, it->second)); } else if(name == "trkn") { - data.append(renderIntPair(name.data(String::Latin1), i->second)); + data.append(renderIntPair(name.data(String::Latin1), it->second)); } else if(name == "disk") { - data.append(renderIntPairNoTrailing(name.data(String::Latin1), i->second)); + data.append(renderIntPairNoTrailing(name.data(String::Latin1), it->second)); } else if(name == "cpil" || name == "pgap" || name == "pcst" || name == "hdvd") { - data.append(renderBool(name.data(String::Latin1), i->second)); + data.append(renderBool(name.data(String::Latin1), it->second)); } else if(name == "tmpo") { - data.append(renderInt(name.data(String::Latin1), i->second)); + data.append(renderInt(name.data(String::Latin1), it->second)); } else if(name == "tvsn" || name == "tves" || name == "cnID" || name == "sfID" || name == "atID" || name == "geID") { - data.append(renderUInt(name.data(String::Latin1), i->second)); + data.append(renderUInt(name.data(String::Latin1), it->second)); } else if(name == "plID") { - data.append(renderLongLong(name.data(String::Latin1), i->second)); + data.append(renderLongLong(name.data(String::Latin1), it->second)); } else if(name == "stik" || name == "rtng" || name == "akID") { - data.append(renderByte(name.data(String::Latin1), i->second)); + data.append(renderByte(name.data(String::Latin1), it->second)); } else if(name == "covr") { - data.append(renderCovr(name.data(String::Latin1), i->second)); + data.append(renderCovr(name.data(String::Latin1), it->second)); } else if(name.size() == 4){ - data.append(renderText(name.data(String::Latin1), i->second)); + data.append(renderText(name.data(String::Latin1), it->second)); } else { debug("MP4: Unknown item name \"" + name + "\""); @@ -492,7 +503,7 @@ MP4::Tag::save() } void -MP4::Tag::updateParents(AtomList &path, long delta, int ignore) +MP4::Tag::updateParents(const AtomList &path, long delta, int ignore) { for(size_t i = 0; i < path.size() - ignore; i++) { d->file->seek(path[i]->offset); @@ -525,7 +536,7 @@ MP4::Tag::updateOffsets(long delta, offset_t offset) atom->offset += delta; } d->file->seek(atom->offset + 12); - ByteVector data = d->file->readBlock(atom->length - 12); + ByteVector data = d->file->readBlock(static_cast(atom->length - 12)); unsigned int count = data.toUInt32BE(0); d->file->seek(atom->offset + 16); size_t pos = 4; @@ -546,7 +557,7 @@ MP4::Tag::updateOffsets(long delta, offset_t offset) atom->offset += delta; } d->file->seek(atom->offset + 12); - ByteVector data = d->file->readBlock(atom->length - 12); + ByteVector data = d->file->readBlock(static_cast(atom->length - 12)); unsigned int count = data.toUInt32BE(0); d->file->seek(atom->offset + 16); size_t pos = 4; @@ -570,7 +581,7 @@ MP4::Tag::updateOffsets(long delta, offset_t offset) atom->offset += delta; } d->file->seek(atom->offset + 9); - ByteVector data = d->file->readBlock(atom->length - 9); + ByteVector data = d->file->readBlock(static_cast(atom->length - 9)); const uint flags = data.toUInt24BE(0); if(flags & 1) { long long o = data.toInt64BE(7); @@ -585,10 +596,11 @@ MP4::Tag::updateOffsets(long delta, offset_t offset) } void -MP4::Tag::saveNew(ByteVector &data) +MP4::Tag::saveNew(ByteVector data) { - data = renderAtom("meta", TagLib::ByteVector(4, '\0') + - renderAtom("hdlr", TagLib::ByteVector(8, '\0') + TagLib::ByteVector("mdirappl") + TagLib::ByteVector(9, '\0')) + + data = renderAtom("meta", ByteVector(4, '\0') + + renderAtom("hdlr", ByteVector(8, '\0') + ByteVector("mdirappl") + + ByteVector(9, '\0')) + data + padIlst(data)); AtomList path = d->atoms->path("moov", "udta"); @@ -605,11 +617,11 @@ MP4::Tag::saveNew(ByteVector &data) } void -MP4::Tag::saveExisting(ByteVector &data, AtomList &path) +MP4::Tag::saveExisting(ByteVector data, const AtomList &path) { MP4::Atom *ilst = path[path.size() - 1]; offset_t offset = ilst->offset; - long length = ilst->length; + offset_t length = ilst->length; MP4::Atom *meta = path[path.size() - 2]; AtomList::ConstIterator index = meta->children.find(ilst); @@ -634,17 +646,17 @@ MP4::Tag::saveExisting(ByteVector &data, AtomList &path) } } - long delta = static_cast(data.size()) - length; + long delta = static_cast(data.size() - length); if(delta > 0 || (delta < 0 && delta > -8)) { data.append(padIlst(data)); - delta = static_cast(data.size()) - length; + delta = static_cast(data.size() - length); } else if(delta < 0) { - data.append(padIlst(data, -delta - 8)); + data.append(padIlst(data, 0 - delta - 8)); delta = 0; } - d->file->insert(data, offset, length); + d->file->insert(data, offset, static_cast(length)); if(delta) { updateParents(path, delta, 1); @@ -750,12 +762,41 @@ MP4::Tag::setTrack(uint value) d->items["trkn"] = MP4::Item(value, 0); } -MP4::ItemListMap & -MP4::Tag::itemListMap() +bool MP4::Tag::isEmpty() const +{ + return d->items.isEmpty(); +} + +MP4::ItemMap &MP4::Tag::itemListMap() { return d->items; } +const MP4::ItemMap &MP4::Tag::itemMap() const +{ + return d->items; +} + +MP4::Item MP4::Tag::item(const String &key) const +{ + return d->items[key]; +} + +void MP4::Tag::setItem(const String &key, const Item &value) +{ + d->items[key] = value; +} + +void MP4::Tag::removeItem(const String &key) +{ + d->items.erase(key); +} + +bool MP4::Tag::contains(const String &key) const +{ + return d->items.contains(key); +} + String MP4::Tag::toString() const { @@ -766,58 +807,56 @@ MP4::Tag::toString() const return desc.toString("\n"); } -namespace -{ - const char *keyTranslation[][2] = { - { "\251nam", "TITLE" }, - { "\251ART", "ARTIST" }, - { "\251alb", "ALBUM" }, - { "\251cmt", "COMMENT" }, - { "\251gen", "GENRE" }, - { "\251day", "DATE" }, - { "\251wrt", "COMPOSER" }, - { "\251grp", "GROUPING" }, - { "aART", "ALBUMARTIST" }, - { "trkn", "TRACKNUMBER" }, - { "disk", "DISCNUMBER" }, - { "cpil", "COMPILATION" }, - { "tmpo", "BPM" }, - { "cprt", "COPYRIGHT" }, - { "\251lyr", "LYRICS" }, - { "\251too", "ENCODEDBY" }, - { "soal", "ALBUMSORT" }, - { "soaa", "ALBUMARTISTSORT" }, - { "soar", "ARTISTSORT" }, - { "sonm", "TITLESORT" }, - { "soco", "COMPOSERSORT" }, - { "sosn", "SHOWSORT" }, - { "----:com.apple.iTunes:MusicBrainz Track Id", "MUSICBRAINZ_TRACKID" }, - { "----:com.apple.iTunes:MusicBrainz Artist Id", "MUSICBRAINZ_ARTISTID" }, - { "----:com.apple.iTunes:MusicBrainz Album Id", "MUSICBRAINZ_ALBUMID" }, - { "----:com.apple.iTunes:MusicBrainz Album Artist Id", "MUSICBRAINZ_ALBUMARTISTID" }, - { "----:com.apple.iTunes:MusicBrainz Release Group Id", "MUSICBRAINZ_RELEASEGROUPID" }, - { "----:com.apple.iTunes:MusicBrainz Work Id", "MUSICBRAINZ_WORKID" }, - { "----:com.apple.iTunes:ASIN", "ASIN" }, - { "----:com.apple.iTunes:LABEL", "LABEL" }, - { "----:com.apple.iTunes:LYRICIST", "LYRICIST" }, - { "----:com.apple.iTunes:CONDUCTOR", "CONDUCTOR" }, - { "----:com.apple.iTunes:REMIXER", "REMIXER" }, - { "----:com.apple.iTunes:ENGINEER", "ENGINEER" }, - { "----:com.apple.iTunes:PRODUCER", "PRODUCER" }, - { "----:com.apple.iTunes:DJMIXER", "DJMIXER" }, - { "----:com.apple.iTunes:MIXER", "MIXER" }, - { "----:com.apple.iTunes:SUBTITLE", "SUBTITLE" }, - { "----:com.apple.iTunes:DISCSUBTITLE", "DISCSUBTITLE" }, - { "----:com.apple.iTunes:MOOD", "MOOD" }, - { "----:com.apple.iTunes:ISRC", "ISRC" }, - { "----:com.apple.iTunes:CATALOGNUMBER", "CATALOGNUMBER" }, - { "----:com.apple.iTunes:BARCODE", "BARCODE" }, - { "----:com.apple.iTunes:SCRIPT", "SCRIPT" }, - { "----:com.apple.iTunes:LANGUAGE", "LANGUAGE" }, - { "----:com.apple.iTunes:LICENSE", "LICENSE" }, - { "----:com.apple.iTunes:MEDIA", "MEDIA" }, - }; -} + +static const char *keyTranslation[][2] = { + { "\251nam", "TITLE" }, + { "\251ART", "ARTIST" }, + { "\251alb", "ALBUM" }, + { "\251cmt", "COMMENT" }, + { "\251gen", "GENRE" }, + { "\251day", "DATE" }, + { "\251wrt", "COMPOSER" }, + { "\251grp", "GROUPING" }, + { "aART", "ALBUMARTIST" }, + { "trkn", "TRACKNUMBER" }, + { "disk", "DISCNUMBER" }, + { "cpil", "COMPILATION" }, + { "tmpo", "BPM" }, + { "cprt", "COPYRIGHT" }, + { "\251lyr", "LYRICS" }, + { "\251too", "ENCODEDBY" }, + { "soal", "ALBUMSORT" }, + { "soaa", "ALBUMARTISTSORT" }, + { "soar", "ARTISTSORT" }, + { "sonm", "TITLESORT" }, + { "soco", "COMPOSERSORT" }, + { "sosn", "SHOWSORT" }, + { "----:com.apple.iTunes:MusicBrainz Track Id", "MUSICBRAINZ_TRACKID" }, + { "----:com.apple.iTunes:MusicBrainz Artist Id", "MUSICBRAINZ_ARTISTID" }, + { "----:com.apple.iTunes:MusicBrainz Album Id", "MUSICBRAINZ_ALBUMID" }, + { "----:com.apple.iTunes:MusicBrainz Album Artist Id", "MUSICBRAINZ_ALBUMARTISTID" }, + { "----:com.apple.iTunes:MusicBrainz Release Group Id", "MUSICBRAINZ_RELEASEGROUPID" }, + { "----:com.apple.iTunes:MusicBrainz Work Id", "MUSICBRAINZ_WORKID" }, + { "----:com.apple.iTunes:ASIN", "ASIN" }, + { "----:com.apple.iTunes:LABEL", "LABEL" }, + { "----:com.apple.iTunes:LYRICIST", "LYRICIST" }, + { "----:com.apple.iTunes:CONDUCTOR", "CONDUCTOR" }, + { "----:com.apple.iTunes:REMIXER", "REMIXER" }, + { "----:com.apple.iTunes:ENGINEER", "ENGINEER" }, + { "----:com.apple.iTunes:PRODUCER", "PRODUCER" }, + { "----:com.apple.iTunes:DJMIXER", "DJMIXER" }, + { "----:com.apple.iTunes:MIXER", "MIXER" }, + { "----:com.apple.iTunes:SUBTITLE", "SUBTITLE" }, + { "----:com.apple.iTunes:DISCSUBTITLE", "DISCSUBTITLE" }, + { "----:com.apple.iTunes:MOOD", "MOOD" }, + { "----:com.apple.iTunes:ISRC", "ISRC" }, + { "----:com.apple.iTunes:CATALOGNUMBER", "CATALOGNUMBER" }, + { "----:com.apple.iTunes:BARCODE", "BARCODE" }, + { "----:com.apple.iTunes:SCRIPT", "SCRIPT" }, + { "----:com.apple.iTunes:LANGUAGE", "LANGUAGE" }, + { "----:com.apple.iTunes:LICENSE", "LICENSE" }, + { "----:com.apple.iTunes:MEDIA", "MEDIA" }, +}; PropertyMap MP4::Tag::properties() const { @@ -830,7 +869,7 @@ PropertyMap MP4::Tag::properties() const } PropertyMap props; - MP4::ItemListMap::ConstIterator it = d->items.begin(); + MP4::ItemMap::ConstIterator it = d->items.begin(); for(; it != d->items.end(); ++it) { if(keyMap.contains(it->first)) { String key = keyMap[it->first]; diff --git a/taglib/mp4/mp4tag.h b/taglib/mp4/mp4tag.h index c5152a5f..596f785c 100644 --- a/taglib/mp4/mp4tag.h +++ b/taglib/mp4/mp4tag.h @@ -39,7 +39,11 @@ namespace TagLib { namespace MP4 { + /*! + * \deprecated + */ typedef TagLib::Map ItemListMap; + typedef TagLib::Map ItemMap; class TAGLIB_EXPORT Tag: public TagLib::Tag { @@ -65,7 +69,38 @@ namespace TagLib { void setYear(uint value); void setTrack(uint value); - ItemListMap &itemListMap(); + virtual bool isEmpty() const; + + /*! + * \deprecated Use the item() and setItem() API instead + */ + ItemMap &itemListMap(); + + /*! + * Returns a string-keyed map of the MP4::Items for this tag. + */ + const ItemMap &itemMap() const; + + /*! + * \return The item, if any, corresponding to \a key. + */ + Item item(const String &key) const; + + /*! + * Sets the value of \a key to \a value, overwriting any previous value. + */ + void setItem(const String &key, const Item &value); + + /*! + * Removes the entry with \a key from the tag, or does nothing if it does + * not exist. + */ + void removeItem(const String &key); + + /*! + * \return True if the tag contains an entry for \a key. + */ + bool contains(const String &key) const; String toString() const; @@ -74,38 +109,42 @@ namespace TagLib { PropertyMap setProperties(const PropertyMap &properties); private: - AtomDataList parseData2(Atom *atom, TagLib::File *file, int expectedFlags = -1, bool freeForm = false); - TagLib::ByteVectorList parseData(Atom *atom, TagLib::File *file, int expectedFlags = -1, bool freeForm = false); - void parseText(Atom *atom, TagLib::File *file, int expectedFlags = 1); - void parseFreeForm(Atom *atom, TagLib::File *file); - void parseInt(Atom *atom, TagLib::File *file); - void parseByte(Atom *atom, TagLib::File *file); - void parseUInt(Atom *atom, TagLib::File *file); - void parseLongLong(Atom *atom, TagLib::File *file); - void parseGnre(Atom *atom, TagLib::File *file); - void parseIntPair(Atom *atom, TagLib::File *file); - void parseBool(Atom *atom, TagLib::File *file); - void parseCovr(Atom *atom, TagLib::File *file); + AtomDataList parseData2(const Atom *atom, int expectedFlags = -1, + bool freeForm = false); + ByteVectorList parseData(const Atom *atom, int expectedFlags = -1, + bool freeForm = false); + void parseText(const Atom *atom, int expectedFlags = 1); + void parseFreeForm(const Atom *atom); + void parseInt(const Atom *atom); + void parseByte(const Atom *atom); + void parseUInt(const Atom *atom); + void parseLongLong(const Atom *atom); + void parseGnre(const Atom *atom); + void parseIntPair(const Atom *atom); + void parseBool(const Atom *atom); + void parseCovr(const Atom *atom); - TagLib::ByteVector padIlst(const ByteVector &data, int length = -1); - TagLib::ByteVector renderAtom(const ByteVector &name, const TagLib::ByteVector &data); - TagLib::ByteVector renderData(const ByteVector &name, int flags, const TagLib::ByteVectorList &data); - TagLib::ByteVector renderText(const ByteVector &name, Item &item, int flags = TypeUTF8); - TagLib::ByteVector renderFreeForm(const String &name, Item &item); - TagLib::ByteVector renderBool(const ByteVector &name, Item &item); - TagLib::ByteVector renderInt(const ByteVector &name, Item &item); - TagLib::ByteVector renderByte(const ByteVector &name, Item &item); - TagLib::ByteVector renderUInt(const ByteVector &name, Item &item); - TagLib::ByteVector renderLongLong(const ByteVector &name, Item &item); - TagLib::ByteVector renderIntPair(const ByteVector &name, Item &item); - TagLib::ByteVector renderIntPairNoTrailing(const ByteVector &name, Item &item); - TagLib::ByteVector renderCovr(const ByteVector &name, Item &item); + ByteVector padIlst(const ByteVector &data, int length = -1) const; + ByteVector renderAtom(const ByteVector &name, const ByteVector &data) const; + ByteVector renderData(const ByteVector &name, int flags, + const ByteVectorList &data) const; + ByteVector renderText(const ByteVector &name, const Item &item, + int flags = TypeUTF8) const; + ByteVector renderFreeForm(const String &name, const Item &item) const; + ByteVector renderBool(const ByteVector &name, const Item &item) const; + ByteVector renderInt(const ByteVector &name, const Item &item) const; + ByteVector renderByte(const ByteVector &name, const Item &item) const; + ByteVector renderUInt(const ByteVector &name, const Item &item) const; + ByteVector renderLongLong(const ByteVector &name, const Item &item) const; + ByteVector renderIntPair(const ByteVector &name, const Item &item) const; + ByteVector renderIntPairNoTrailing(const ByteVector &name, const Item &item) const; + ByteVector renderCovr(const ByteVector &name, const Item &item) const; - void updateParents(AtomList &path, long delta, int ignore = 0); + void updateParents(const AtomList &path, long delta, int ignore = 0); void updateOffsets(long delta, offset_t offset); - void saveNew(TagLib::ByteVector &data); - void saveExisting(TagLib::ByteVector &data, AtomList &path); + void saveNew(ByteVector data); + void saveExisting(ByteVector data, const AtomList &path); void addItem(const String &name, const Item &value); diff --git a/taglib/mpc/mpcfile.cpp b/taglib/mpc/mpcfile.cpp index e496840a..dab10d69 100644 --- a/taglib/mpc/mpcfile.cpp +++ b/taglib/mpc/mpcfile.cpp @@ -53,7 +53,6 @@ public: ID3v2Location(-1), ID3v2Size(0), properties(0), - scanned(false), hasAPE(false), hasID3v1(false), hasID3v2(false) {} @@ -76,7 +75,6 @@ public: DoubleTagUnion tag; AudioProperties *properties; - bool scanned; // These indicate whether the file *on disk* has these tags, not if // this data structure does. This is used in computing offsets. @@ -90,20 +88,20 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -MPC::File::File(FileName file, bool readProperties, - AudioProperties::ReadStyle propertiesStyle) : TagLib::File(file) +MPC::File::File(FileName file, bool readProperties, AudioProperties::ReadStyle) : + TagLib::File(file), + d(new FilePrivate()) { - d = new FilePrivate; if(isOpen()) - read(readProperties, propertiesStyle); + read(readProperties); } -MPC::File::File(IOStream *stream, bool readProperties, - AudioProperties::ReadStyle propertiesStyle) : TagLib::File(stream) +MPC::File::File(IOStream *stream, bool readProperties, AudioProperties::ReadStyle) : + TagLib::File(stream), + d(new FilePrivate()) { - d = new FilePrivate; if(isOpen()) - read(readProperties, propertiesStyle); + read(readProperties); } MPC::File::~File() @@ -253,7 +251,7 @@ bool MPC::File::hasAPETag() const // private members //////////////////////////////////////////////////////////////////////////////// -void MPC::File::read(bool readProperties, AudioProperties::ReadStyle /* propertiesStyle */) +void MPC::File::read(bool readProperties) { // Look for an ID3v1 tag @@ -266,8 +264,6 @@ void MPC::File::read(bool readProperties, AudioProperties::ReadStyle /* properti // Look for an APE tag - findAPE(); - d->APELocation = findAPE(); if(d->APELocation >= 0) { @@ -281,7 +277,7 @@ void MPC::File::read(bool readProperties, AudioProperties::ReadStyle /* properti if(!d->hasID3v1) APETag(true); - // Look for and skip an ID3v2 tag + // Look for an ID3v2 tag d->ID3v2Location = findID3v2(); @@ -292,15 +288,28 @@ void MPC::File::read(bool readProperties, AudioProperties::ReadStyle /* properti d->hasID3v2 = true; } - if(d->hasID3v2) - seek(d->ID3v2Location + d->ID3v2Size); - else - seek(0); - // Look for MPC metadata if(readProperties) { - d->properties = new AudioProperties(this, length() - d->ID3v2Size - d->APESize); + + offset_t streamLength; + + if(d->hasAPE) + streamLength = d->APELocation; + else if(d->hasID3v1) + streamLength = d->ID3v1Location; + else + streamLength = length(); + + if(d->hasID3v2) { + seek(d->ID3v2Location + d->ID3v2Size); + streamLength -= (d->ID3v2Location + d->ID3v2Size); + } + else { + seek(0); + } + + d->properties = new AudioProperties(this, streamLength); } } diff --git a/taglib/mpc/mpcfile.h b/taglib/mpc/mpcfile.h index f01ca74c..d80452b1 100644 --- a/taglib/mpc/mpcfile.h +++ b/taglib/mpc/mpcfile.h @@ -130,6 +130,11 @@ namespace TagLib { /*! * Saves the file. + * + * This returns true if the save was successful. + * + * \warning In the current implementation, it's dangerous to call save() + * repeatedly. At worst it will corrupt the file. */ virtual bool save(); @@ -140,8 +145,8 @@ namespace TagLib { * if there is no valid APE tag. If \a create is true it will create * an APE tag if one does not exist and returns a valid pointer. * - * \note This may return a valid pointer regardless of whether or not the - * file on disk has an ID3v1 tag. Use hasID3v1Tag() to check if the file + * \note This may return a valid pointer regardless of whether or not the + * file on disk has an ID3v1 tag. Use hasID3v1Tag() to check if the file * on disk actually has an ID3v1 tag. * * \note The Tag is still owned by the MPEG::File and should not be @@ -157,11 +162,11 @@ namespace TagLib { * * If \a create is false (the default) this may return a null pointer * if there is no valid APE tag. If \a create is true it will create - * an APE tag if one does not exist and returns a valid pointer. If + * an APE tag if one does not exist and returns a valid pointer. If * there already be an ID3v1 tag, the new APE tag will be placed before it. * - * \note This may return a valid pointer regardless of whether or not the - * file on disk has an APE tag. Use hasAPETag() to check if the file + * \note This may return a valid pointer regardless of whether or not the + * file on disk has an APE tag. Use hasAPETag() to check if the file * on disk actually has an APE tag. * * \note The Tag is still owned by the MPEG::File and should not be @@ -207,8 +212,7 @@ namespace TagLib { File(const File &); File &operator=(const File &); - void read(bool readProperties, AudioProperties::ReadStyle propertiesStyle); - void scan(); + void read(bool readProperties); offset_t findAPE(); offset_t findID3v1(); offset_t findID3v2(); diff --git a/taglib/mpc/mpcproperties.cpp b/taglib/mpc/mpcproperties.cpp index 2d18b3ca..74c08cb5 100644 --- a/taglib/mpc/mpcproperties.cpp +++ b/taglib/mpc/mpcproperties.cpp @@ -33,6 +33,11 @@ using namespace TagLib; +namespace +{ + const TagLib::uint HeaderSize = 56; +} + class MPC::AudioProperties::PropertiesPrivate { public: @@ -67,17 +72,18 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -MPC::AudioProperties::AudioProperties(File *file, offset_t streamLength, ReadStyle style) : +MPC::AudioProperties::AudioProperties(File *file, offset_t streamLength, ReadStyle) : + TagLib::AudioProperties(), d(new PropertiesPrivate()) { - const ByteVector magic = file->readBlock(4); + ByteVector magic = file->readBlock(4); if(magic == "MPCK") { // Musepack version 8 readSV8(file, streamLength); } else { // Musepack version 7 or older, fixed size header - readSV7(magic + file->readBlock(52), streamLength); + readSV7(magic + file->readBlock(HeaderSize - 4), streamLength); } } @@ -87,6 +93,16 @@ MPC::AudioProperties::~AudioProperties() } int MPC::AudioProperties::length() const +{ + return lengthInSeconds(); +} + +int MPC::AudioProperties::lengthInSeconds() const +{ + return d->length / 1000; +} + +int MPC::AudioProperties::lengthInMilliseconds() const { return d->length; } @@ -183,7 +199,7 @@ namespace // This array looks weird, but the same as original MusePack code found at: // https://www.musepack.net/index.php?pg=src - static const unsigned short sftable [8] = { 44100, 48000, 37800, 32000, 0, 0, 0, 0 }; + const unsigned short sftable [8] = { 44100, 48000, 37800, 32000, 0, 0, 0, 0 }; } void MPC::AudioProperties::readSV8(File *file, offset_t streamLength) @@ -230,7 +246,7 @@ void MPC::AudioProperties::readSV8(File *file, offset_t streamLength) break; } - ulong begSilence = readSize(data, pos); + const ulong begSilence = readSize(data, pos); if(pos > dataSize - 2) { debug("MPC::Properties::readSV8() - \"SH\" packet is corrupt."); break; @@ -243,12 +259,12 @@ void MPC::AudioProperties::readSV8(File *file, offset_t streamLength) d->channels = ((flags >> 4) & 0x0F) + 1; const uint frameCount = d->sampleFrames - begSilence; - if(frameCount != 0 && d->sampleRate != 0) { - d->bitrate = (int)(streamLength * 8.0 * d->sampleRate / frameCount / 1000); - d->length = frameCount / d->sampleRate; + if(frameCount > 0 && d->sampleRate > 0) { + const double length = frameCount * 1000.0 / d->sampleRate; + d->length = static_cast(length + 0.5); + d->bitrate = static_cast(streamLength * 8.0 / length + 0.5); } } - else if (packetType == "RG") { // Replay Gain // http://trac.musepack.net/wiki/SV8Specification#ReplaygainPacket @@ -260,7 +276,7 @@ void MPC::AudioProperties::readSV8(File *file, offset_t streamLength) readRG = true; - int replayGainVersion = data[0]; + const int replayGainVersion = data[0]; if(replayGainVersion == 1) { d->trackGain = data.toUInt16BE(1); d->trackPeak = data.toUInt16BE(3); @@ -288,9 +304,9 @@ void MPC::AudioProperties::readSV7(const ByteVector &data, offset_t streamLength d->totalFrames = data.toUInt32LE(4); - std::bitset<32> flags(TAGLIB_CONSTRUCT_BITSET(data.toUInt32LE(8))); - d->sampleRate = sftable[flags[17] * 2 + flags[16]]; - d->channels = 2; + const uint flags = data.toUInt32LE(8); + d->sampleRate = sftable[(flags >> 16) & 0x03]; + d->channels = 2; const uint gapless = data.toUInt32LE(5); @@ -329,10 +345,10 @@ void MPC::AudioProperties::readSV7(const ByteVector &data, offset_t streamLength else { const uint headerData = data.toUInt32LE(0); - d->bitrate = (headerData >> 23) & 0x01ff; - d->version = (headerData >> 11) & 0x03ff; + d->bitrate = (headerData >> 23) & 0x01ff; + d->version = (headerData >> 11) & 0x03ff; d->sampleRate = 44100; - d->channels = 2; + d->channels = 2; if(d->version >= 5) d->totalFrames = data.toUInt32LE(4); @@ -342,8 +358,11 @@ void MPC::AudioProperties::readSV7(const ByteVector &data, offset_t streamLength d->sampleFrames = d->totalFrames * 1152 - 576; } - d->length = d->sampleRate > 0 ? (d->sampleFrames + (d->sampleRate / 2)) / d->sampleRate : 0; + if(d->sampleFrames > 0 && d->sampleRate > 0) { + const double length = d->sampleFrames * 1000.0 / d->sampleRate; + d->length = static_cast(length + 0.5); - if(!d->bitrate) - d->bitrate = d->length > 0 ? static_cast(streamLength * 8L / d->length / 1000) : 0; + if(d->bitrate == 0) + d->bitrate = static_cast(streamLength * 8.0 / length + 0.5); + } } diff --git a/taglib/mpc/mpcproperties.h b/taglib/mpc/mpcproperties.h index 53d82fbf..ccce40e6 100644 --- a/taglib/mpc/mpcproperties.h +++ b/taglib/mpc/mpcproperties.h @@ -56,17 +56,53 @@ namespace TagLib { */ virtual ~AudioProperties(); - // Reimplementations. - + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \note This method is just an alias of lengthInSeconds(). + * + * \deprecated + */ virtual int length() const; + + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \see lengthInMilliseconds() + */ + // BIC: make virtual + int lengthInSeconds() const; + + /*! + * Returns the length of the file in milliseconds. + * + * \see lengthInSeconds() + */ + // BIC: make virtual + int lengthInMilliseconds() const; + + /*! + * Returns the average bit rate of the file in kb/s. + */ virtual int bitrate() const; + + /*! + * Returns the sample rate in Hz. + */ virtual int sampleRate() const; + + /*! + * Returns the number of audio channels. + */ virtual int channels() const; /*! * Returns the version of the bitstream (SV4-SV8) */ int mpcVersion() const; + uint totalFrames() const; uint sampleFrames() const; diff --git a/taglib/mpeg/id3v1/id3v1tag.h b/taglib/mpeg/id3v1/id3v1tag.h index 05e00521..01dda34b 100644 --- a/taglib/mpeg/id3v1/id3v1tag.h +++ b/taglib/mpeg/id3v1/id3v1tag.h @@ -84,7 +84,7 @@ namespace TagLib { //! The main class in the ID3v1 implementation /*! - * This is an implementation of the ID3v1 format. ID3v1 is both the simplist + * This is an implementation of the ID3v1 format. ID3v1 is both the simplest * and most common of tag formats but is rather limited. Because of its * pervasiveness and the way that applications have been written around the * fields that it provides, the generic TagLib::Tag API is a mirror of what is diff --git a/taglib/mpeg/id3v2/frames/chapterframe.cpp b/taglib/mpeg/id3v2/frames/chapterframe.cpp index 8c754c17..7f71b122 100644 --- a/taglib/mpeg/id3v2/frames/chapterframe.cpp +++ b/taglib/mpeg/id3v2/frames/chapterframe.cpp @@ -64,18 +64,26 @@ ChapterFrame::ChapterFrame(const ID3v2::Header *tagHeader, const ByteVector &dat setData(data); } -ChapterFrame::ChapterFrame(const ByteVector &eID, const TagLib::uint &sT, const TagLib::uint &eT, - const TagLib::uint &sO, const TagLib::uint &eO, const FrameList &eF) : +ChapterFrame::ChapterFrame(const ByteVector &elementID, + const TagLib::uint &startTime, const TagLib::uint &endTime, + const TagLib::uint &startOffset, const TagLib::uint &endOffset, + const FrameList &embeddedFrames) : ID3v2::Frame("CHAP") { d = new ChapterFramePrivate; - d->elementID = eID; - d->startTime = sT; - d->endTime = eT; - d->startOffset = sO; - d->endOffset = eO; - FrameList l = eF; - for(FrameList::ConstIterator it = l.begin(); it != l.end(); ++it) + + // setElementID has a workaround for a previously silly API where you had to + // specifically include the null byte. + + setElementID(elementID); + + d->startTime = startTime; + d->endTime = endTime; + d->startOffset = startOffset; + d->endOffset = endOffset; + + for(FrameList::ConstIterator it = embeddedFrames.begin(); + it != embeddedFrames.end(); ++it) addEmbeddedFrame(*it); } @@ -112,8 +120,9 @@ TagLib::uint ChapterFrame::endOffset() const void ChapterFrame::setElementID(const ByteVector &eID) { d->elementID = eID; - if(eID.at(eID.size() - 1) != char(0)) - d->elementID.append(char(0)); + + if(d->elementID.endsWith(char(0))) + d->elementID = d->elementID.mid(0, d->elementID.size() - 1); } void ChapterFrame::setStartTime(const TagLib::uint &sT) @@ -181,7 +190,25 @@ void ChapterFrame::removeEmbeddedFrames(const ByteVector &id) String ChapterFrame::toString() const { - return String::null; + String s = String(d->elementID) + + ": start time: " + String::number(d->startTime) + + ", end time: " + String::number(d->endTime); + + if(d->startOffset != 0xFFFFFFFF) + s += ", start offset: " + String::number(d->startOffset); + + if(d->endOffset != 0xFFFFFFFF) + s += ", start offset: " + String::number(d->endOffset); + + 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 ChapterFrame::asProperties() const @@ -221,7 +248,6 @@ void ChapterFrame::parseFields(const ByteVector &data) size_t pos = 0; size_t embPos = 0; d->elementID = readStringField(data, String::Latin1, pos).data(String::Latin1); - d->elementID.append(char(0)); d->startTime = data.toUInt32BE(pos); pos += 4; d->endTime = data.toUInt32BE(pos); @@ -230,9 +256,15 @@ void ChapterFrame::parseFields(const ByteVector &data) pos += 4; d->endOffset = data.toUInt32BE(pos); pos += 4; - size -= pos; - while(embPos < size - header()->size()) { + size -= pos; + + // Embedded frames are optional + + if(size < header()->size()) + return; + + while(embPos < size - header()->size()) { Frame *frame = FrameFactory::instance()->createFrame(data.mid(pos + embPos), d->tagHeader); if(!frame) @@ -254,6 +286,7 @@ ByteVector ChapterFrame::renderFields() const ByteVector data; data.append(d->elementID); + data.append('\0'); data.append(ByteVector::fromUInt32BE(d->startTime)); data.append(ByteVector::fromUInt32BE(d->endTime)); data.append(ByteVector::fromUInt32BE(d->startOffset)); diff --git a/taglib/mpeg/id3v2/frames/chapterframe.h b/taglib/mpeg/id3v2/frames/chapterframe.h index 05bdd980..692895ee 100644 --- a/taglib/mpeg/id3v2/frames/chapterframe.h +++ b/taglib/mpeg/id3v2/frames/chapterframe.h @@ -53,17 +53,24 @@ namespace TagLib { ChapterFrame(const ID3v2::Header *tagHeader, const ByteVector &data); /*! - * Creates a chapter frame with the element ID \a eID, - * start time \a sT, end time \a eT, start offset \a sO, - * end offset \a eO and embedded frames, that are in \a eF. + * Creates a chapter frame with the element ID \a elementID, start time + * \a startTime, end time \a endTime, start offset \a startOffset, + * end offset \a endOffset and optionally a list of embedded frames, + * whose ownership will then be taken over by this Frame, in + * \a embeededFrames; + * + * All times are in milliseconds. */ - ChapterFrame(const ByteVector &eID, const uint &sT, const uint &eT, const uint &sO, - const uint &eO, const FrameList &eF); + // BIC: There's no reason to use const-references with uints + ChapterFrame(const ByteVector &elementID, + const uint &startTime, const uint &endTime, + const uint &startOffset, const uint &endOffset, + const FrameList &embeddedFrames = FrameList()); /*! * Destroys the frame. */ - ~ChapterFrame(); + virtual ~ChapterFrame(); /*! * Returns the element ID of the frame. Element ID @@ -74,14 +81,14 @@ namespace TagLib { ByteVector elementID() const; /*! - * Returns time of chapter's start (in miliseconds). + * Returns time of chapter's start (in milliseconds). * * \see setStartTime() */ uint startTime() const; /*! - * Returns time of chapter's end (in miliseconds). + * Returns time of chapter's end (in milliseconds). * * \see setEndTime() */ @@ -114,14 +121,14 @@ namespace TagLib { void setElementID(const ByteVector &eID); /*! - * Sets time of chapter's start (in miliseconds) to \a sT. + * Sets time of chapter's start (in milliseconds) to \a sT. * * \see startTime() */ void setStartTime(const uint &sT); /*! - * Sets time of chapter's end (in miliseconds) to \a eT. + * Sets time of chapter's end (in milliseconds) to \a eT. * * \see endTime() */ diff --git a/taglib/mpeg/id3v2/frames/commentsframe.h b/taglib/mpeg/id3v2/frames/commentsframe.h index f65f6f01..4da9d535 100644 --- a/taglib/mpeg/id3v2/frames/commentsframe.h +++ b/taglib/mpeg/id3v2/frames/commentsframe.h @@ -36,7 +36,7 @@ namespace TagLib { //! An implementation of ID3v2 comments /*! - * This implements the ID3v2 comment format. An ID3v2 comment concists of + * This implements the ID3v2 comment format. An ID3v2 comment consists of * a language encoding, a description and a single text field. */ @@ -106,7 +106,7 @@ namespace TagLib { /*! * Sets the description of the comment to \a s. * - * \see decription() + * \see description() */ void setDescription(const String &s); @@ -149,7 +149,7 @@ namespace TagLib { /*! * Comments each have a unique description. This searches for a comment - * frame with the decription \a d and returns a pointer to it. If no + * frame with the description \a d and returns a pointer to it. If no * frame is found that matches the given description null is returned. * * \see description() diff --git a/taglib/mpeg/id3v2/frames/ownershipframe.h b/taglib/mpeg/id3v2/frames/ownershipframe.h index 34fc9129..06a1e233 100644 --- a/taglib/mpeg/id3v2/frames/ownershipframe.h +++ b/taglib/mpeg/id3v2/frames/ownershipframe.h @@ -94,21 +94,21 @@ namespace TagLib { * \see pricePaid() */ void setPricePaid(const String &pricePaid); - + /*! * Returns the seller. * * \see setSeller() */ String seller() const; - + /*! * Set the seller. * * \see seller() */ void setSeller(const String &seller); - + /*! * Returns the text encoding that will be used in rendering this frame. * This defaults to the type that was either specified in the constructor @@ -118,7 +118,7 @@ namespace TagLib { * \see render() */ String::Type textEncoding() const; - + /*! * Sets the text encoding to be used when rendering this frame to * \a encoding. diff --git a/taglib/mpeg/id3v2/frames/popularimeterframe.h b/taglib/mpeg/id3v2/frames/popularimeterframe.h index d39f1aa8..79b88cbf 100644 --- a/taglib/mpeg/id3v2/frames/popularimeterframe.h +++ b/taglib/mpeg/id3v2/frames/popularimeterframe.h @@ -36,7 +36,7 @@ namespace TagLib { //! An implementation of ID3v2 "popularimeter" /*! - * This implements the ID3v2 popularimeter (POPM frame). It concists of + * This implements the ID3v2 popularimeter (POPM frame). It consists of * an email, a rating and an optional counter. */ diff --git a/taglib/mpeg/id3v2/frames/relativevolumeframe.h b/taglib/mpeg/id3v2/frames/relativevolumeframe.h index 9efa362e..db00c99d 100644 --- a/taglib/mpeg/id3v2/frames/relativevolumeframe.h +++ b/taglib/mpeg/id3v2/frames/relativevolumeframe.h @@ -140,14 +140,14 @@ namespace TagLib { /* * There was a terrible API goof here, and while this can't be changed to - * the way it appears below for binary compaibility reasons, let's at + * the way it appears below for binary compatibility reasons, let's at * least pretend that it looks clean. */ /*! * Returns the relative volume adjustment "index". As indicated by the * ID3v2 standard this is a 16-bit signed integer that reflects the - * decibils of adjustment when divided by 512. + * decibels of adjustment when divided by 512. * * This defaults to returning the value for the master volume channel if * available and returns 0 if the specified channel does not exist. @@ -159,7 +159,7 @@ namespace TagLib { /*! * Set the volume adjustment to \a index. As indicated by the ID3v2 - * standard this is a 16-bit signed integer that reflects the decibils of + * standard this is a 16-bit signed integer that reflects the decibels of * adjustment when divided by 512. * * By default this sets the value for the master volume. diff --git a/taglib/mpeg/id3v2/frames/synchronizedlyricsframe.h b/taglib/mpeg/id3v2/frames/synchronizedlyricsframe.h index 6e81b51b..704ac4b5 100644 --- a/taglib/mpeg/id3v2/frames/synchronizedlyricsframe.h +++ b/taglib/mpeg/id3v2/frames/synchronizedlyricsframe.h @@ -197,7 +197,7 @@ namespace TagLib { /*! * Sets the description of the synchronized lyrics frame to \a s. * - * \see decription() + * \see description() */ void setDescription(const String &s); diff --git a/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp b/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp index dd9a220d..a3598398 100644 --- a/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp +++ b/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp @@ -50,6 +50,31 @@ public: FrameList embeddedFrameList; }; +namespace { + + // These functions are needed to try to aim for backward compatibility with + // an API that previously (unreasonably) required null bytes to be appeneded + // at the end of identifiers explicitly by the API user. + + // BIC: remove these + + ByteVector &strip(ByteVector &b) + { + if(b.endsWith('\0')) + b.resize(b.size() - 1); + return b; + } + + ByteVectorList &strip(ByteVectorList &l) + { + for(ByteVectorList::Iterator it = l.begin(); it != l.end(); ++it) + { + strip(*it); + } + return l; + } +} + //////////////////////////////////////////////////////////////////////////////// // public methods //////////////////////////////////////////////////////////////////////////////// @@ -62,15 +87,17 @@ TableOfContentsFrame::TableOfContentsFrame(const ID3v2::Header *tagHeader, const setData(data); } -TableOfContentsFrame::TableOfContentsFrame(const ByteVector &eID, const ByteVectorList &ch, - const FrameList &eF) : +TableOfContentsFrame::TableOfContentsFrame(const ByteVector &elementID, + const ByteVectorList &children, + const FrameList &embeddedFrames) : ID3v2::Frame("CTOC") { d = new TableOfContentsFramePrivate; - d->elementID = eID; - d->childElements = ch; - FrameList l = eF; - for(FrameList::ConstIterator it = l.begin(); it != l.end(); ++it) + d->elementID = elementID; + strip(d->elementID); + d->childElements = children; + + for(FrameList::ConstIterator it = embeddedFrames.begin(); it != embeddedFrames.end(); ++it) addEmbeddedFrame(*it); } @@ -107,8 +134,7 @@ ByteVectorList TableOfContentsFrame::childElements() const void TableOfContentsFrame::setElementID(const ByteVector &eID) { d->elementID = eID; - if(eID.at(eID.size() - 1) != char(0)) - d->elementID.append(char(0)); + strip(d->elementID); } void TableOfContentsFrame::setIsTopLevel(const bool &t) @@ -124,16 +150,22 @@ void TableOfContentsFrame::setIsOrdered(const bool &o) void TableOfContentsFrame::setChildElements(const ByteVectorList &l) { d->childElements = l; + strip(d->childElements); } void TableOfContentsFrame::addChildElement(const ByteVector &cE) { d->childElements.append(cE); + strip(d->childElements); } void TableOfContentsFrame::removeChildElement(const ByteVector &cE) { ByteVectorList::Iterator it = d->childElements.find(cE); + + if(it == d->childElements.end()) + it = d->childElements.find(cE + ByteVector("\0")); + d->childElements.erase(it); } @@ -240,18 +272,19 @@ void TableOfContentsFrame::parseFields(const ByteVector &data) size_t pos = 0; size_t embPos = 0; d->elementID = readStringField(data, String::Latin1, pos).data(String::Latin1); - d->elementID.append(char(0)); d->isTopLevel = (data.at(pos) & 2) > 0; d->isOrdered = (data.at(pos++) & 1) > 0; TagLib::uint entryCount = data.at(pos++); - for(TagLib::uint i = 0; i < entryCount; i++) - { + for(TagLib::uint i = 0; i < entryCount; i++) { ByteVector childElementID = readStringField(data, String::Latin1, pos).data(String::Latin1); - childElementID.append(char(0)); d->childElements.append(childElementID); } size -= pos; + + if(size < header()->size()) + return; + while(embPos < size - header()->size()) { Frame *frame = FrameFactory::instance()->createFrame(data.mid(pos + embPos), d->tagHeader); @@ -274,6 +307,7 @@ ByteVector TableOfContentsFrame::renderFields() const ByteVector data; data.append(d->elementID); + data.append('\0'); char flags = 0; if(d->isTopLevel) flags += 2; @@ -284,6 +318,7 @@ ByteVector TableOfContentsFrame::renderFields() const ByteVectorList::ConstIterator it = d->childElements.begin(); while(it != d->childElements.end()) { data.append(*it); + data.append('\0'); it++; } FrameList l = d->embeddedFrameList; diff --git a/taglib/mpeg/id3v2/frames/tableofcontentsframe.h b/taglib/mpeg/id3v2/frames/tableofcontentsframe.h index 532e1d0e..66facf80 100644 --- a/taglib/mpeg/id3v2/frames/tableofcontentsframe.h +++ b/taglib/mpeg/id3v2/frames/tableofcontentsframe.h @@ -52,10 +52,13 @@ namespace TagLib { TableOfContentsFrame(const ID3v2::Header *tagHeader, const ByteVector &data); /*! - * Creates a table of contents frame with the element ID \a eID, - * the child elements \a ch and embedded frames, that are in \a eF. + * Creates a table of contents frame with the element ID \a elementID, + * the child elements \a children and embedded frames, which become owned + * by this frame, in \a embeddedFrames. */ - TableOfContentsFrame(const ByteVector &eID, const ByteVectorList &ch, const FrameList &eF); + TableOfContentsFrame(const ByteVector &elementID, + const ByteVectorList &children = ByteVectorList(), + const FrameList &embeddedFrames = FrameList()); /*! * Destroys the frame. @@ -71,7 +74,7 @@ namespace TagLib { ByteVector elementID() const; /*! - * Returns true, if the frame is top-level (doen't have + * Returns true, if the frame is top-level (doesn't have * any parent CTOC frame). * * \see setIsTopLevel() @@ -87,7 +90,7 @@ namespace TagLib { bool isOrdered() const; /*! - * Returns count of child elements of the frame. It allways + * Returns count of child elements of the frame. It always * corresponds to size of child elements list. * * \see childElements() @@ -110,7 +113,7 @@ namespace TagLib { void setElementID(const ByteVector &eID); /*! - * Sets, if the frame is top-level (doen't have + * Sets, if the frame is top-level (doesn't have * any parent CTOC frame). * * \see isTopLevel() diff --git a/taglib/mpeg/id3v2/frames/uniquefileidentifierframe.h b/taglib/mpeg/id3v2/frames/uniquefileidentifierframe.h index add5a2e0..decf1b0d 100644 --- a/taglib/mpeg/id3v2/frames/uniquefileidentifierframe.h +++ b/taglib/mpeg/id3v2/frames/uniquefileidentifierframe.h @@ -46,7 +46,7 @@ namespace TagLib { public: /*! - * Creates a uniqe file identifier frame based on \a data. + * Creates a unique file identifier frame based on \a data. */ UniqueFileIdentifierFrame(const ByteVector &data); diff --git a/taglib/mpeg/id3v2/frames/unsynchronizedlyricsframe.h b/taglib/mpeg/id3v2/frames/unsynchronizedlyricsframe.h index 3af354fc..dad67c7a 100644 --- a/taglib/mpeg/id3v2/frames/unsynchronizedlyricsframe.h +++ b/taglib/mpeg/id3v2/frames/unsynchronizedlyricsframe.h @@ -104,7 +104,7 @@ namespace TagLib { /*! * Sets the description of the unsynchronized lyrics frame to \a s. * - * \see decription() + * \see description() */ void setDescription(const String &s); @@ -149,7 +149,7 @@ namespace TagLib { /*! * LyricsFrames each have a unique description. This searches for a lyrics - * frame with the decription \a d and returns a pointer to it. If no + * frame with the description \a d and returns a pointer to it. If no * frame is found that matches the given description null is returned. * * \see description() diff --git a/taglib/mpeg/id3v2/id3v2frame.h b/taglib/mpeg/id3v2/id3v2frame.h index ec0b0374..a2ac1891 100644 --- a/taglib/mpeg/id3v2/id3v2frame.h +++ b/taglib/mpeg/id3v2/id3v2frame.h @@ -165,7 +165,7 @@ namespace TagLib { * All other processing of \a data should be handled in a subclass. * * \note This need not contain anything more than a frame ID, but - * \e must constain at least that. + * \e must contain at least that. */ explicit Frame(const ByteVector &data); @@ -218,9 +218,9 @@ namespace TagLib { ByteVector fieldData(const ByteVector &frameData) const; /*! - * Reads a String of type \a encodiong from the ByteVector \a data. If \a + * Reads a String of type \a encoding from the ByteVector \a data. If \a * position is passed in it is used both as the starting point and is - * updated to replect the position just after the string that has been read. + * updated to return the position just after the string that has been read. * This is useful for reading strings sequentially. */ String readStringField(const ByteVector &data, String::Type encoding, size_t &positon); @@ -459,7 +459,7 @@ namespace TagLib { bool readOnly() const; /*! - * Returns true if the flag for the grouping identifity is set. + * Returns true if the flag for the grouping identity is set. * * \note This flag is currently ignored internally in TagLib. */ diff --git a/taglib/mpeg/id3v2/id3v2framefactory.h b/taglib/mpeg/id3v2/id3v2framefactory.h index 023420b4..899ac582 100644 --- a/taglib/mpeg/id3v2/id3v2framefactory.h +++ b/taglib/mpeg/id3v2/id3v2framefactory.h @@ -84,7 +84,7 @@ namespace TagLib { * Returns the default text encoding for text frames. If setTextEncoding() * has not been explicitly called this will only be used for new text * frames. However, if this value has been set explicitly all frames will be - * converted to this type (unless it's explitly set differently for the + * converted to this type (unless it's explicitly set differently for the * individual frame) when being rendered. * * \see setDefaultTextEncoding() diff --git a/taglib/mpeg/id3v2/id3v2header.h b/taglib/mpeg/id3v2/id3v2header.h index 307ba96c..52294ddd 100644 --- a/taglib/mpeg/id3v2/id3v2header.h +++ b/taglib/mpeg/id3v2/id3v2header.h @@ -37,7 +37,7 @@ namespace TagLib { /*! * This class implements ID3v2 headers. It attempts to follow, both - * semantically and programatically, the structure specified in + * semantically and programmatically, the structure specified in * the ID3v2 standard. The API is based on the properties of ID3v2 headers * specified there. If any of the terms used in this documentation are * unclear please check the specification in the linked section. diff --git a/taglib/mpeg/id3v2/id3v2tag.cpp b/taglib/mpeg/id3v2/id3v2tag.cpp index 119ba811..ca26dd20 100644 --- a/taglib/mpeg/id3v2/id3v2tag.cpp +++ b/taglib/mpeg/id3v2/id3v2tag.cpp @@ -24,10 +24,10 @@ ***************************************************************************/ #ifdef HAVE_CONFIG_H -#include +#include "config.h" #endif -#include +#include "tfile.h" #include "id3v2tag.h" #include "id3v2header.h" @@ -37,7 +37,7 @@ #include "tbytevector.h" #include "id3v1genres.h" #include "tpropertymap.h" -#include +#include "tdebug.h" #include "frames/textidentificationframe.h" #include "frames/commentsframe.h" @@ -93,7 +93,7 @@ const TagLib::StringHandler *ID3v2::Tag::TagPrivate::stringHandler = &defaultStr namespace { - const uint DefaultPaddingSize = 1024; + const TagLib::uint DefaultPaddingSize = 1024; } //////////////////////////////////////////////////////////////////////////////// @@ -109,7 +109,7 @@ String ID3v2::Latin1StringHandler::parse(const ByteVector &data) const return String(data, String::Latin1); } -ByteVector ID3v2::Latin1StringHandler::render(const String &s) const +ByteVector ID3v2::Latin1StringHandler::render(const String &) const { return ByteVector(); } @@ -602,12 +602,19 @@ ByteVector ID3v2::Tag::render(int version) const for(FrameList::ConstIterator it = frameList.begin(); it != frameList.end(); it++) { (*it)->header()->setVersion(version); if((*it)->header()->frameID().size() != 4) { - debug("A frame of unsupported or unknown type \'" + debug("An ID3v2 frame of unsupported or unknown type \'" + String((*it)->header()->frameID()) + "\' has been discarded"); continue; } - if(!(*it)->header()->tagAlterPreservation()) - tagData.append((*it)->render()); + if(!(*it)->header()->tagAlterPreservation()) { + const ByteVector frameData = (*it)->render(); + if(frameData.size() == Frame::headerSize((*it)->header()->version())) { + debug("An empty ID3v2 frame \'" + + String((*it)->header()->frameID()) + "\' has been discarded"); + continue; + } + tagData.append(frameData); + } } // Compute the amount of padding, and append that to tagData. diff --git a/taglib/mpeg/id3v2/id3v2tag.h b/taglib/mpeg/id3v2/id3v2tag.h index 905ecc9b..cd742c5c 100644 --- a/taglib/mpeg/id3v2/id3v2tag.h +++ b/taglib/mpeg/id3v2/id3v2tag.h @@ -61,13 +61,13 @@ namespace TagLib { //! An abstraction for the ISO-8859-1 string to data encoding in ID3v2 tags. /*! - * ID3v2 tag can store strings in ISO-8859-1 (Latin1), and TagLib only - * supports genuine ISO-8859-1 by default. However, in practice, non - * ISO-8859-1 encodings are often used instead of ISO-8859-1, such as + * ID3v2 tag can store strings in ISO-8859-1 (Latin1), and TagLib only + * supports genuine ISO-8859-1 by default. However, in practice, non + * ISO-8859-1 encodings are often used instead of ISO-8859-1, such as * Windows-1252 for western languages, Shift_JIS for Japanese and so on. * * Here is an option to read such tags by subclassing this class, - * reimplementing parse() and setting your reimplementation as the default + * reimplementing parse() and setting your reimplementation as the default * with ID3v2::Tag::setStringHandler(). * * \note Writing non-ISO-8859-1 tags is not implemented intentionally. @@ -105,7 +105,7 @@ namespace TagLib { * split into data components. * * ID3v2 tags have several parts, TagLib attempts to provide an interface - * for them all. header(), footer() and extendedHeader() corespond to those + * for them all. header(), footer() and extendedHeader() correspond to those * data structures in the ID3v2 standard and the APIs for the classes that * they return attempt to reflect this. * @@ -122,7 +122,7 @@ namespace TagLib { * class. * * read() and parse() pass binary data to the other ID3v2 class structures, - * they do not handle parsing of flags or fields, for instace. Those are + * they do not handle parsing of flags or fields, for instance. Those are * handled by similar functions within those classes. * * \note All pointers to data structures within the tag will become invalid @@ -133,7 +133,7 @@ namespace TagLib { * rather long, but if you're planning on messing with this class and others * that deal with the details of ID3v2 (rather than the nice, safe, abstract * TagLib::Tag and friends), it's worth your time to familiarize yourself - * with said spec (which is distrubuted with the TagLib sources). TagLib + * with said spec (which is distributed with the TagLib sources). TagLib * tries to do most of the work, but with a little luck, you can still * convince it to generate invalid ID3v2 tags. The APIs for ID3v2 assume a * working knowledge of ID3v2 structure. You're been warned. @@ -157,7 +157,7 @@ namespace TagLib { * \note You should be able to ignore the \a factory parameter in almost * all situations. You would want to specify your own FrameFactory * subclass in the case that you are extending TagLib to support additional - * frame types, which would be incorperated into your factory. + * frame types, which would be incorporated into your factory. * * \see FrameFactory */ @@ -360,9 +360,9 @@ namespace TagLib { */ // BIC: combine with the above method ByteVector render(int version) const; - + /*! - * Gets the current string handler that decides how the "Latin-1" data + * Gets the current string handler that decides how the "Latin-1" data * will be converted to and from binary data. * * \see Latin1StringHandler @@ -376,7 +376,7 @@ namespace TagLib { * released and default ISO-8859-1 handler is restored. * * \note The caller is responsible for deleting the previous handler - * as needed after it is released. + * as needed after it is released. * * \see Latin1StringHandler */ diff --git a/taglib/mpeg/mpegfile.cpp b/taglib/mpeg/mpegfile.cpp index 4c7d00e0..0c2c849a 100644 --- a/taglib/mpeg/mpegfile.cpp +++ b/taglib/mpeg/mpegfile.cpp @@ -31,14 +31,31 @@ #include #include -#include - #include "mpegfile.h" #include "mpegheader.h" #include "tpropertymap.h" using namespace TagLib; +namespace +{ + /*! + * MPEG frames can be recognized by the bit pattern 11111111 111, so the + * first byte is easy to check for, however checking to see if the second byte + * starts with \e 111 is a bit more tricky, hence these functions. + */ + + inline bool firstSyncByte(uchar byte) + { + return (byte == 0xFF); + } + + inline bool secondSynchByte(uchar byte) + { + return ((byte & 0xE0) == 0xE0); + } +} + namespace { enum { ID3v2Index = 0, APEIndex = 1, ID3v1Index = 2 }; @@ -60,7 +77,6 @@ public: hasAPE(false), properties(0) { - } ~FilePrivate() @@ -95,33 +111,30 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -MPEG::File::File(FileName file, bool readProperties, - AudioProperties::ReadStyle propertiesStyle) : TagLib::File(file) +MPEG::File::File(FileName file, bool readProperties, AudioProperties::ReadStyle) : + TagLib::File(file), + d(new FilePrivate()) { - d = new FilePrivate; - if(isOpen()) - read(readProperties, propertiesStyle); + read(readProperties); } MPEG::File::File(FileName file, ID3v2::FrameFactory *frameFactory, - bool readProperties, AudioProperties::ReadStyle propertiesStyle) : - TagLib::File(file) + bool readProperties, AudioProperties::ReadStyle) : + TagLib::File(file), + d(new FilePrivate(frameFactory)) { - d = new FilePrivate(frameFactory); - if(isOpen()) - read(readProperties, propertiesStyle); + read(readProperties); } MPEG::File::File(IOStream *stream, ID3v2::FrameFactory *frameFactory, - bool readProperties, AudioProperties::ReadStyle propertiesStyle) : - TagLib::File(stream) + bool readProperties, AudioProperties::ReadStyle) : + TagLib::File(stream), + d(new FilePrivate(frameFactory)) { - d = new FilePrivate(frameFactory); - if(isOpen()) - read(readProperties, propertiesStyle); + read(readProperties); } MPEG::File::~File() @@ -152,11 +165,6 @@ bool MPEG::File::save() return save(AllTags); } -bool MPEG::File::save(int tags, bool stripOthers, int id3v2Version) -{ - return save(tags, stripOthers, id3v2Version, true); -} - bool MPEG::File::save(int tags, bool stripOthers, int id3v2Version, bool duplicateTags) { if(tags == NoTags && stripOthers) @@ -359,11 +367,11 @@ offset_t MPEG::File::nextFrameOffset(offset_t position) return position - 1; for(uint i = 0; i < buffer.size() - 1; i++) { - if(uchar(buffer[i]) == 0xff && secondSynchByte(buffer[i + 1])) + if(firstSyncByte(buffer[i]) && secondSynchByte(buffer[i + 1])) return position + i; } - foundLastSyncPattern = uchar(buffer[buffer.size() - 1]) == 0xff; + foundLastSyncPattern = firstSyncByte(buffer[buffer.size() - 1]); position += buffer.size(); } } @@ -384,11 +392,11 @@ offset_t MPEG::File::previousFrameOffset(offset_t position) if(buffer.isEmpty()) break; - if(foundFirstSyncPattern && uchar(buffer[buffer.size() - 1]) == 0xff) + if(foundFirstSyncPattern && firstSyncByte(buffer[buffer.size() - 1])) return position + buffer.size() - 1; for(int i = static_cast(buffer.size()) - 2; i >= 0; i--) { - if(uchar(buffer[i]) == 0xff && secondSynchByte(buffer[i + 1])) + if(firstSyncByte(buffer[i]) && secondSynchByte(buffer[i + 1])) return position + i; } @@ -401,7 +409,7 @@ offset_t MPEG::File::firstFrameOffset() { offset_t position = 0; - if(ID3v2Tag()) { + if(hasID3v2Tag()) { position = d->ID3v2Location + ID3v2Tag()->header()->completeTagSize(); // Skip duplicate ID3v2 tags. @@ -409,7 +417,7 @@ offset_t MPEG::File::firstFrameOffset() // Workaround for some faulty files that have duplicate ID3v2 tags. // Combination of EAC and LAME creates such files when configured incorrectly. - long location; + offset_t location; while((location = findID3v2(position)) >= 0) { seek(location); const ID3v2::Header header(readBlock(ID3v2::Header::size())); @@ -424,7 +432,7 @@ offset_t MPEG::File::firstFrameOffset() offset_t MPEG::File::lastFrameOffset() { - return previousFrameOffset(ID3v1Tag() ? d->ID3v1Location - 1 : length()); + return previousFrameOffset(hasID3v1Tag() ? d->ID3v1Location - 1 : length()); } bool MPEG::File::hasID3v1Tag() const @@ -446,7 +454,7 @@ bool MPEG::File::hasAPETag() const // private members //////////////////////////////////////////////////////////////////////////////// -void MPEG::File::read(bool readProperties, AudioProperties::ReadStyle propertiesStyle) +void MPEG::File::read(bool readProperties) { // Look for an ID3v2 tag @@ -485,7 +493,7 @@ void MPEG::File::read(bool readProperties, AudioProperties::ReadStyle properties } if(readProperties) - d->properties = new AudioProperties(this, propertiesStyle); + d->properties = new AudioProperties(this); // Make sure that we have our default tag types available. @@ -496,7 +504,7 @@ void MPEG::File::read(bool readProperties, AudioProperties::ReadStyle properties offset_t MPEG::File::findID3v2(offset_t offset) { // This method is based on the contents of TagLib::File::find(), but because - // of some subtlteies -- specifically the need to look for the bit pattern of + // of some subtleties -- specifically the need to look for the bit pattern of // an MPEG sync, it has been modified for use here. if(isValid() && ID3v2::Header::fileIdentifier().size() <= bufferSize()) { @@ -639,11 +647,3 @@ void MPEG::File::findAPE() d->APELocation = -1; d->APEFooterLocation = -1; } - -bool MPEG::File::secondSynchByte(char byte) -{ - std::bitset<8> b(byte); - - // check to see if the byte matches 111xxxxx - return b.test(7) && b.test(6) && b.test(5); -} diff --git a/taglib/mpeg/mpegfile.h b/taglib/mpeg/mpegfile.h index b2be675c..57a64098 100644 --- a/taglib/mpeg/mpegfile.h +++ b/taglib/mpeg/mpegfile.h @@ -163,7 +163,10 @@ namespace TagLib { * 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. + * with the concession of generality, use parameterized save call below. + * + * \warning In the current implementation, it's dangerous to call save() + * repeatedly. At worst it will corrupt the file. * * \see save(int tags) */ @@ -180,26 +183,11 @@ namespace TagLib { * * The \a id3v2Version parameter specifies the version of the saved * ID3v2 tag. It can be either 4 or 3. + * + * \warning In the current implementation, it's dangerous to call save() + * repeatedly. At worst it will corrupt the file. */ - bool save(int tags, bool stripOthers = true, int id3v2Version = 4); - - /*! - * 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. - * - * If \a stripOthers is true 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. - * - * The \a id3v2Version parameter specifies the version of the saved - * ID3v2 tag. It can be either 4 or 3. - * - * If \a duplicateTags is true and at least one tag -- ID3v1 or ID3v2 -- - * exists this will duplicate its content into the other tag. - */ - // BIC: combine with the above method - bool save(int tags, bool stripOthers, int id3v2Version, bool duplicateTags); + bool save(int tags, bool stripOthers = true, int id3v2Version = 4, bool duplicateTags = true); /*! * Returns a pointer to the ID3v2 tag of the file. @@ -267,6 +255,8 @@ namespace TagLib { * * \note This will also invalidate pointers to the ID3 and APE tags * as their memory will be freed. + * + * \note This will update the file immediately. */ bool strip(int tags = AllTags); @@ -277,6 +267,8 @@ namespace TagLib { * * If \a freeMemory is true the ID3 and APE tags will be deleted and * pointers to them will be invalidated. + * + * \note This will update the file immediately. */ // BIC: merge with the method above bool strip(int tags, bool freeMemory); @@ -285,6 +277,7 @@ namespace TagLib { * Set the ID3v2::FrameFactory to something other than the default. * * \see ID3v2FrameFactory + * \deprecated This value should be passed in via the constructor */ void setID3v2FrameFactory(const ID3v2::FrameFactory *factory); @@ -335,18 +328,11 @@ namespace TagLib { File(const File &); File &operator=(const File &); - void read(bool readProperties, AudioProperties::ReadStyle propertiesStyle); + void read(bool readProperties); offset_t findID3v2(offset_t offset); offset_t findID3v1(); void findAPE(); - /*! - * MPEG frames can be recognized by the bit pattern 11111111 111, so the - * first byte is easy to check for, however checking to see if the second byte - * starts with \e 111 is a bit more tricky, hence this member function. - */ - static bool secondSynchByte(char byte); - class FilePrivate; FilePrivate *d; }; diff --git a/taglib/mpeg/mpegheader.cpp b/taglib/mpeg/mpegheader.cpp index 9b884313..571b3057 100644 --- a/taglib/mpeg/mpegheader.cpp +++ b/taglib/mpeg/mpegheader.cpp @@ -53,8 +53,8 @@ namespace class MPEG::Header::HeaderPrivate { public: - HeaderPrivate() - : data(new HeaderData()) + HeaderPrivate() : + data(new HeaderData()) { data->isValid = false; data->layer = 0; @@ -66,7 +66,7 @@ public: data->isCopyrighted = false; data->isOriginal = false; data->frameLength = 0; - data->samplesPerFrame = 0; + data->samplesPerFrame = 0; } SHARED_PTR data; @@ -76,14 +76,14 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -MPEG::Header::Header(const ByteVector &data) - : d(new HeaderPrivate()) +MPEG::Header::Header(const ByteVector &data) : + d(new HeaderPrivate()) { parse(data); } -MPEG::Header::Header(const Header &h) - : d(new HeaderPrivate(*h.d)) +MPEG::Header::Header(const Header &h) : + d(new HeaderPrivate(*h.d)) { } @@ -213,8 +213,8 @@ void MPEG::Header::parse(const ByteVector &data) } }; - const int versionIndex = d->data->version == Version1 ? 0 : 1; - const int layerIndex = d->data->layer > 0 ? d->data->layer - 1 : 0; + const int versionIndex = (d->data->version == Version1) ? 0 : 1; + const int layerIndex = (d->data->layer > 0) ? d->data->layer - 1 : 0; // The bitrate index is encoded as the first 4 bits of the 3rd byte, // i.e. 1111xxxx @@ -253,13 +253,6 @@ void MPEG::Header::parse(const ByteVector &data) d->data->isCopyrighted = flags[3]; d->data->isPadded = flags[9]; - // Calculate the frame length - - if(d->data->layer == 1) - d->data->frameLength = 24000 * 2 * d->data->bitrate / d->data->sampleRate + int(d->data->isPadded); - else - d->data->frameLength = 72000 * d->data->bitrate / d->data->sampleRate + int(d->data->isPadded); - // Samples per frame static const int samplesPerFrame[3][2] = { @@ -271,6 +264,15 @@ void MPEG::Header::parse(const ByteVector &data) d->data->samplesPerFrame = samplesPerFrame[layerIndex][versionIndex]; + // Calculate the frame length + + static const int paddingSize[3] = { 4, 1, 1 }; + + d->data->frameLength = d->data->samplesPerFrame * d->data->bitrate * 125 / d->data->sampleRate; + + if(d->data->isPadded) + d->data->frameLength += paddingSize[layerIndex]; + // Now that we're done parsing, set this to be a valid frame. d->data->isValid = true; diff --git a/taglib/mpeg/mpegheader.h b/taglib/mpeg/mpegheader.h index 020ebd06..a55cac09 100644 --- a/taglib/mpeg/mpegheader.h +++ b/taglib/mpeg/mpegheader.h @@ -140,7 +140,7 @@ namespace TagLib { bool isOriginal() const; /*! - * Returns the frame length. + * Returns the frame length in bytes. */ int frameLength() const; diff --git a/taglib/mpeg/mpegproperties.cpp b/taglib/mpeg/mpegproperties.cpp index d7c28db7..9d0abe41 100644 --- a/taglib/mpeg/mpegproperties.cpp +++ b/taglib/mpeg/mpegproperties.cpp @@ -30,6 +30,8 @@ #include "mpegproperties.h" #include "mpegfile.h" #include "xingheader.h" +#include "apetag.h" +#include "apefooter.h" using namespace TagLib; @@ -65,7 +67,8 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -MPEG::AudioProperties::AudioProperties(File *file, ReadStyle style) : +MPEG::AudioProperties::AudioProperties(File *file, ReadStyle) : + TagLib::AudioProperties(), d(new PropertiesPrivate()) { read(file); @@ -77,6 +80,16 @@ MPEG::AudioProperties::~AudioProperties() } int MPEG::AudioProperties::length() const +{ + return lengthInSeconds(); +} + +int MPEG::AudioProperties::lengthInSeconds() const +{ + return d->length / 1000; +} + +int MPEG::AudioProperties::lengthInMilliseconds() const { return d->length; } @@ -137,105 +150,68 @@ bool MPEG::AudioProperties::isOriginal() const void MPEG::AudioProperties::read(File *file) { - // Since we've likely just looked for the ID3v1 tag, start at the end of the - // file where we're least likely to have to have to move the disk head. - - offset_t last = file->lastFrameOffset(); - - if(last < 0) { - debug("MPEG::Properties::read() -- Could not find a valid last MPEG frame in the stream."); - return; - } - - file->seek(last); - Header lastHeader(file->readBlock(4)); - - offset_t first = file->firstFrameOffset(); + // Only the first frame is required if we have a VBR header. + const offset_t first = file->firstFrameOffset(); if(first < 0) { debug("MPEG::Properties::read() -- Could not find a valid first MPEG frame in the stream."); return; } - if(!lastHeader.isValid()) { - - offset_t pos = last; - - while(pos > first) { - - pos = file->previousFrameOffset(pos); - - if(pos < 0) - break; - - file->seek(pos); - Header header(file->readBlock(4)); - - if(header.isValid()) { - lastHeader = header; - last = pos; - break; - } - } - } - - // Now jump back to the front of the file and read what we need from there. - file->seek(first); - Header firstHeader(file->readBlock(4)); + const Header firstHeader(file->readBlock(4)); - if(!firstHeader.isValid() || !lastHeader.isValid()) { - debug("MPEG::Properties::read() -- Page headers were invalid."); + if(!firstHeader.isValid()) { + debug("MPEG::Properties::read() -- The first page header is invalid."); return; } - // Check for a Xing header that will help us in gathering information about a + // Check for a VBR header that will help us in gathering information about a // VBR stream. - int xingHeaderOffset = MPEG::XingHeader::offset(firstHeader.version(), - firstHeader.channelMode()); - - file->seek(first + xingHeaderOffset); - d->xingHeader.reset(new XingHeader(file->readBlock(16))); - - // Read the length and the bitrate from the Xing header. - - if(d->xingHeader->isValid() && - firstHeader.sampleRate() > 0 && - d->xingHeader->totalFrames() > 0) - { - double timePerFrame = - double(firstHeader.samplesPerFrame()) / firstHeader.sampleRate(); - - double length = timePerFrame * d->xingHeader->totalFrames(); - - d->length = int(length); - d->bitrate = d->length > 0 ? (int)(d->xingHeader->totalSize() * 8 / length / 1000) : 0; - } - else { - // Since there was no valid Xing header found, we hope that we're in a constant - // bitrate file. - + file->seek(first + 4); + d->xingHeader.reset(new XingHeader(file->readBlock(firstHeader.frameLength() - 4))); + if(!d->xingHeader->isValid()) d->xingHeader.reset(); + if(d->xingHeader && firstHeader.samplesPerFrame() > 0 && firstHeader.sampleRate() > 0) { + + // Read the length and the bitrate from the VBR header. + + const double timePerFrame = firstHeader.samplesPerFrame() * 1000.0 / firstHeader.sampleRate(); + const double length = timePerFrame * d->xingHeader->totalFrames(); + + d->length = static_cast(length + 0.5); + d->bitrate = static_cast(d->xingHeader->totalSize() * 8.0 / length + 0.5); + } + else if(firstHeader.bitrate() > 0) { + + // Since there was no valid VBR header found, we hope that we're in a constant + // bitrate file. + // TODO: Make this more robust with audio property detection for VBR without a // Xing header. - if(firstHeader.frameLength() > 0 && firstHeader.bitrate() > 0) { - int frames = static_cast((last - first) / firstHeader.frameLength() + 1); + d->bitrate = firstHeader.bitrate(); - d->length = int(float(firstHeader.frameLength() * frames) / - float(firstHeader.bitrate() * 125) + 0.5); - d->bitrate = firstHeader.bitrate(); - } + offset_t streamLength = file->length() - first; + + if(file->hasID3v1Tag()) + streamLength -= 128; + + if(file->hasAPETag()) + streamLength -= file->APETag()->footer()->completeTagSize(); + + if(streamLength > 0) + d->length = static_cast(streamLength * 8.0 / d->bitrate + 0.5); } - d->sampleRate = firstHeader.sampleRate(); - d->channels = firstHeader.channelMode() == Header::SingleChannel ? 1 : 2; - d->version = firstHeader.version(); - d->layer = firstHeader.layer(); + d->sampleRate = firstHeader.sampleRate(); + d->channels = firstHeader.channelMode() == Header::SingleChannel ? 1 : 2; + d->version = firstHeader.version(); + d->layer = firstHeader.layer(); d->protectionEnabled = firstHeader.protectionEnabled(); - d->channelMode = firstHeader.channelMode(); - d->isCopyrighted = firstHeader.isCopyrighted(); - d->isOriginal = firstHeader.isOriginal(); + d->channelMode = firstHeader.channelMode(); + d->isCopyrighted = firstHeader.isCopyrighted(); + d->isOriginal = firstHeader.isOriginal(); } diff --git a/taglib/mpeg/mpegproperties.h b/taglib/mpeg/mpegproperties.h index 38e27adc..2abd8185 100644 --- a/taglib/mpeg/mpegproperties.h +++ b/taglib/mpeg/mpegproperties.h @@ -49,7 +49,7 @@ namespace TagLib { { public: /*! - * Creates an instance of MPEG::AudioProperties with the data read from + * Creates an instance of MPEG::AudioProperties with the data read from * the MPEG::File \a file. */ AudioProperties(File *file, ReadStyle style = Average); @@ -59,18 +59,52 @@ namespace TagLib { */ virtual ~AudioProperties(); - // Reimplementations. - + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \note This method is just an alias of lengthInSeconds(). + * + * \deprecated + */ virtual int length() const; + + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \see lengthInMilliseconds() + */ + // BIC: make virtual + int lengthInSeconds() const; + + /*! + * Returns the length of the file in milliseconds. + * + * \see lengthInSeconds() + */ + // BIC: make virtual + int lengthInMilliseconds() const; + + /*! + * Returns the average bit rate of the file in kb/s. + */ virtual int bitrate() const; + + /*! + * Returns the sample rate in Hz. + */ virtual int sampleRate() const; + + /*! + * Returns the number of audio channels. + */ virtual int channels() const; /*! - * Returns a pointer to the XingHeader if one exists or null if no - * XingHeader was found. + * Returns a pointer to the Xing/VBRI header if one exists or null if no + * Xing/VBRI header was found. */ - const XingHeader *xingHeader() const; /*! diff --git a/taglib/mpeg/xingheader.cpp b/taglib/mpeg/xingheader.cpp index 04ef67b5..80608f18 100644 --- a/taglib/mpeg/xingheader.cpp +++ b/taglib/mpeg/xingheader.cpp @@ -28,6 +28,7 @@ #include #include "xingheader.h" +#include "mpegfile.h" using namespace TagLib; @@ -37,17 +38,21 @@ public: XingHeaderPrivate() : frames(0), size(0), - valid(false) - {} + type(MPEG::XingHeader::Invalid) {} uint frames; uint size; - bool valid; + + MPEG::XingHeader::HeaderType type; }; -MPEG::XingHeader::XingHeader(const ByteVector &data) +//////////////////////////////////////////////////////////////////////////////// +// public members +//////////////////////////////////////////////////////////////////////////////// + +MPEG::XingHeader::XingHeader(const ByteVector &data) : + d(new XingHeaderPrivate()) { - d = new XingHeaderPrivate; parse(data); } @@ -58,7 +63,7 @@ MPEG::XingHeader::~XingHeader() bool MPEG::XingHeader::isValid() const { - return d->valid; + return (d->type != Invalid && d->frames > 0 && d->size > 0); } TagLib::uint MPEG::XingHeader::totalFrames() const @@ -71,45 +76,59 @@ TagLib::uint MPEG::XingHeader::totalSize() const return d->size; } -int MPEG::XingHeader::offset(TagLib::MPEG::Header::Version v, - TagLib::MPEG::Header::ChannelMode c) +MPEG::XingHeader::HeaderType MPEG::XingHeader::type() const { - if(v == MPEG::Header::Version1) { - if(c == MPEG::Header::SingleChannel) - return 0x15; - else - return 0x24; - } - else { - if(c == MPEG::Header::SingleChannel) - return 0x0D; - else - return 0x15; - } + return d->type; } +//////////////////////////////////////////////////////////////////////////////// +// private members +//////////////////////////////////////////////////////////////////////////////// + void MPEG::XingHeader::parse(const ByteVector &data) { - // Check to see if a valid Xing header is available. + // Look for a Xing header. - if(!data.startsWith("Xing") && !data.startsWith("Info")) - return; + size_t offset = data.find("Xing"); + if(offset == ByteVector::npos) + offset = data.find("Info"); - // If the XingHeader doesn't contain the number of frames and the total stream - // info it's invalid. + if(offset != ByteVector::npos) { - if(!(data[7] & 0x01)) { - debug("MPEG::XingHeader::parse() -- Xing header doesn't contain the total number of frames."); - return; + // Xing header found. + + if(data.size() < static_cast(offset + 16)) { + debug("MPEG::XingHeader::parse() -- Xing header found but too short."); + return; + } + + if((data[offset + 7] & 0x03) != 0x03) { + debug("MPEG::XingHeader::parse() -- Xing header doesn't contain the required information."); + return; + } + + d->frames = data.toUInt32BE(offset + 8); + d->size = data.toUInt32BE(offset + 12); + d->type = Xing; } + else { - if(!(data[7] & 0x02)) { - debug("MPEG::XingHeader::parse() -- Xing header doesn't contain the total stream size."); - return; + // Xing header not found. Then look for a VBRI header. + + offset = data.find("VBRI"); + + if(offset != ByteVector::npos) { + + // VBRI header found. + + if(data.size() < static_cast(offset + 32)) { + debug("MPEG::XingHeader::parse() -- VBRI header found but too short."); + return; + } + + d->frames = data.toUInt32BE(offset + 14); + d->size = data.toUInt32BE(offset + 10); + d->type = VBRI; + } } - - d->frames = data.toUInt32BE(8); - d->size = data.toUInt32BE(12); - - d->valid = true; } diff --git a/taglib/mpeg/xingheader.h b/taglib/mpeg/xingheader.h index 0875135e..037a66d0 100644 --- a/taglib/mpeg/xingheader.h +++ b/taglib/mpeg/xingheader.h @@ -35,24 +35,47 @@ namespace TagLib { namespace MPEG { - //! An implementation of the Xing VBR headers + class File; + + //! An implementation of the Xing/VBRI headers /*! - * This is a minimalistic implementation of the Xing VBR headers. Xing - * headers are often added to VBR (variable bit rate) MP3 streams to make it - * easy to compute the length and quality of a VBR stream. Our implementation - * is only concerned with the total size of the stream (so that we can - * calculate the total playing time and the average bitrate). It uses - * this text - * and the XMMS sources as references. + * This is a minimalistic implementation of the Xing/VBRI VBR headers. + * Xing/VBRI headers are often added to VBR (variable bit rate) MP3 streams + * to make it easy to compute the length and quality of a VBR stream. Our + * implementation is only concerned with the total size of the stream (so + * that we can calculate the total playing time and the average bitrate). + * It uses + * this text and the XMMS sources as references. */ class TAGLIB_EXPORT XingHeader { public: /*! - * Parses a Xing header based on \a data. The data must be at least 16 - * bytes long (anything longer than this is discarded). + * The type of the VBR header. + */ + enum HeaderType + { + /*! + * Invalid header or no VBR header found. + */ + Invalid = 0, + + /*! + * Xing header. + */ + Xing = 1, + + /*! + * VBRI header. + */ + VBRI = 2, + }; + + /*! + * Parses an Xing/VBRI header based on \a data which contains the entire + * first MPEG frame. */ XingHeader(const ByteVector &data); @@ -63,7 +86,7 @@ namespace TagLib { /*! * Returns true if the data was parsed properly and if there is a valid - * Xing header present. + * Xing/VBRI header present. */ bool isValid() const; @@ -78,11 +101,9 @@ namespace TagLib { uint totalSize() const; /*! - * Returns the offset for the start of this Xing header, given the - * version and channels of the frame + * Returns the type of the VBR header. */ - static int offset(TagLib::MPEG::Header::Version v, - TagLib::MPEG::Header::ChannelMode c); + HeaderType type() const; private: XingHeader(const XingHeader &); diff --git a/taglib/ogg/flac/oggflacfile.h b/taglib/ogg/flac/oggflacfile.h index 950fbb07..38fd51ec 100644 --- a/taglib/ogg/flac/oggflacfile.h +++ b/taglib/ogg/flac/oggflacfile.h @@ -42,7 +42,7 @@ namespace TagLib { /*! * This is implementation of FLAC metadata for Ogg FLAC files. For "pure" - * FLAC files look under the FLAC hiearchy. + * FLAC files look under the FLAC hierarchy. * * Unlike "pure" FLAC-files, Ogg FLAC only supports Xiph-comments, * while the audio-properties are the same. @@ -64,7 +64,7 @@ namespace TagLib { { public: /*! - * Constructs an Ogg/FLAC file from \a file. If \a readProperties is true + * Constructs an Ogg/FLAC 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. @@ -73,7 +73,7 @@ namespace TagLib { AudioProperties::ReadStyle propertiesStyle = AudioProperties::Average); /*! - * Constructs an Ogg/FLAC file from \a stream. If \a readProperties is true + * Constructs an Ogg/FLAC 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 @@ -92,10 +92,10 @@ namespace TagLib { /*! * Returns the Tag for this file. This will always be a XiphComment. * - * \note This always returns a valid pointer regardless of whether or not - * the file on disk has a XiphComment. Use hasXiphComment() to check if + * \note This always returns a valid pointer regardless of whether or not + * the file on disk has a XiphComment. Use hasXiphComment() to check if * the file on disk actually has a XiphComment. - * + * * \note The Tag is still owned by the FLAC::File and should not be * deleted by the user. It will be deleted when the file (object) is * destroyed. @@ -111,22 +111,25 @@ namespace TagLib { virtual AudioProperties *audioProperties() const; - /*! + /*! * Implements the unified property interface -- export function. * This forwards directly to XiphComment::properties(). */ PropertyMap properties() const; - /*! + /*! * Implements the unified tag dictionary interface -- import function. * Like properties(), this is a forwarder to the file's XiphComment. */ - PropertyMap setProperties(const PropertyMap &); + PropertyMap setProperties(const PropertyMap &); /*! * Save the file. This will primarily save and update the XiphComment. * Returns true if the save is successful. + * + * \warning In the current implementation, it's dangerous to call save() + * repeatedly. It leads to a segfault. */ virtual bool save(); diff --git a/taglib/ogg/oggpage.h b/taglib/ogg/oggpage.h index ff905913..a2331b85 100644 --- a/taglib/ogg/oggpage.h +++ b/taglib/ogg/oggpage.h @@ -41,7 +41,7 @@ namespace TagLib { /*! * This is an implementation of the pages that make up an Ogg stream. * This handles parsing pages and breaking them down into packets and handles - * the details of packets spanning multiple pages and pages that contiain + * the details of packets spanning multiple pages and pages that contain * multiple packets. * * In most Xiph.org formats the comments are found in the first few packets, @@ -162,7 +162,7 @@ namespace TagLib { /*! * Pack \a packets into Ogg pages using the \a strategy for pagination. - * The page number indicater inside of the rendered packets will start + * The page number indicator inside of the rendered packets will start * with \a firstPage and be incremented for each page rendered. * \a containsLastPacket should be set to true if \a packets contains the * last page in the stream and will set the appropriate flag in the last diff --git a/taglib/ogg/opus/opusfile.cpp b/taglib/ogg/opus/opusfile.cpp index 7f7e3e78..e4d14474 100644 --- a/taglib/ogg/opus/opusfile.cpp +++ b/taglib/ogg/opus/opusfile.cpp @@ -27,8 +27,6 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ -#include - #include #include #include @@ -59,20 +57,20 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -Opus::File::File(FileName file, bool readProperties, AudioProperties::ReadStyle propertiesStyle) : +Opus::File::File(FileName file, bool readProperties, AudioProperties::ReadStyle) : Ogg::File(file), d(new FilePrivate()) { if(isOpen()) - read(readProperties, propertiesStyle); + read(readProperties); } -Opus::File::File(IOStream *stream, bool readProperties, AudioProperties::ReadStyle propertiesStyle) : +Opus::File::File(IOStream *stream, bool readProperties, AudioProperties::ReadStyle) : Ogg::File(stream), d(new FilePrivate()) { if(isOpen()) - read(readProperties, propertiesStyle); + read(readProperties); } Opus::File::~File() @@ -114,7 +112,7 @@ bool Opus::File::save() // private members //////////////////////////////////////////////////////////////////////////////// -void Opus::File::read(bool readProperties, AudioProperties::ReadStyle propertiesStyle) +void Opus::File::read(bool readProperties) { ByteVector opusHeaderData = packet(0); @@ -135,5 +133,5 @@ void Opus::File::read(bool readProperties, AudioProperties::ReadStyle properties d->comment = new Ogg::XiphComment(commentHeaderData.mid(8)); if(readProperties) - d->properties = new AudioProperties(this, propertiesStyle); + d->properties = new AudioProperties(this); } diff --git a/taglib/ogg/opus/opusfile.h b/taglib/ogg/opus/opusfile.h index 2658648a..5aa7a111 100644 --- a/taglib/ogg/opus/opusfile.h +++ b/taglib/ogg/opus/opusfile.h @@ -106,13 +106,21 @@ namespace TagLib { */ virtual AudioProperties *audioProperties() const; + /*! + * Save the file. + * + * This returns true if the save was successful. + * + * \warning In the current implementation, it's dangerous to call save() + * repeatedly. It leads to a segfault. + */ virtual bool save(); private: File(const File &); File &operator=(const File &); - void read(bool readProperties, AudioProperties::ReadStyle propertiesStyle); + void read(bool readProperties); class FilePrivate; FilePrivate *d; diff --git a/taglib/ogg/opus/opusproperties.cpp b/taglib/ogg/opus/opusproperties.cpp index b3636626..3e01eb02 100644 --- a/taglib/ogg/opus/opusproperties.cpp +++ b/taglib/ogg/opus/opusproperties.cpp @@ -58,7 +58,8 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -Opus::AudioProperties::AudioProperties(File *file, ReadStyle style) : +Opus::AudioProperties::AudioProperties(File *file, ReadStyle) : + TagLib::AudioProperties(), d(new PropertiesPrivate()) { read(file); @@ -70,6 +71,16 @@ Opus::AudioProperties::~AudioProperties() } int Opus::AudioProperties::length() const +{ + return lengthInSeconds(); +} + +int Opus::AudioProperties::lengthInSeconds() const +{ + return d->length / 1000; +} + +int Opus::AudioProperties::lengthInMilliseconds() const { return d->length; } @@ -147,8 +158,13 @@ void Opus::AudioProperties::read(File *file) const long long end = last->absoluteGranularPosition(); if(start >= 0 && end >= 0) { - d->length = (int)((end - start - preSkip) / 48000); - d->bitrate = (int)(file->length() * 8.0 / (1000.0 * d->length) + 0.5); + const long long frameCount = (end - start - preSkip); + + if(frameCount > 0) { + const double length = frameCount * 1000.0 / 48000.0; + d->length = static_cast(length + 0.5); + d->bitrate = static_cast(file->length() * 8.0 / length + 0.5); + } } else { debug("Opus::Properties::read() -- The PCM values for the start or " diff --git a/taglib/ogg/opus/opusproperties.h b/taglib/ogg/opus/opusproperties.h index 7309a58d..5d4201d3 100644 --- a/taglib/ogg/opus/opusproperties.h +++ b/taglib/ogg/opus/opusproperties.h @@ -51,7 +51,7 @@ namespace TagLib { { public: /*! - * Creates an instance of Opus::AudioProperties with the data read from + * Creates an instance of Opus::AudioProperties with the data read from * the Opus::File \a file. */ AudioProperties(File *file, ReadStyle style = Average); @@ -61,11 +61,49 @@ namespace TagLib { */ virtual ~AudioProperties(); - // Reimplementations. - + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \note This method is just an alias of lengthInSeconds(). + * + * \deprecated + */ virtual int length() const; + + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \see lengthInMilliseconds() + */ + // BIC: make virtual + int lengthInSeconds() const; + + /*! + * Returns the length of the file in milliseconds. + * + * \see lengthInSeconds() + */ + // BIC: make virtual + int lengthInMilliseconds() const; + + /*! + * Returns the average bit rate of the file in kb/s. + */ virtual int bitrate() const; + + /*! + * Returns the sample rate in Hz. + * + * \note Always returns 48000, because Opus can decode any stream at a + * sample rate of 8, 12, 16, 24, or 48 kHz, + */ virtual int sampleRate() const; + + /*! + * Returns the number of audio channels. + */ virtual int channels() const; /*! @@ -76,7 +114,7 @@ namespace TagLib { int inputSampleRate() const; /*! - * Returns the Opus version, currently "0" (as specified by the spec). + * Returns the Opus version, in the range 0...255. */ int opusVersion() const; diff --git a/taglib/ogg/speex/speexfile.cpp b/taglib/ogg/speex/speexfile.cpp index 96f5c8eb..0f9ef335 100644 --- a/taglib/ogg/speex/speexfile.cpp +++ b/taglib/ogg/speex/speexfile.cpp @@ -27,8 +27,6 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ -#include - #include #include #include @@ -59,20 +57,20 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -Speex::File::File(FileName file, bool readProperties, - AudioProperties::ReadStyle propertiesStyle) : Ogg::File(file) +Speex::File::File(FileName file, bool readProperties, AudioProperties::ReadStyle) : + Ogg::File(file), + d(new FilePrivate()) { - d = new FilePrivate; if(isOpen()) - read(readProperties, propertiesStyle); + read(readProperties); } -Speex::File::File(IOStream *stream, bool readProperties, - AudioProperties::ReadStyle propertiesStyle) : Ogg::File(stream) +Speex::File::File(IOStream *stream, bool readProperties, AudioProperties::ReadStyle) : + Ogg::File(stream), + d(new FilePrivate()) { - d = new FilePrivate; if(isOpen()) - read(readProperties, propertiesStyle); + read(readProperties); } Speex::File::~File() @@ -114,7 +112,7 @@ bool Speex::File::save() // private members //////////////////////////////////////////////////////////////////////////////// -void Speex::File::read(bool readProperties, AudioProperties::ReadStyle propertiesStyle) +void Speex::File::read(bool readProperties) { ByteVector speexHeaderData = packet(0); @@ -128,5 +126,5 @@ void Speex::File::read(bool readProperties, AudioProperties::ReadStyle propertie d->comment = new Ogg::XiphComment(commentHeaderData); if(readProperties) - d->properties = new AudioProperties(this, propertiesStyle); + d->properties = new AudioProperties(this); } diff --git a/taglib/ogg/speex/speexfile.h b/taglib/ogg/speex/speexfile.h index 8073cd6f..ab3b0d54 100644 --- a/taglib/ogg/speex/speexfile.h +++ b/taglib/ogg/speex/speexfile.h @@ -106,15 +106,21 @@ namespace TagLib { */ virtual AudioProperties *audioProperties() const; - - + /*! + * Save the file. + * + * This returns true if the save was successful. + * + * \warning In the current implementation, it's dangerous to call save() + * repeatedly. It leads to a segfault. + */ virtual bool save(); private: File(const File &); File &operator=(const File &); - void read(bool readProperties, AudioProperties::ReadStyle propertiesStyle); + void read(bool readProperties); class FilePrivate; FilePrivate *d; diff --git a/taglib/ogg/speex/speexproperties.cpp b/taglib/ogg/speex/speexproperties.cpp index 6784ca58..72e85f5b 100644 --- a/taglib/ogg/speex/speexproperties.cpp +++ b/taglib/ogg/speex/speexproperties.cpp @@ -43,6 +43,7 @@ public: PropertiesPrivate() : length(0), bitrate(0), + bitrateNominal(0), sampleRate(0), channels(0), speexVersion(0), @@ -51,6 +52,7 @@ public: int length; int bitrate; + int bitrateNominal; int sampleRate; int channels; int speexVersion; @@ -62,7 +64,8 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -Speex::AudioProperties::AudioProperties(File *file, ReadStyle style) : +Speex::AudioProperties::AudioProperties(File *file, ReadStyle) : + TagLib::AudioProperties(), d(new PropertiesPrivate()) { read(file); @@ -74,13 +77,28 @@ Speex::AudioProperties::~AudioProperties() } int Speex::AudioProperties::length() const +{ + return lengthInSeconds(); +} + +int Speex::AudioProperties::lengthInSeconds() const +{ + return d->length / 1000; +} + +int Speex::AudioProperties::lengthInMilliseconds() const { return d->length; } int Speex::AudioProperties::bitrate() const { - return int(float(d->bitrate) / float(1000) + 0.5); + return d->bitrate; +} + +int Speex::AudioProperties::bitrateNominal() const +{ + return d->bitrateNominal; } int Speex::AudioProperties::sampleRate() const @@ -107,6 +125,10 @@ void Speex::AudioProperties::read(File *file) // Get the identification header from the Ogg implementation. const ByteVector data = file->packet(0); + if(data.size() < 64) { + debug("Speex::Properties::read() -- data is too short."); + return; + } size_t pos = 28; @@ -133,7 +155,7 @@ void Speex::AudioProperties::read(File *file) pos += 4; // bitrate; /**< Bit-rate used */ - d->bitrate = data.toUInt32LE(pos); + d->bitrateNominal = data.toUInt32LE(pos); pos += 4; // frame_size; /**< Size of frames */ @@ -154,12 +176,25 @@ void Speex::AudioProperties::read(File *file) const long long start = first->absoluteGranularPosition(); const long long end = last->absoluteGranularPosition(); - if(start >= 0 && end >= 0 && d->sampleRate > 0) - d->length = (int) ((end - start) / (long long) d->sampleRate); - else + if(start >= 0 && end >= 0 && d->sampleRate > 0) { + const long long frameCount = end - start; + + if(frameCount > 0) { + const double length = frameCount * 1000.0 / d->sampleRate; + d->length = static_cast(length + 0.5); + d->bitrate = static_cast(file->length() * 8.0 / length + 0.5); + } + } + else { debug("Speex::Properties::read() -- Either the PCM values for the start or " "end of this file was incorrect or the sample rate is zero."); + } } else debug("Speex::Properties::read() -- Could not find valid first and last Ogg pages."); + + // Alternative to the actual average bitrate. + + if(d->bitrate == 0 && d->bitrateNominal > 0) + d->bitrate = static_cast(d->bitrateNominal / 1000.0 + 0.5); } diff --git a/taglib/ogg/speex/speexproperties.h b/taglib/ogg/speex/speexproperties.h index 91fad343..fb497e44 100644 --- a/taglib/ogg/speex/speexproperties.h +++ b/taglib/ogg/speex/speexproperties.h @@ -51,7 +51,7 @@ namespace TagLib { { public: /*! - * Creates an instance of Speex::AudioProperties with the data read from + * Creates an instance of Speex::AudioProperties with the data read from * the Speex::File \a file. */ AudioProperties(File *file, ReadStyle style = Average); @@ -61,11 +61,51 @@ namespace TagLib { */ virtual ~AudioProperties(); - // Reimplementations. - + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \note This method is just an alias of lengthInSeconds(). + * + * \deprecated + */ virtual int length() const; + + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \see lengthInMilliseconds() + */ + // BIC: make virtual + int lengthInSeconds() const; + + /*! + * Returns the length of the file in milliseconds. + * + * \see lengthInSeconds() + */ + // BIC: make virtual + int lengthInMilliseconds() const; + + /*! + * Returns the average bit rate of the file in kb/s. + */ virtual int bitrate() const; + + /*! + * Returns the nominal bit rate as read from the Speex header in kb/s. + */ + int bitrateNominal() const; + + /*! + * Returns the sample rate in Hz. + */ virtual int sampleRate() const; + + /*! + * Returns the number of audio channels. + */ virtual int channels() const; /*! diff --git a/taglib/ogg/vorbis/vorbisfile.cpp b/taglib/ogg/vorbis/vorbisfile.cpp index 22b1c2d0..9fcf72a1 100644 --- a/taglib/ogg/vorbis/vorbisfile.cpp +++ b/taglib/ogg/vorbis/vorbisfile.cpp @@ -63,20 +63,20 @@ namespace TagLib { // public members //////////////////////////////////////////////////////////////////////////////// -Ogg::Vorbis::File::File(FileName file, bool readProperties, - AudioProperties::ReadStyle propertiesStyle) : Ogg::File(file) +Ogg::Vorbis::File::File(FileName file, bool readProperties, AudioProperties::ReadStyle) : + Ogg::File(file), + d(new FilePrivate()) { - d = new FilePrivate; if(isOpen()) - read(readProperties, propertiesStyle); + read(readProperties); } -Ogg::Vorbis::File::File(IOStream *stream, bool readProperties, - AudioProperties::ReadStyle propertiesStyle) : Ogg::File(stream) +Ogg::Vorbis::File::File(IOStream *stream, bool readProperties, AudioProperties::ReadStyle) : + Ogg::File(stream), + d(new FilePrivate()) { - d = new FilePrivate; if(isOpen()) - read(readProperties, propertiesStyle); + read(readProperties); } Ogg::Vorbis::File::~File() @@ -121,7 +121,7 @@ bool Ogg::Vorbis::File::save() // private members //////////////////////////////////////////////////////////////////////////////// -void Ogg::Vorbis::File::read(bool readProperties, AudioProperties::ReadStyle propertiesStyle) +void Ogg::Vorbis::File::read(bool readProperties) { ByteVector commentHeaderData = packet(1); @@ -134,5 +134,5 @@ void Ogg::Vorbis::File::read(bool readProperties, AudioProperties::ReadStyle pro d->comment = new Ogg::XiphComment(commentHeaderData.mid(7)); if(readProperties) - d->properties = new AudioProperties(this, propertiesStyle); + d->properties = new AudioProperties(this); } diff --git a/taglib/ogg/vorbis/vorbisfile.h b/taglib/ogg/vorbis/vorbisfile.h index 2a054a2c..e05ebe35 100644 --- a/taglib/ogg/vorbis/vorbisfile.h +++ b/taglib/ogg/vorbis/vorbisfile.h @@ -105,13 +105,21 @@ namespace TagLib { */ virtual AudioProperties *audioProperties() const; + /*! + * Save the file. + * + * This returns true if the save was successful. + * + * \warning In the current implementation, it's dangerous to call save() + * repeatedly. It leads to a segfault. + */ virtual bool save(); private: File(const File &); File &operator=(const File &); - void read(bool readProperties, AudioProperties::ReadStyle propertiesStyle); + void read(bool readProperties); class FilePrivate; FilePrivate *d; diff --git a/taglib/ogg/vorbis/vorbisproperties.cpp b/taglib/ogg/vorbis/vorbisproperties.cpp index 41cb87d5..6dc13f0e 100644 --- a/taglib/ogg/vorbis/vorbisproperties.cpp +++ b/taglib/ogg/vorbis/vorbisproperties.cpp @@ -68,7 +68,8 @@ namespace TagLib { // public members //////////////////////////////////////////////////////////////////////////////// -Ogg::Vorbis::AudioProperties::AudioProperties(File *file, ReadStyle style) : +Ogg::Vorbis::AudioProperties::AudioProperties(File *file, ReadStyle) : + TagLib::AudioProperties(), d(new PropertiesPrivate()) { read(file); @@ -80,13 +81,23 @@ Ogg::Vorbis::AudioProperties::~AudioProperties() } int Ogg::Vorbis::AudioProperties::length() const +{ + return lengthInSeconds(); +} + +int Ogg::Vorbis::AudioProperties::lengthInSeconds() const +{ + return d->length / 1000; +} + +int Ogg::Vorbis::AudioProperties::lengthInMilliseconds() const { return d->length; } int Ogg::Vorbis::AudioProperties::bitrate() const { - return int(float(d->bitrate) / float(1000) + 0.5); + return d->bitrate; } int Ogg::Vorbis::AudioProperties::sampleRate() const @@ -137,6 +148,10 @@ void Ogg::Vorbis::AudioProperties::read(File *file) // Get the identification header from the Ogg implementation. const ByteVector data = file->packet(0); + if(data.size() < 28) { + debug("Vorbis::Properties::read() -- data is too short."); + return; + } size_t pos = 0; @@ -163,9 +178,7 @@ void Ogg::Vorbis::AudioProperties::read(File *file) pos += 4; d->bitrateMinimum = data.toUInt32LE(pos); - - // TODO: Later this should be only the "fast" mode. - d->bitrate = d->bitrateNominal; + pos += 4; // Find the length of the file. See http://wiki.xiph.org/Ogg::VorbisStreamLength/ // for my notes on the topic. @@ -177,12 +190,26 @@ void Ogg::Vorbis::AudioProperties::read(File *file) const long long start = first->absoluteGranularPosition(); const long long end = last->absoluteGranularPosition(); - if(start >= 0 && end >= 0 && d->sampleRate > 0) - d->length = (int)((end - start) / (long long) d->sampleRate); - else - debug("Ogg::Vorbis::Properties::read() -- Either the PCM values for the start or " + if(start >= 0 && end >= 0 && d->sampleRate > 0) { + const long long frameCount = end - start; + + if(frameCount > 0) { + const double length = frameCount * 1000.0 / d->sampleRate; + + d->length = static_cast(length + 0.5); + d->bitrate = static_cast(file->length() * 8.0 / length + 0.5); + } + } + else { + debug("Vorbis::Properties::read() -- Either the PCM values for the start or " "end of this file was incorrect or the sample rate is zero."); + } } else - debug("Ogg::Vorbis::Properties::read() -- Could not find valid first and last Ogg pages."); + debug("Vorbis::Properties::read() -- Could not find valid first and last Ogg pages."); + + // Alternative to the actual average bitrate. + + if(d->bitrate == 0 && d->bitrateNominal > 0) + d->bitrate = static_cast(d->bitrateNominal / 1000.0 + 0.5); } diff --git a/taglib/ogg/vorbis/vorbisproperties.h b/taglib/ogg/vorbis/vorbisproperties.h index 3f56d3b9..49ad3a29 100644 --- a/taglib/ogg/vorbis/vorbisproperties.h +++ b/taglib/ogg/vorbis/vorbisproperties.h @@ -58,11 +58,46 @@ namespace TagLib { */ virtual ~AudioProperties(); - // Reimplementations. - + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \note This method is just an alias of lengthInSeconds(). + * + * \deprecated + */ virtual int length() const; + + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \see lengthInMilliseconds() + */ + // BIC: make virtual + int lengthInSeconds() const; + + /*! + * Returns the length of the file in milliseconds. + * + * \see lengthInSeconds() + */ + // BIC: make virtual + int lengthInMilliseconds() const; + + /*! + * Returns the average bit rate of the file in kb/s. + */ virtual int bitrate() const; + + /*! + * Returns the sample rate in Hz. + */ virtual int sampleRate() const; + + /*! + * Returns the number of audio channels. + */ virtual int channels() const; virtual String toString() const; diff --git a/taglib/riff/aiff/aifffile.cpp b/taglib/riff/aiff/aifffile.cpp index 7b83e1cb..f12f72df 100644 --- a/taglib/riff/aiff/aifffile.cpp +++ b/taglib/riff/aiff/aifffile.cpp @@ -40,10 +40,7 @@ public: properties(0), tag(0), tagChunkID("ID3 "), - hasID3v2(false) - { - - } + hasID3v2(false) {} ~FilePrivate() { @@ -62,20 +59,20 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -RIFF::AIFF::File::File(FileName file, bool readProperties, - AudioProperties::ReadStyle propertiesStyle) : RIFF::File(file, BigEndian) +RIFF::AIFF::File::File(FileName file, bool readProperties, AudioProperties::ReadStyle) : + RIFF::File(file, BigEndian), + d(new FilePrivate()) { - d = new FilePrivate; if(isOpen()) - read(readProperties, propertiesStyle); + read(readProperties); } -RIFF::AIFF::File::File(IOStream *stream, bool readProperties, - AudioProperties::ReadStyle propertiesStyle) : RIFF::File(stream, BigEndian) +RIFF::AIFF::File::File(IOStream *stream, bool readProperties, AudioProperties::ReadStyle) : + RIFF::File(stream, BigEndian), + d(new FilePrivate()) { - d = new FilePrivate; if(isOpen()) - read(readProperties, propertiesStyle); + read(readProperties); } RIFF::AIFF::File::~File() @@ -135,18 +132,25 @@ bool RIFF::AIFF::File::hasID3v2Tag() const // private members //////////////////////////////////////////////////////////////////////////////// -void RIFF::AIFF::File::read(bool readProperties, AudioProperties::ReadStyle propertiesStyle) +void RIFF::AIFF::File::read(bool readProperties) { - for(uint i = 0; i < chunkCount(); i++) { - if(chunkName(i) == "ID3 " || chunkName(i) == "id3 ") { - d->tagChunkID = chunkName(i); - d->tag = new ID3v2::Tag(this, chunkOffset(i)); - d->hasID3v2 = true; + for(uint i = 0; i < chunkCount(); ++i) { + const ByteVector name = chunkName(i); + if(name == "ID3 " || name == "id3 ") { + if(!d->tag) { + d->tag = new ID3v2::Tag(this, chunkOffset(i)); + d->tagChunkID = name; + d->hasID3v2 = true; + } + else { + debug("RIFF::AIFF::File::read() - Duplicate ID3v2 tag found."); + } } - else if(chunkName(i) == "COMM" && readProperties) - d->properties = new AudioProperties(chunkData(i), propertiesStyle); } if(!d->tag) - d->tag = new ID3v2::Tag; + d->tag = new ID3v2::Tag(); + + if(readProperties) + d->properties = new AudioProperties(this, AudioProperties::Average); } diff --git a/taglib/riff/aiff/aifffile.h b/taglib/riff/aiff/aifffile.h index 6686d6de..5578de60 100644 --- a/taglib/riff/aiff/aifffile.h +++ b/taglib/riff/aiff/aifffile.h @@ -86,8 +86,8 @@ namespace TagLib { /*! * Returns the 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 + * \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() @@ -130,7 +130,9 @@ namespace TagLib { File(const File &); File &operator=(const File &); - void read(bool readProperties, AudioProperties::ReadStyle propertiesStyle); + void read(bool readProperties); + + friend class AudioProperties; class FilePrivate; FilePrivate *d; diff --git a/taglib/riff/aiff/aiffproperties.cpp b/taglib/riff/aiff/aiffproperties.cpp index ad18e97b..de162468 100644 --- a/taglib/riff/aiff/aiffproperties.cpp +++ b/taglib/riff/aiff/aiffproperties.cpp @@ -25,6 +25,7 @@ #include #include +#include "aifffile.h" #include "aiffproperties.h" using namespace TagLib; @@ -37,14 +38,14 @@ public: bitrate(0), sampleRate(0), channels(0), - sampleWidth(0), + bitsPerSample(0), sampleFrames(0) {} int length; int bitrate; int sampleRate; int channels; - int sampleWidth; + int bitsPerSample; ByteVector compressionType; String compressionName; @@ -56,10 +57,18 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -RIFF::AIFF::AudioProperties::AudioProperties(const ByteVector &data, ReadStyle style) : +RIFF::AIFF::AudioProperties::AudioProperties(const ByteVector &, ReadStyle) : + TagLib::AudioProperties(), d(new PropertiesPrivate()) { - read(data); + debug("RIFF::AIFF::Properties::Properties() - This constructor is no longer used."); +} + +RIFF::AIFF::AudioProperties::AudioProperties(File *file, ReadStyle) : + TagLib::AudioProperties(), + d(new PropertiesPrivate()) +{ + read(file); } RIFF::AIFF::AudioProperties::~AudioProperties() @@ -73,6 +82,16 @@ bool RIFF::AIFF::AudioProperties::isNull() const } int RIFF::AIFF::AudioProperties::length() const +{ + return lengthInSeconds(); +} + +int RIFF::AIFF::AudioProperties::lengthInSeconds() const +{ + return d->length / 1000; +} + +int RIFF::AIFF::AudioProperties::lengthInMilliseconds() const { return d->length; } @@ -92,9 +111,14 @@ int RIFF::AIFF::AudioProperties::channels() const return d->channels; } +int RIFF::AIFF::AudioProperties::bitsPerSample() const +{ + return d->bitsPerSample; +} + int RIFF::AIFF::AudioProperties::sampleWidth() const { - return d->sampleWidth; + return bitsPerSample(); } TagLib::uint RIFF::AIFF::AudioProperties::sampleFrames() const @@ -121,24 +145,52 @@ String RIFF::AIFF::AudioProperties::compressionName() const // private members //////////////////////////////////////////////////////////////////////////////// -void RIFF::AIFF::AudioProperties::read(const ByteVector &data) +void RIFF::AIFF::AudioProperties::read(File *file) { + ByteVector data; + uint streamLength = 0; + for(uint i = 0; i < file->chunkCount(); i++) { + const ByteVector name = file->chunkName(i); + if(name == "COMM") { + if(data.isEmpty()) + data = file->chunkData(i); + else + debug("RIFF::AIFF::Properties::read() - Duplicate 'COMM' chunk found."); + } + else if(name == "SSND") { + if(streamLength == 0) + streamLength = file->chunkDataSize(i) + file->chunkPadding(i); + else + debug("RIFF::AIFF::Properties::read() - Duplicate 'SSND' chunk found."); + } + } + if(data.size() < 18) { - debug("RIFF::AIFF::AudioProperties::read() - \"COMM\" chunk is too short for AIFF."); + debug("RIFF::AIFF::Properties::read() - 'COMM' chunk not found or too short."); return; } - d->channels = data.toInt16BE(0); - d->sampleFrames = data.toUInt32BE(2); - d->sampleWidth = data.toInt16BE(6); + if(streamLength == 0) { + debug("RIFF::AIFF::Properties::read() - 'SSND' chunk not found."); + return; + } + + d->channels = data.toUInt16BE(0); + d->sampleFrames = data.toUInt32BE(2); + d->bitsPerSample = data.toUInt16BE(6); const long double sampleRate = data.toFloat80BE(8); - d->sampleRate = static_cast(sampleRate); - d->bitrate = static_cast((sampleRate * d->sampleWidth * d->channels) / 1000.0); - d->length = d->sampleRate > 0 ? d->sampleFrames / d->sampleRate : 0; + if(sampleRate >= 1.0) + d->sampleRate = static_cast(sampleRate + 0.5); + + if(d->sampleFrames > 0 && d->sampleRate > 0) { + const double length = d->sampleFrames * 1000.0 / d->sampleRate; + d->length = static_cast(length + 0.5); + d->bitrate = static_cast(streamLength * 8.0 / length + 0.5); + } if(data.size() >= 23) { d->compressionType = data.mid(18, 4); - d->compressionName = String(data.mid(23, static_cast(data[22]))); + d->compressionName = String(data.mid(23, static_cast(data[22])), String::Latin1); } } diff --git a/taglib/riff/aiff/aiffproperties.h b/taglib/riff/aiff/aiffproperties.h index 09a7ed01..575a31b5 100644 --- a/taglib/riff/aiff/aiffproperties.h +++ b/taglib/riff/aiff/aiffproperties.h @@ -47,11 +47,19 @@ namespace TagLib { { public: /*! - * Creates an instance of AIFF::AudioProperties with the data read from + * Create an instance of AIFF::AudioProperties with the data read from * the ByteVector \a data. + * + * \deprecated */ AudioProperties(const ByteVector &data, ReadStyle style); + /*! + * Create an instance of AIFF::AudioProperties with the data read from + * the AIFF::File \a file. + */ + AudioProperties(File *file, ReadStyle style); + /*! * Destroys this AIFF::AudioProperties instance. */ @@ -59,14 +67,65 @@ namespace TagLib { virtual bool isNull() const; - // Reimplementations. - + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \note This method is just an alias of lengthInSeconds(). + * + * \deprecated + */ virtual int length() const; + + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \see lengthInMilliseconds() + */ + // BIC: make virtual + int lengthInSeconds() const; + + /*! + * Returns the length of the file in milliseconds. + * + * \see lengthInSeconds() + */ + // BIC: make virtual + int lengthInMilliseconds() const; + + /*! + * Returns the average bit rate of the file in kb/s. + */ virtual int bitrate() const; + + /*! + * Returns the sample rate in Hz. + */ virtual int sampleRate() const; + + /*! + * Returns the number of audio channels. + */ virtual int channels() const; + /*! + * Returns the number of bits per audio sample. + */ + int bitsPerSample() const; + + /*! + * Returns the number of bits per audio sample. + * + * \note This method is just an alias of bitsPerSample(). + * + * \deprecated + */ int sampleWidth() const; + + /*! + * Returns the number of sample frames + */ uint sampleFrames() const; /*! @@ -94,7 +153,7 @@ namespace TagLib { String compressionName() const; private: - void read(const ByteVector &data); + void read(File *file); class PropertiesPrivate; PropertiesPrivate *d; diff --git a/taglib/riff/rifffile.cpp b/taglib/riff/rifffile.cpp index 958f1cae..0fb0306a 100644 --- a/taglib/riff/rifffile.cpp +++ b/taglib/riff/rifffile.cpp @@ -144,15 +144,7 @@ ByteVector RIFF::File::chunkData(uint i) if(i >= chunkCount()) return ByteVector::null; - // Offset for the first subchunk's data - - long begin = 12 + 8; - - for(uint it = 0; it < i; it++) - begin += 8 + d->chunks[it].size + d->chunks[it].padding; - - seek(begin); - + seek(d->chunks[i].offset); return readBlock(d->chunks[i].size); } diff --git a/taglib/riff/rifffile.h b/taglib/riff/rifffile.h index 2a759f8c..36a52890 100644 --- a/taglib/riff/rifffile.h +++ b/taglib/riff/rifffile.h @@ -99,7 +99,7 @@ namespace TagLib { ByteVector chunkData(uint i); /*! - * Sets the data for the the specified chunk to \a data. + * Sets the data for the specified chunk to \a data. * * \warning This will update the file immediately. */ @@ -119,9 +119,9 @@ namespace TagLib { * given name already exists it will be overwritten, otherwise it will be * created after the existing chunks. * - * \note If \a alwaysCreate is true, a new chunk is created regardless of - * whether or not the chunk \a name exists. It should only be used for - * "LIST" chunks. + * \note If \a alwaysCreate is true, a new chunk is created regardless of + * whether or not the chunk \a name exists. It should only be used for + * "LIST" chunks. * * \warning This will update the file immediately. */ diff --git a/taglib/riff/wav/infotag.h b/taglib/riff/wav/infotag.h index ff01af7a..aa65fd63 100644 --- a/taglib/riff/wav/infotag.h +++ b/taglib/riff/wav/infotag.h @@ -38,7 +38,7 @@ namespace TagLib { class File; - //! A RIFF INFO tag implementation. + //! A RIFF INFO tag implementation. namespace RIFF { namespace Info { @@ -52,8 +52,8 @@ namespace TagLib { * In practice, local encoding of each system is largely used and UTF-8 is * popular too. * - * Here is an option to read and write tags in your preferrd encoding - * by subclassing this class, reimplementing parse() and render() and setting + * Here is an option to read and write tags in your preferrd encoding + * by subclassing this class, reimplementing parse() and render() and setting * your reimplementation as the default with Info::Tag::setStringHandler(). * * \see ID3v1::Tag::setStringHandler() @@ -72,7 +72,7 @@ namespace TagLib { /*! * Encode a ByteVector with the data from \a s. The default implementation - * assumes that \a s is an UTF-8 string. + * assumes that \a s is an UTF-8 string. */ virtual ByteVector render(const String &s) const; }; @@ -80,11 +80,11 @@ namespace TagLib { //! The main class in the RIFF INFO tag implementation /*! - * This is the main class in the INFO tag implementation. RIFF INFO tag is - * a metadata format found in WAV audio and AVI video files. Though it is a - * part of Microsoft/IBM's RIFF specification, the author could not find the - * official documents about it. So, this implementation is referring to - * unofficial documents on the web and some applications' behaviors especially + * This is the main class in the INFO tag implementation. RIFF INFO tag is + * a metadata format found in WAV audio and AVI video files. Though it is a + * part of Microsoft/IBM's RIFF specification, the author could not find the + * official documents about it. So, this implementation is referring to + * unofficial documents on the web and some applications' behaviors especially * Windows Explorer. */ class TAGLIB_EXPORT Tag : public TagLib::Tag @@ -123,7 +123,7 @@ namespace TagLib { virtual bool isEmpty() const; /*! - * Returns a copy of the internal fields of the tag. The returned map directly + * Returns a copy of the internal fields of the tag. The returned map directly * reflects the contents of the "INFO" chunk. * * \note Modifying this map does not affect the tag's internal data. @@ -138,13 +138,13 @@ namespace TagLib { * Gets the value of the field with the ID \a id. */ String fieldText(const ByteVector &id) const; - + /* * Sets the value of the field with the ID \a id to \a s. * If the field does not exist, it is created. * If \s is empty, the field is removed. * - * \note fieldId must be four-byte long pure ASCII string. This function + * \note fieldId must be four-byte long pure ASCII string. This function * performs nothing if fieldId is invalid. */ void setFieldText(const ByteVector &id, const String &s); @@ -157,7 +157,7 @@ namespace TagLib { /*! * Render the tag back to binary data, suitable to be written to disk. * - * \note Returns empty ByteVector is the tag contains no fields. + * \note Returns empty ByteVector is the tag contains no fields. */ ByteVector render() const; @@ -173,14 +173,13 @@ namespace TagLib { * \see StringHandler */ static void setStringHandler(const TagLib::StringHandler *handler); - + protected: /*! * Pareses the body of the tag in \a data. */ void parse(const ByteVector &data); - private: Tag(const Tag &); Tag &operator=(const Tag &); diff --git a/taglib/riff/wav/wavfile.cpp b/taglib/riff/wav/wavfile.cpp index feab8d92..0c298e38 100644 --- a/taglib/riff/wav/wavfile.cpp +++ b/taglib/riff/wav/wavfile.cpp @@ -70,20 +70,20 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -RIFF::WAV::File::File(FileName file, bool readProperties, - AudioProperties::ReadStyle propertiesStyle) : RIFF::File(file, LittleEndian) +RIFF::WAV::File::File(FileName file, bool readProperties, AudioProperties::ReadStyle) : + RIFF::File(file, LittleEndian), + d(new FilePrivate()) { - d = new FilePrivate; if(isOpen()) - read(readProperties, propertiesStyle); + read(readProperties); } -RIFF::WAV::File::File(IOStream *stream, bool readProperties, - AudioProperties::ReadStyle propertiesStyle) : RIFF::File(stream, LittleEndian) +RIFF::WAV::File::File(IOStream *stream, bool readProperties, AudioProperties::ReadStyle) : + RIFF::File(stream, LittleEndian), + d(new FilePrivate()) { - d = new FilePrivate; if(isOpen()) - read(readProperties, propertiesStyle); + read(readProperties); } RIFF::WAV::File::~File() @@ -189,40 +189,42 @@ bool RIFF::WAV::File::hasInfoTag() const // private members //////////////////////////////////////////////////////////////////////////////// -void RIFF::WAV::File::read(bool readProperties, AudioProperties::ReadStyle propertiesStyle) +void RIFF::WAV::File::read(bool readProperties) { - ByteVector formatData; - uint streamLength = 0; - for(uint i = 0; i < chunkCount(); i++) { - String name = chunkName(i); + for(uint i = 0; i < chunkCount(); ++i) { + const ByteVector name = chunkName(i); if(name == "ID3 " || name == "id3 ") { - d->tagChunkID = chunkName(i); - d->tag.set(ID3v2Index, new ID3v2::Tag(this, chunkOffset(i))); - d->hasID3v2 = true; + if(!d->tag[ID3v2Index]) { + d->tagChunkID = name; + d->tag.set(ID3v2Index, new ID3v2::Tag(this, chunkOffset(i))); + d->hasID3v2 = true; + } + else { + debug("RIFF::WAV::File::read() - Duplicate ID3v2 tag found."); + } } - else if(name == "fmt " && readProperties) - formatData = chunkData(i); - else if(name == "data" && readProperties) - streamLength = chunkDataSize(i); else if(name == "LIST") { - ByteVector data = chunkData(i); - ByteVector type = data.mid(0, 4); - - if(type == "INFO") { - d->tag.set(InfoIndex, new RIFF::Info::Tag(data)); - d->hasInfo = true; + const ByteVector data = chunkData(i); + if(data.startsWith("INFO")) { + if(!d->tag[InfoIndex]) { + d->tag.set(InfoIndex, new RIFF::Info::Tag(data)); + d->hasInfo = true; + } + else { + debug("RIFF::WAV::File::read() - Duplicate INFO tag found."); + } } } } - if (!d->tag[ID3v2Index]) - d->tag.set(ID3v2Index, new ID3v2::Tag); + if(!d->tag[ID3v2Index]) + d->tag.set(ID3v2Index, new ID3v2::Tag()); - if (!d->tag[InfoIndex]) - d->tag.set(InfoIndex, new RIFF::Info::Tag); + if(!d->tag[InfoIndex]) + d->tag.set(InfoIndex, new RIFF::Info::Tag()); - if(!formatData.isEmpty()) - d->properties = new AudioProperties(formatData, streamLength, propertiesStyle); + if(readProperties) + d->properties = new AudioProperties(this, AudioProperties::Average); } void RIFF::WAV::File::strip(TagTypes tags) @@ -244,7 +246,7 @@ void RIFF::WAV::File::strip(TagTypes tags) TagLib::uint RIFF::WAV::File::findInfoTagChunk() { for(uint i = 0; i < chunkCount(); ++i) { - if(chunkName(i) == "LIST" && chunkData(i).mid(0, 4) == "INFO") { + if(chunkName(i) == "LIST" && chunkData(i).startsWith("INFO")) { return i; } } diff --git a/taglib/riff/wav/wavfile.h b/taglib/riff/wav/wavfile.h index 11ace2b5..f82fa3b2 100644 --- a/taglib/riff/wav/wavfile.h +++ b/taglib/riff/wav/wavfile.h @@ -104,8 +104,8 @@ namespace TagLib { /*! * 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 + * \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() @@ -115,8 +115,8 @@ namespace TagLib { /*! * Returns the RIFF INFO Tag for this file. * - * \note This always returns a valid pointer regardless of whether or not - * the file on disk has a RIFF INFO tag. Use hasInfoTag() to check if the + * \note This always returns a valid pointer regardless of whether or not + * the file on disk has a RIFF INFO tag. Use hasInfoTag() to check if the * file on disk actually has a RIFF INFO tag. * * \see hasInfoTag() @@ -149,7 +149,7 @@ namespace TagLib { virtual bool save(); bool save(TagTypes tags, bool stripOthers = true, int id3v2Version = 4); - + /*! * Returns whether or not the file on disk actually has an ID3v2 tag. * @@ -168,7 +168,7 @@ namespace TagLib { File(const File &); File &operator=(const File &); - void read(bool readProperties, AudioProperties::ReadStyle propertiesStyle); + void read(bool readProperties); void strip(TagTypes tags); @@ -177,6 +177,8 @@ namespace TagLib { */ uint findInfoTagChunk(); + friend class AudioProperties; + class FilePrivate; FilePrivate *d; }; diff --git a/taglib/riff/wav/wavproperties.cpp b/taglib/riff/wav/wavproperties.cpp index 0c4ba8d9..dc9a366c 100644 --- a/taglib/riff/wav/wavproperties.cpp +++ b/taglib/riff/wav/wavproperties.cpp @@ -25,10 +25,22 @@ #include #include + +#include "wavfile.h" #include "wavproperties.h" using namespace TagLib; +namespace +{ + // Quoted from RFC 2361. + enum WaveFormat + { + FORMAT_UNKNOWN = 0x0000, + FORMAT_PCM = 0x0001 + }; +} + class RIFF::WAV::AudioProperties::PropertiesPrivate { public: @@ -38,15 +50,15 @@ public: bitrate(0), sampleRate(0), channels(0), - sampleWidth(0), + bitsPerSample(0), sampleFrames(0) {} - short format; + int format; int length; int bitrate; int sampleRate; int channels; - int sampleWidth; + int bitsPerSample; uint sampleFrames; }; @@ -54,11 +66,25 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -RIFF::WAV::AudioProperties::AudioProperties(const ByteVector &data, uint streamLength, - ReadStyle style) : +RIFF::WAV::AudioProperties::AudioProperties(const ByteVector &, ReadStyle) : + TagLib::AudioProperties(), d(new PropertiesPrivate()) { - read(data, streamLength); + debug("RIFF::WAV::Properties::Properties() -- This constructor is no longer used."); +} + +RIFF::WAV::AudioProperties::AudioProperties(const ByteVector &, uint, ReadStyle) : + TagLib::AudioProperties(), + d(new PropertiesPrivate()) +{ + debug("RIFF::WAV::Properties::Properties() -- This constructor is no longer used."); +} + +TagLib::RIFF::WAV::AudioProperties::AudioProperties(File *file, ReadStyle) : + TagLib::AudioProperties(), + d(new PropertiesPrivate()) +{ + read(file); } RIFF::WAV::AudioProperties::~AudioProperties() @@ -67,6 +93,16 @@ RIFF::WAV::AudioProperties::~AudioProperties() } int RIFF::WAV::AudioProperties::length() const +{ + return lengthInSeconds(); +} + +int RIFF::WAV::AudioProperties::lengthInSeconds() const +{ + return d->length / 1000; +} + +int RIFF::WAV::AudioProperties::lengthInMilliseconds() const { return d->length; } @@ -86,9 +122,14 @@ int RIFF::WAV::AudioProperties::channels() const return d->channels; } +int RIFF::WAV::AudioProperties::bitsPerSample() const +{ + return d->bitsPerSample; +} + int RIFF::WAV::AudioProperties::sampleWidth() const { - return d->sampleWidth; + return bitsPerSample(); } TagLib::uint RIFF::WAV::AudioProperties::sampleFrames() const @@ -96,7 +137,7 @@ TagLib::uint RIFF::WAV::AudioProperties::sampleFrames() const return d->sampleFrames; } -TagLib::uint RIFF::WAV::AudioProperties::format() const +int RIFF::WAV::AudioProperties::format() const { return d->format; } @@ -105,24 +146,69 @@ TagLib::uint RIFF::WAV::AudioProperties::format() const // private members //////////////////////////////////////////////////////////////////////////////// -void RIFF::WAV::AudioProperties::read(const ByteVector &data, uint streamLength) +void RIFF::WAV::AudioProperties::read(File *file) { + ByteVector data; + uint streamLength = 0; + uint totalSamples = 0; + + for(uint i = 0; i < file->chunkCount(); ++i) { + const ByteVector name = file->chunkName(i); + if(name == "fmt ") { + if(data.isEmpty()) + data = file->chunkData(i); + else + debug("RIFF::WAV::Properties::read() - Duplicate 'fmt ' chunk found."); + } + else if(name == "data") { + if(streamLength == 0) + streamLength = file->chunkDataSize(i) + file->chunkPadding(i); + else + debug("RIFF::WAV::Properties::read() - Duplicate 'data' chunk found."); + } + else if(name == "fact") { + if(totalSamples == 0) + totalSamples = file->chunkData(i).toUInt32LE(0); + else + debug("RIFF::WAV::Properties::read() - Duplicate 'fact' chunk found."); + } + } + if(data.size() < 16) { - debug("RIFF::WAV::AudioProperties::read() - \"fmt \" chunk is too short for WAV."); + debug("RIFF::WAV::Properties::read() - 'fmt ' chunk not found or too short."); return; } - d->format = data.toInt16LE(0); - d->channels = data.toInt16LE(2); - d->sampleRate = data.toUInt32LE(4); - d->sampleWidth = data.toInt16LE(14); + if(streamLength == 0) { + debug("RIFF::WAV::Properties::read() - 'data' chunk not found."); + return; + } - const uint byteRate = data.toUInt32LE(8); - d->bitrate = byteRate * 8 / 1000; + d->format = data.toUInt16LE(0); + if(d->format != FORMAT_PCM && totalSamples == 0) { + debug("RIFF::WAV::Properties::read() - Non-PCM format, but 'fact' chunk not found."); + return; + } - d->length = byteRate > 0 ? streamLength / byteRate : 0; + d->channels = data.toUInt16LE(2); + d->sampleRate = data.toUInt32LE(4); + d->bitsPerSample = data.toUInt16LE(14); - // format ID 1 means uncompressed PCM. - if(d->format == 1 && d->channels > 0 && d->sampleWidth > 0) - d->sampleFrames = streamLength / (d->channels * ((d->sampleWidth + 7) / 8)); + if(totalSamples > 0) + d->sampleFrames = totalSamples; + else if(d->channels > 0 && d->bitsPerSample > 0) + d->sampleFrames = streamLength / (d->channels * ((d->bitsPerSample + 7) / 8)); + + if(d->sampleFrames > 0 && d->sampleRate > 0) { + const double length = d->sampleFrames * 1000.0 / d->sampleRate; + d->length = static_cast(length + 0.5); + d->bitrate = static_cast(streamLength * 8.0 / length + 0.5); + } + else { + const uint byteRate = data.toUInt32LE(8); + if(byteRate > 0) { + d->length = static_cast(streamLength * 1000.0 / byteRate + 0.5); + d->bitrate = static_cast(byteRate * 8.0 / 1000.0 + 0.5); + } + } } diff --git a/taglib/riff/wav/wavproperties.h b/taglib/riff/wav/wavproperties.h index 9a6dd4ba..e15daddc 100644 --- a/taglib/riff/wav/wavproperties.h +++ b/taglib/riff/wav/wavproperties.h @@ -50,25 +50,85 @@ namespace TagLib { { public: /*! - * Creates an instance of WAV::Properties with the data read from the + * Create an instance of WAV::Properties with the data read from the + * ByteVector \a data. + * + * \deprecated + */ + AudioProperties(const ByteVector &data, ReadStyle style); + + /*! + * Create an instance of WAV::Properties with the data read from the * ByteVector \a data and the length calculated using \a streamLength. + * + * \deprecated */ AudioProperties(const ByteVector &data, uint streamLength, ReadStyle style); /*! - * Destroys this WAV::AudioProperties instance. + * Create an instance of WAV::Properties with the data read from the + * WAV::File \a file. + */ + AudioProperties(File *file, ReadStyle style); + + /*! + * Destroys this WAV::Properties instance. */ virtual ~AudioProperties(); - // Reimplementations. - + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \note This method is just an alias of lengthInSeconds(). + * + * \deprecated + */ virtual int length() const; + + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \see lengthInMilliseconds() + */ + // BIC: make virtual + int lengthInSeconds() const; + + /*! + * Returns the length of the file in milliseconds. + * + * \see lengthInSeconds() + */ + // BIC: make virtual + int lengthInMilliseconds() const; + + /*! + * Returns the average bit rate of the file in kb/s. + */ virtual int bitrate() const; + + /*! + * Returns the sample rate in Hz. + */ virtual int sampleRate() const; + + /*! + * Returns the number of audio channels. + */ virtual int channels() const; /*! - * Returns the count of bits per sample. + * Returns the number of bits per audio sample. + */ + int bitsPerSample() const; + + /*! + * Returns the number of bits per audio sample. + * + * \note This method is just an alias of bitsPerSample(). + * + * \deprecated */ int sampleWidth() const; @@ -82,13 +142,17 @@ namespace TagLib { uint sampleFrames() const; /*! - * Returns the format ID of the WAVE file. For example, 0 for Unknown, - * 1 for PCM and so forth. + * Returns the format ID of the file. + * 0 for unknown, 1 for PCM, 2 for ADPCM, 3 for 32/64-bit IEEE754, and + * so forth. + * + * \note For further information, refer to the WAVE Form Registration + * Numbers in RFC 2361. */ - uint format() const; + int format() const; private: - void read(const ByteVector &data, uint streamLength); + void read(File *file); class PropertiesPrivate; PropertiesPrivate *d; diff --git a/taglib/s3m/s3mproperties.cpp b/taglib/s3m/s3mproperties.cpp index 065863c4..345d3d2a 100644 --- a/taglib/s3m/s3mproperties.cpp +++ b/taglib/s3m/s3mproperties.cpp @@ -59,7 +59,8 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -S3M::AudioProperties::AudioProperties(AudioProperties::ReadStyle propertiesStyle) : +S3M::AudioProperties::AudioProperties(AudioProperties::ReadStyle) : + TagLib::AudioProperties(), d(new PropertiesPrivate()) { } @@ -74,6 +75,16 @@ int S3M::AudioProperties::length() const return 0; } +int S3M::AudioProperties::lengthInSeconds() const +{ + return 0; +} + +int S3M::AudioProperties::lengthInMilliseconds() const +{ + return 0; +} + int S3M::AudioProperties::bitrate() const { return 0; diff --git a/taglib/s3m/s3mproperties.h b/taglib/s3m/s3mproperties.h index efdb4ba7..ecb2c712 100644 --- a/taglib/s3m/s3mproperties.h +++ b/taglib/s3m/s3mproperties.h @@ -50,10 +50,12 @@ namespace TagLib { AudioProperties(AudioProperties::ReadStyle propertiesStyle); virtual ~AudioProperties(); - int length() const; - int bitrate() const; - int sampleRate() const; - int channels() const; + int length() const; + int lengthInSeconds() const; + int lengthInMilliseconds() const; + int bitrate() const; + int sampleRate() const; + int channels() const; ushort lengthInPatterns() const; bool stereo() const; diff --git a/taglib/tag.cpp b/taglib/tag.cpp index bcb913d7..f4447a49 100644 --- a/taglib/tag.cpp +++ b/taglib/tag.cpp @@ -137,7 +137,7 @@ PropertyMap Tag::setProperties(const PropertyMap &origProps) setTrack(0); } else - setYear(0); + setTrack(0); // 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. diff --git a/taglib/tag.h b/taglib/tag.h index 693b38b4..a8199ea7 100644 --- a/taglib/tag.h +++ b/taglib/tag.h @@ -48,7 +48,7 @@ namespace TagLib { public: /*! - * Detroys this Tag instance. + * Destroys this Tag instance. */ virtual ~Tag(); diff --git a/taglib/toolkit/taglib.h b/taglib/toolkit/taglib.h index c9fc89a3..acf79598 100644 --- a/taglib/toolkit/taglib.h +++ b/taglib/toolkit/taglib.h @@ -27,8 +27,8 @@ #define TAGLIB_H #define TAGLIB_MAJOR_VERSION 1 -#define TAGLIB_MINOR_VERSION 9 -#define TAGLIB_PATCH_VERSION 1 +#define TAGLIB_MINOR_VERSION 10 +#define TAGLIB_PATCH_VERSION 0 #if (defined(_MSC_VER) && _MSC_VER >= 1600) #define TAGLIB_CONSTRUCT_BITSET(x) static_cast(x) @@ -85,7 +85,7 @@ namespace TagLib * - Full support for unicode and internationalized tags. * - Dual MPL and * LGPL licenses. - * - No external toolkit dependancies. + * - No external toolkit dependencies. * * \section why Why TagLib? * diff --git a/taglib/toolkit/tbytevector.cpp b/taglib/toolkit/tbytevector.cpp index 1a19c663..761a0468 100644 --- a/taglib/toolkit/tbytevector.cpp +++ b/taglib/toolkit/tbytevector.cpp @@ -195,7 +195,7 @@ inline T toNumber(const ByteVector &v, size_t offset) T tmp; ::memcpy(&tmp, v.data() + offset, sizeof(T)); - if(ENDIAN != Utils::SystemByteOrder) + if(ENDIAN != Utils::systemByteOrder()) return Utils::byteSwap(tmp); else return tmp; @@ -219,7 +219,7 @@ inline T toNumber(const ByteVector &v, size_t offset) template inline ByteVector fromNumber(T value) { - if (ENDIAN != Utils::SystemByteOrder) + if (ENDIAN != Utils::systemByteOrder()) value = Utils::byteSwap(value); return ByteVector(reinterpret_cast(&value), sizeof(T)); @@ -239,7 +239,7 @@ TFloat toFloat(const ByteVector &v, size_t offset) } tmp; ::memcpy(&tmp, v.data() + offset, sizeof(TInt)); - if(ENDIAN != Utils::FloatByteOrder) + if(ENDIAN != Utils::floatByteOrder()) tmp.i = Utils::byteSwap(tmp.i); return tmp.f; @@ -254,7 +254,7 @@ ByteVector fromFloat(TFloat value) } tmp; tmp.f = value; - if(ENDIAN != Utils::FloatByteOrder) + if(ENDIAN != Utils::floatByteOrder()) tmp.i = Utils::byteSwap(tmp.i); return ByteVector(reinterpret_cast(&tmp), sizeof(TInt)); @@ -640,7 +640,14 @@ ByteVector &ByteVector::resize(size_t size, char padding) { if(size != d->length) { detach(); + + // Remove the excessive length of the internal buffer first to pad correctly. + // This doesn't reallocate the buffer, since std::vector::resize() doesn't + // reallocate the buffer when shrinking. + + d->data->resize(d->offset + d->length); d->data->resize(d->offset + size, padding); + d->length = size; } @@ -650,7 +657,7 @@ ByteVector &ByteVector::resize(size_t size, char padding) ByteVector::Iterator ByteVector::begin() { detach(); - return d->data->begin(); + return d->data->begin() + d->offset; } ByteVector::ConstIterator ByteVector::begin() const @@ -661,7 +668,7 @@ ByteVector::ConstIterator ByteVector::begin() const ByteVector::Iterator ByteVector::end() { detach(); - return d->data->end(); + return d->data->begin() + d->offset + d->length; } ByteVector::ConstIterator ByteVector::end() const @@ -672,7 +679,7 @@ ByteVector::ConstIterator ByteVector::end() const ByteVector::ReverseIterator ByteVector::rbegin() { detach(); - return d->data->rbegin(); + return d->data->rbegin() + (d->data->size() - (d->offset + d->length)); } ByteVector::ConstReverseIterator ByteVector::rbegin() const @@ -683,7 +690,7 @@ ByteVector::ConstReverseIterator ByteVector::rbegin() const ByteVector::ReverseIterator ByteVector::rend() { detach(); - return d->data->rend(); + return d->data->rbegin() + (d->data->size() - d->offset); } ByteVector::ConstReverseIterator ByteVector::rend() const diff --git a/taglib/toolkit/tbytevector.h b/taglib/toolkit/tbytevector.h index 3cb98bae..e56ac67d 100644 --- a/taglib/toolkit/tbytevector.h +++ b/taglib/toolkit/tbytevector.h @@ -38,7 +38,7 @@ namespace TagLib { /*! * This class provides a byte vector with some methods that are useful for * tagging purposes. Many of the search functions are tailored to what is - * useful for finding tag related paterns in a data array. + * useful for finding tag related patterns in a data array. */ class TAGLIB_EXPORT ByteVector @@ -84,9 +84,10 @@ namespace TagLib { /*! * Constructs a byte vector that copies \a data up to the first null - * byte. The behavior is undefined if \a data is not null terminated. - * This is particularly useful for constructing byte arrays from string - * constants. + * byte. This is particularly useful for constructing byte arrays from + * string constants. + * + * \warning The behavior is undefined if \a data is not null terminated. */ ByteVector(const char *data); @@ -143,7 +144,7 @@ namespace TagLib { /*! * Searches the char for \a c starting at \a offset and returns - * the offset. Returns \a npos if the pattern was not found. If \a byteAlign is + * the offset. Returns \a -1 if the pattern was not found. If \a byteAlign is * specified the pattern will only be matched if it starts on a byte divisible * by \a byteAlign (starting from \a offset). */ @@ -280,7 +281,10 @@ namespace TagLib { /*! * Returns a CRC checksum of the byte vector's data. + * + * \note This uses an uncommon variant of CRC32 specializes in Ogg. */ + // BIC: Remove or make generic. uint checksum() const; /*! @@ -543,12 +547,14 @@ namespace TagLib { ByteVector &operator=(const ByteVector &v); /*! - * Copies ByteVector \a v. + * Copies a byte \a c. */ ByteVector &operator=(char c); /*! - * Copies ByteVector \a v. + * Copies \a data up to the first null byte. + * + * \warning The behavior is undefined if \a data is not null terminated. */ ByteVector &operator=(const char *data); diff --git a/taglib/toolkit/tdebuglistener.h b/taglib/toolkit/tdebuglistener.h index a32f285f..3c8e1185 100644 --- a/taglib/toolkit/tdebuglistener.h +++ b/taglib/toolkit/tdebuglistener.h @@ -29,17 +29,17 @@ #include "taglib_export.h" #include "tstring.h" -namespace TagLib +namespace TagLib { //! An abstraction for the listener to the debug messages. /*! - * This class enables you to handle the debug messages in your preferred - * way by subclassing this class, reimplementing printMessage() and setting + * This class enables you to handle the debug messages in your preferred + * way by subclassing this class, reimplementing printMessage() and setting * your reimplementation as the default with setDebugListener(). * * \see setDebugListener() - */ + */ class TAGLIB_EXPORT DebugListener { public: @@ -60,8 +60,8 @@ namespace TagLib /*! * Sets the listener that decides how the debug messages are redirected. - * If the parameter \a listener is null, the previous listener is released - * and default stderr listener is restored. + * If the parameter \a listener is null, the previous listener is released + * and default stderr listener is restored. * * \note The caller is responsible for deleting the previous listener * as needed after it is released. diff --git a/taglib/toolkit/tfile.cpp b/taglib/toolkit/tfile.cpp index 3acd7c93..c07d2946 100644 --- a/taglib/toolkit/tfile.cpp +++ b/taglib/toolkit/tfile.cpp @@ -36,14 +36,10 @@ using namespace TagLib; class File::FilePrivateBase { public: - FilePrivateBase() - : valid(true) - { - } + FilePrivateBase() : + valid(true) {} - virtual ~FilePrivateBase() - { - } + virtual ~FilePrivateBase() {} virtual IOStream *stream() const = 0; @@ -55,10 +51,8 @@ public: class File::ManagedFilePrivate : public File::FilePrivateBase { public: - ManagedFilePrivate(IOStream *stream) - : p(stream) - { - } + ManagedFilePrivate(IOStream *stream) : + p(stream) {} virtual IOStream *stream() const { @@ -74,10 +68,8 @@ private: class File::UnmanagedFilePrivate : public File::FilePrivateBase { public: - UnmanagedFilePrivate(IOStream *stream) - : p(stream) - { - } + UnmanagedFilePrivate(IOStream *stream) : + p(stream) {} virtual IOStream *stream() const { @@ -161,7 +153,7 @@ offset_t File::find(const ByteVector &pattern, offset_t fromOffset, const ByteVe // (2) The search pattern is wholly contained within the current buffer. // // (3) The current buffer ends with a partial match of the pattern. We will - // note this for use in the next itteration, where we will check for the rest + // note this for use in the next iteration, where we will check for the rest // of the pattern. // // All three of these are done in two steps. First we check for the pattern @@ -177,8 +169,8 @@ offset_t File::find(const ByteVector &pattern, offset_t fromOffset, const ByteVe // (1) previous partial match - if(previousPartialMatch != ByteVector::npos - && bufferSize() > previousPartialMatch) + if(previousPartialMatch != ByteVector::npos + && bufferSize() > previousPartialMatch) { const size_t patternOffset = (bufferSize() - previousPartialMatch); if(buffer.containsAt(pattern, 0, patternOffset)) { @@ -187,9 +179,9 @@ offset_t File::find(const ByteVector &pattern, offset_t fromOffset, const ByteVe } } - if(!before.isNull() - && beforePreviousPartialMatch != ByteVector::npos - && bufferSize() > beforePreviousPartialMatch) + if(!before.isNull() + && beforePreviousPartialMatch != ByteVector::npos + && bufferSize() > beforePreviousPartialMatch) { const size_t beforeOffset = (bufferSize() - beforePreviousPartialMatch); if(buffer.containsAt(before, 0, beforeOffset)) { @@ -243,21 +235,26 @@ offset_t File::rfind(const ByteVector &pattern, offset_t fromOffset, const ByteV // Start the search at the offset. - offset_t bufferOffset; - if(fromOffset == 0) { - seek(-1 * int(bufferSize()), End); - bufferOffset = tell(); - } - else { - seek(fromOffset + -1 * int(bufferSize()), Beginning); - bufferOffset = tell(); - } + if(fromOffset == 0) + fromOffset = length(); + + offset_t bufferLength = bufferSize(); + offset_t bufferOffset = fromOffset + pattern.size(); // See the notes in find() for an explanation of this algorithm. - while(true) - { - ByteVector buffer = readBlock(bufferSize()); + while(true) { + + if(bufferOffset > bufferLength) { + bufferOffset -= bufferLength; + } + else { + bufferLength = bufferOffset; + bufferOffset = 0; + } + seek(bufferOffset); + + const ByteVector buffer = readBlock(static_cast(bufferLength)); if(buffer.isEmpty()) break; @@ -277,9 +274,6 @@ offset_t File::rfind(const ByteVector &pattern, offset_t fromOffset, const ByteV } // TODO: (3) partial match - - bufferOffset -= bufferSize(); - seek(bufferOffset); } // Since we hit the end of the file, reset the status before continuing. diff --git a/taglib/toolkit/tfile.h b/taglib/toolkit/tfile.h index 2584383e..08de59eb 100644 --- a/taglib/toolkit/tfile.h +++ b/taglib/toolkit/tfile.h @@ -81,7 +81,7 @@ namespace TagLib { * names (uppercase Strings) to StringLists of tag values. Calls the according * specialization in the File subclasses. * For each metadata object of the file that could not be parsed into the PropertyMap - * format, the returend map's unsupportedData() list will contain one entry identifying + * format, the returned map's unsupportedData() list will contain one entry identifying * that object (e.g. the frame type for ID3v2 tags). Use removeUnsupportedProperties() * to remove (a subset of) them. * For files that contain more than one tag (e.g. an MP3 with both an ID3v1 and an ID3v2 @@ -102,7 +102,7 @@ namespace TagLib { * into the format-specific details. * If some value(s) could not be written imported to the specific metadata format, * the returned PropertyMap will contain those value(s). Otherwise it will be empty, - * indicating that no problems occured. + * indicating that no problems occurred. * With file types that support several tag formats (for instance, MP3 files can have * ID3v1, ID3v2, and APEv2 tags), this function will create the most appropriate one * (ID3v2 for MP3 files). Older formats will be updated as well, if they exist, but won't @@ -110,7 +110,7 @@ namespace TagLib { * See the documentation of the subclass implementations for detailed descriptions. */ virtual PropertyMap setProperties(const PropertyMap &properties); - + /*! * Returns a pointer to this file's audio properties. This should be * reimplemented in the concrete subclasses. If no audio properties were @@ -150,12 +150,12 @@ namespace TagLib { * Returns the offset in the file that \a pattern occurs at or -1 if it can * not be found. If \a before is set, the search will only continue until the * pattern \a before is found. This is useful for tagging purposes to search - * for a tag before the synch frame. + * for a tag before the sync frame. * * Searching starts at \a fromOffset, which defaults to the beginning of the * file. * - * \note This has the practial limitation that \a pattern can not be longer + * \note This has the practical limitation that \a pattern can not be longer * than the buffer size used by readBlock(). Currently this is 1024 bytes. */ offset_t find(const ByteVector &pattern, @@ -166,12 +166,12 @@ namespace TagLib { * Returns the offset in the file that \a pattern occurs at or -1 if it can * not be found. If \a before is set, the search will only continue until the * pattern \a before is found. This is useful for tagging purposes to search - * for a tag before the synch frame. + * for a tag before the sync frame. * * Searching starts at \a fromOffset and proceeds from the that point to the * beginning of the file and defaults to the end of the file. * - * \note This has the practial limitation that \a pattern can not be longer + * \note This has the practical limitation that \a pattern can not be longer * than the buffer size used by readBlock(). Currently this is 1024 bytes. */ offset_t rfind(const ByteVector &pattern, @@ -242,7 +242,7 @@ namespace TagLib { protected: /*! - * Construct a File object and opens the file specified by \a fileName. + * Construct a File object and opens the file specified by \a fileName. * * \note Constructor is protected since this class should only be * instantiated through subclasses. diff --git a/taglib/toolkit/tfilestream.cpp b/taglib/toolkit/tfilestream.cpp index 3a506dd9..882b3cdc 100644 --- a/taglib/toolkit/tfilestream.cpp +++ b/taglib/toolkit/tfilestream.cpp @@ -50,7 +50,6 @@ namespace typedef FileName FileNameHandle; typedef HANDLE FileHandle; - const size_t BufferSize = 8192; const FileHandle InvalidFileHandle = INVALID_HANDLE_VALUE; inline FileHandle openFile(const FileName &path, bool readOnly) @@ -98,7 +97,6 @@ namespace typedef FILE* FileHandle; - const size_t BufferSize = 1024; const FileHandle InvalidFileHandle = 0; inline FileHandle openFile(const FileName &path, bool readOnly) @@ -188,7 +186,7 @@ ByteVector FileStream::readBlock(size_t length) return ByteVector::null; const offset_t streamLength = FileStream::length(); - if(length > BufferSize && static_cast(length) > streamLength) + if(length > bufferSize() && static_cast(length) > streamLength) length = static_cast(streamLength); ByteVector buffer(length); @@ -248,10 +246,10 @@ void FileStream::insert(const ByteVector &data, offset_t start, size_t replace) // the *differnce* in the tag sizes. We want to avoid overwriting parts // that aren't yet in memory, so this is necessary. - size_t bufferLength = BufferSize; + size_t bufferLength = bufferSize(); while(data.size() - replace > bufferLength) - bufferLength += BufferSize; + bufferLength += bufferSize(); // Set where to start the reading and writing. @@ -303,7 +301,7 @@ void FileStream::removeBlock(offset_t start, size_t length) return; } - size_t bufferLength = BufferSize; + size_t bufferLength = bufferSize(); offset_t readPosition = start + length; offset_t writePosition = start; @@ -373,13 +371,10 @@ void FileStream::seek(offset_t offset, Position p) SetLastError(NO_ERROR); SetFilePointer(d->file, liOffset.LowPart, &liOffset.HighPart, whence); - if(GetLastError() == ERROR_NEGATIVE_SEEK) { - SetLastError(NO_ERROR); - SetFilePointer(d->file, 0, NULL, FILE_BEGIN); - } - if(GetLastError() != NO_ERROR) { + + const int lastError = GetLastError(); + if(lastError != NO_ERROR && lastError != ERROR_NEGATIVE_SEEK) debug("FileStream::seek() -- Failed to set the file pointer."); - } #else @@ -480,7 +475,7 @@ offset_t FileStream::length() size_t FileStream::bufferSize() { - return BufferSize; + return 1024; } //////////////////////////////////////////////////////////////////////////////// diff --git a/taglib/toolkit/tiostream.h b/taglib/toolkit/tiostream.h index fe1ce673..e856f765 100644 --- a/taglib/toolkit/tiostream.h +++ b/taglib/toolkit/tiostream.h @@ -47,7 +47,7 @@ namespace TagLib { FileName &operator=(const FileName &name); const std::wstring &wstr() const; - const std::string &str() const; + const std::string &str() const; String toString() const; diff --git a/taglib/toolkit/tlist.h b/taglib/toolkit/tlist.h index 116adf41..ee40f3a9 100644 --- a/taglib/toolkit/tlist.h +++ b/taglib/toolkit/tlist.h @@ -72,7 +72,7 @@ namespace TagLib { /*! * Destroys this List instance. If auto deletion is enabled and this list - * contains a pointer type all of the memebers are also deleted. + * contains a pointer type all of the members are also deleted. */ virtual ~List(); diff --git a/taglib/toolkit/tlist.tcc b/taglib/toolkit/tlist.tcc index a7890858..656c9224 100644 --- a/taglib/toolkit/tlist.tcc +++ b/taglib/toolkit/tlist.tcc @@ -299,9 +299,7 @@ template T &List::operator[](size_t i) { Iterator it = d->list.begin(); - - for(uint j = 0; j < i; j++) - ++it; + std::advance(it, i); return *it; } @@ -310,9 +308,7 @@ template const T &List::operator[](size_t i) const { ConstIterator it = d->list.begin(); - - for(uint j = 0; j < i; j++) - ++it; + std::advance(it, i); return *it; } diff --git a/taglib/toolkit/tmap.tcc b/taglib/toolkit/tmap.tcc index d388fb2d..b8c2d063 100644 --- a/taglib/toolkit/tmap.tcc +++ b/taglib/toolkit/tmap.tcc @@ -148,9 +148,7 @@ template Map &Map::erase(const Key &key) { detach(); - Iterator it = d->map.find(key); - if(it != d->map.end()) - d->map.erase(it); + d->map.erase(key); return *this; } diff --git a/taglib/toolkit/tpropertymap.h b/taglib/toolkit/tpropertymap.h index 2be49ddb..c1b835be 100644 --- a/taglib/toolkit/tpropertymap.h +++ b/taglib/toolkit/tpropertymap.h @@ -19,8 +19,8 @@ * MA 02110-1301 USA * ***************************************************************************/ -#ifndef PROPERTYMAP_H_ -#define PROPERTYMAP_H_ +#ifndef TAGLIB_PROPERTYMAP_H_ +#define TAGLIB_PROPERTYMAP_H_ #include "tmap.h" #include "tstringlist.h" @@ -34,13 +34,13 @@ namespace TagLib { /*! * This map implements a generic representation of textual audio metadata * ("tags") realized as pairs of a case-insensitive key - * and a nonempty list of corresponding values, each value being an an arbitrary + * and a nonempty list of corresponding values, each value being an arbitrary * unicode String. * * Note that most metadata formats pose additional conditions on the tag keys. The * most popular ones (Vorbis, APE, ID3v2) should support all ASCII only words of * length between 2 and 16. - * + * * This class can contain any tags, but here is a list of "well-known" tags that * you might want to use: * @@ -81,14 +81,14 @@ namespace TagLib { * - COPYRIGHT * - ENCODEDBY * - MOOD - * - COMMENT + * - COMMENT * - MEDIA * - LABEL * - CATALOGNUMBER * - BARCODE * * MusicBrainz identifiers: - * + * * - MUSICBRAINZ_TRACKID * - MUSICBRAINZ_ALBUMID * - MUSICBRAINZ_RELEASEGROUPID @@ -123,7 +123,7 @@ namespace TagLib { /*! * Inserts \a values under \a key in the map. If \a key already exists, - * then \values will be appended to the existing StringList. + * then \a values will be appended to the existing StringList. * The returned value indicates success, i.e. whether \a key is a * valid key. */ @@ -230,4 +230,4 @@ namespace TagLib { }; } -#endif /* PROPERTYMAP_H_ */ +#endif /* TAGLIB_PROPERTYMAP_H_ */ diff --git a/taglib/toolkit/trefcounter.cpp b/taglib/toolkit/trefcounter.cpp index fc508efc..b13215f3 100644 --- a/taglib/toolkit/trefcounter.cpp +++ b/taglib/toolkit/trefcounter.cpp @@ -69,16 +69,17 @@ namespace TagLib { + class RefCounter::RefCounterPrivate { public: - RefCounterPrivate() : + RefCounterPrivate() : refCount(1) {} ATOMIC_INT refCount; }; - RefCounter::RefCounter() : + RefCounter::RefCounter() : d(new RefCounterPrivate()) { } @@ -88,14 +89,14 @@ namespace TagLib delete d; } - void RefCounter::ref() + void RefCounter::ref() { - ATOMIC_INC(d->refCount); + ATOMIC_INC(d->refCount); } bool RefCounter::deref() - { - return (ATOMIC_DEC(d->refCount) == 0); + { + return (ATOMIC_DEC(d->refCount) == 0); } int RefCounter::count() const @@ -103,8 +104,8 @@ namespace TagLib return static_cast(d->refCount); } - bool RefCounter::unique() const - { - return (d->refCount == 1); + bool RefCounter::unique() const + { + return (d->refCount == 1); } } diff --git a/taglib/toolkit/tstring.cpp b/taglib/toolkit/tstring.cpp index 07c75c67..22a90187 100644 --- a/taglib/toolkit/tstring.cpp +++ b/taglib/toolkit/tstring.cpp @@ -665,7 +665,7 @@ void String::copyFromUTF8(const char *s, size_t length) { d->data->resize(length); - if(length > 0) { + if(length > 0) { const size_t len = UTF8toUTF16(s, length, &(*d->data)[0], d->data->size()); d->data->resize(len); } @@ -743,7 +743,7 @@ void String::copyFromUTF16(const char *s, size_t length, Type t) } const String::Type String::WCharByteOrder - = (Utils::SystemByteOrder == BigEndian) ? String::UTF16BE : String::UTF16LE; + = (Utils::systemByteOrder() == BigEndian) ? String::UTF16BE : String::UTF16LE; //////////////////////////////////////////////////////////////////////////////// // related functions diff --git a/taglib/toolkit/tstring.h b/taglib/toolkit/tstring.h index eeb96bcd..2a95aaef 100644 --- a/taglib/toolkit/tstring.h +++ b/taglib/toolkit/tstring.h @@ -37,7 +37,12 @@ * \note consider conversion via usual char-by-char for loop to avoid UTF16->UTF8->UTF16 * conversion happening in the background */ + +#if QT_VERSION >= 0x040000 +#define QStringToTString(s) TagLib::String(s.toUtf8().data(), TagLib::String::UTF8) +#else #define QStringToTString(s) TagLib::String(s.utf8().data(), TagLib::String::UTF8) +#endif /*! * \relates TagLib::String @@ -48,6 +53,7 @@ * conversion happening in the background * */ + #define TStringToQString(s) QString::fromUtf8(s.toCString(true)) namespace TagLib { @@ -482,7 +488,7 @@ namespace TagLib { /*! * To be able to use this class in a Map, this operator needed to be - * implemented. Returns true if \a s is less than this string in a bytewise + * implemented. Returns true if \a s is less than this string in a byte-wise * comparison. */ bool operator<(const String &s) const; diff --git a/taglib/toolkit/tstringlist.h b/taglib/toolkit/tstringlist.h index 8961bd4e..82c9b07f 100644 --- a/taglib/toolkit/tstringlist.h +++ b/taglib/toolkit/tstringlist.h @@ -38,7 +38,7 @@ namespace TagLib { //! A list of strings /*! - * This is a spcialization of the List class with some members convention for + * This is a specialization of the List class with some members convention for * string operations. */ diff --git a/taglib/toolkit/tutils.h b/taglib/toolkit/tutils.h index 155a5be1..409af82b 100644 --- a/taglib/toolkit/tutils.h +++ b/taglib/toolkit/tutils.h @@ -44,7 +44,7 @@ # include #endif -#include "tstring.h" +#include #include #include #include @@ -53,9 +53,13 @@ namespace TagLib { namespace Utils { + + /*! + * Reverses the order of bytes in an 16-bit integer. + */ inline ushort byteSwap(ushort x) { -#if defined(HAVE_GCC_BYTESWAP_16) +#if defined(HAVE_GCC_BYTESWAP) return __builtin_bswap16(x); @@ -82,9 +86,12 @@ namespace TagLib #endif } + /*! + * Reverses the order of bytes in an 32-bit integer. + */ inline uint byteSwap(uint x) { -#if defined(HAVE_GCC_BYTESWAP_32) +#if defined(HAVE_GCC_BYTESWAP) return __builtin_bswap32(x); @@ -114,9 +121,12 @@ namespace TagLib #endif } + /*! + * Reverses the order of bytes in an 64-bit integer. + */ inline ulonglong byteSwap(ulonglong x) { -#if defined(HAVE_GCC_BYTESWAP_64) +#if defined(HAVE_GCC_BYTESWAP) return __builtin_bswap64(x); @@ -150,6 +160,10 @@ namespace TagLib #endif } + /*! + * Returns a formatted string just like standard sprintf(), but makes use of + * safer functions such as snprintf() if available. + */ inline String formatString(const char *format, ...) { // Sufficient buffer size for the current internal uses. @@ -163,11 +177,11 @@ namespace TagLib char buf[BufferSize]; int length; -#if defined(HAVE_SNPRINTF) +#if defined(HAVE_VSNPRINTF) length = vsnprintf(buf, BufferSize, format, args); -#elif defined(HAVE_SPRINTF_S) +#elif defined(HAVE_VSPRINTF_S) length = vsprintf_s(buf, format, args); @@ -191,20 +205,9 @@ namespace TagLib return String::null; } -#ifdef SYSTEM_BYTEORDER - -# if SYSTEM_BYTEORDER == 1 - - const ByteOrder SystemByteOrder = LittleEndian; - -# else - - const ByteOrder SystemByteOrder = BigEndian; - -# endif - -#else - + /*! + * Returns the integer byte order of the system. + */ inline ByteOrder systemByteOrder() { union { @@ -219,41 +222,26 @@ namespace TagLib return BigEndian; } - const ByteOrder SystemByteOrder = systemByteOrder(); - -#endif - -#ifdef FLOAT_BYTEORDER - -# if FLOAT_BYTEORDER == 1 - - const ByteOrder FloatByteOrder = LittleEndian; - -# else - - const ByteOrder FloatByteOrder = BigEndian; - -# endif - -#else - + /*! + * Returns the IEEE754 byte order of the system. + */ inline ByteOrder floatByteOrder() { - double bin[] = { - // "*TAGLIB*" encoded as a little-endian floating-point number - (double) 3.9865557444897601e-105, (double) 0.0 - }; + union { + double d; + char c; + } u; - char *str = (char*)&bin[0]; - if(strncmp(&str[1], "TAGLIB", 6) == 0) - return LittleEndian; - else - return BigEndian; + // 1.0 is stored in memory like 0x3FF0000000000000 in canonical form. + // So the first byte is zero if little endian. + + u.d = 1.0; + if(u.c == 0) + return LittleEndian; + else + return BigEndian; } - const ByteOrder FloatByteOrder = floatByteOrder(); - -#endif } } diff --git a/taglib/trueaudio/trueaudiofile.cpp b/taglib/trueaudio/trueaudiofile.cpp index 5c2aca1b..119cb0c2 100644 --- a/taglib/trueaudio/trueaudiofile.cpp +++ b/taglib/trueaudio/trueaudiofile.cpp @@ -44,6 +44,8 @@ using namespace TagLib; namespace { enum { TrueAudioID3v2Index = 0, TrueAudioID3v1Index = 1 }; + + const TagLib::uint HeaderSize = 18; } class TrueAudio::File::FilePrivate @@ -84,38 +86,38 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -TrueAudio::File::File(FileName file, bool readProperties, - AudioProperties::ReadStyle propertiesStyle) : TagLib::File(file) +TrueAudio::File::File(FileName file, bool readProperties, AudioProperties::ReadStyle) : + TagLib::File(file), + d(new FilePrivate()) { - d = new FilePrivate; if(isOpen()) - read(readProperties, propertiesStyle); + read(readProperties); } TrueAudio::File::File(FileName file, ID3v2::FrameFactory *frameFactory, - bool readProperties, AudioProperties::ReadStyle propertiesStyle) : - TagLib::File(file) + bool readProperties, AudioProperties::ReadStyle) : + TagLib::File(file), + d(new FilePrivate(frameFactory)) { - d = new FilePrivate(frameFactory); if(isOpen()) - read(readProperties, propertiesStyle); + read(readProperties); } -TrueAudio::File::File(IOStream *stream, bool readProperties, - AudioProperties::ReadStyle propertiesStyle) : TagLib::File(stream) +TrueAudio::File::File(IOStream *stream, bool readProperties, AudioProperties::ReadStyle) : + TagLib::File(stream), + d(new FilePrivate()) { - d = new FilePrivate; if(isOpen()) - read(readProperties, propertiesStyle); + read(readProperties); } TrueAudio::File::File(IOStream *stream, ID3v2::FrameFactory *frameFactory, - bool readProperties, AudioProperties::ReadStyle propertiesStyle) : - TagLib::File(stream) + bool readProperties, AudioProperties::ReadStyle) : + TagLib::File(stream), + d(new FilePrivate(frameFactory)) { - d = new FilePrivate(frameFactory); if(isOpen()) - read(readProperties, propertiesStyle); + read(readProperties); } TrueAudio::File::~File() @@ -229,12 +231,11 @@ bool TrueAudio::File::hasID3v2Tag() const return d->hasID3v2; } - //////////////////////////////////////////////////////////////////////////////// // private members //////////////////////////////////////////////////////////////////////////////// -void TrueAudio::File::read(bool readProperties, AudioProperties::ReadStyle /* propertiesStyle */) +void TrueAudio::File::read(bool readProperties) { // Look for an ID3v2 tag @@ -267,14 +268,23 @@ void TrueAudio::File::read(bool readProperties, AudioProperties::ReadStyle /* pr // Look for TrueAudio metadata if(readProperties) { - if(d->ID3v2Location >= 0) { + + offset_t streamLength; + + if(d->hasID3v1) + streamLength = d->ID3v1Location; + else + streamLength = length(); + + if(d->hasID3v2) { seek(d->ID3v2Location + d->ID3v2OriginalSize); - d->properties = new AudioProperties(this, length() - d->ID3v2OriginalSize); + streamLength -= (d->ID3v2Location + d->ID3v2OriginalSize); } else { seek(0); - d->properties = new AudioProperties(this, length()); } + + d->properties = new AudioProperties(readBlock(HeaderSize), streamLength); } } diff --git a/taglib/trueaudio/trueaudiofile.h b/taglib/trueaudio/trueaudiofile.h index c3b7118f..bfdd163a 100644 --- a/taglib/trueaudio/trueaudiofile.h +++ b/taglib/trueaudio/trueaudiofile.h @@ -79,7 +79,7 @@ namespace TagLib { }; /*! - * Constructs a TrueAudio file from \a file. If \a readProperties is true + * Constructs a TrueAudio 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. @@ -88,7 +88,7 @@ namespace TagLib { AudioProperties::ReadStyle propertiesStyle = AudioProperties::Average); /*! - * Constructs a TrueAudio file from \a file. If \a readProperties is true + * Constructs a TrueAudio file from \a file. If \a readProperties is true * the file's audio properties will also be read. * * If this file contains and ID3v2 tag the frames will be created using @@ -113,7 +113,7 @@ namespace TagLib { AudioProperties::ReadStyle propertiesStyle = AudioProperties::Average); /*! - * Constructs a TrueAudio file from \a stream. If \a readProperties is true + * Constructs a TrueAudio 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 @@ -155,6 +155,7 @@ namespace TagLib { * Set the ID3v2::FrameFactory to something other than the default. * * \see ID3v2FrameFactory + * \deprecated This value should be passed in via the constructor */ void setID3v2FrameFactory(const ID3v2::FrameFactory *factory); @@ -170,8 +171,8 @@ namespace TagLib { * if there is no valid ID3v1 tag. If \a create is true it will create * an ID3v1 tag if one does not exist and returns a valid pointer. * - * \note This may return a valid pointer regardless of whether or not the - * file on disk has an ID3v1 tag. Use hasID3v1Tag() to check if the file + * \note This may return a valid pointer regardless of whether or not the + * file on disk has an ID3v1 tag. Use hasID3v1Tag() to check if the file * on disk actually has an ID3v1 tag. * * \note The Tag is still owned by the MPEG::File and should not be @@ -189,8 +190,8 @@ namespace TagLib { * if there is no valid ID3v2 tag. If \a create is true it will create * an ID3v2 tag if one does not exist and returns a valid pointer. * - * \note This may return a valid pointer regardless of whether or not the - * file on disk has an ID3v2 tag. Use hasID3v2Tag() to check if the file + * \note This may return 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. * * \note The Tag is still owned by the MPEG::File and should not be @@ -210,7 +211,7 @@ namespace TagLib { * \note In order to make the removal permanent save() still needs to be called */ void strip(int tags = AllTags); - + /*! * Returns whether or not the file on disk actually has an ID3v1 tag. * @@ -224,13 +225,12 @@ namespace TagLib { * \see ID3v2Tag() */ bool hasID3v2Tag() const; - + private: File(const File &); File &operator=(const File &); - void read(bool readProperties, AudioProperties::ReadStyle propertiesStyle); - void scan(); + void read(bool readProperties); offset_t findID3v1(); offset_t findID3v2(); diff --git a/taglib/trueaudio/trueaudioproperties.cpp b/taglib/trueaudio/trueaudioproperties.cpp index 0d5eab0c..91e45801 100644 --- a/taglib/trueaudio/trueaudioproperties.cpp +++ b/taglib/trueaudio/trueaudioproperties.cpp @@ -60,10 +60,11 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -TrueAudio::AudioProperties::AudioProperties(File *file, offset_t streamLength, ReadStyle style) : +TrueAudio::AudioProperties::AudioProperties(const ByteVector &data, offset_t streamLength, ReadStyle) : + TagLib::AudioProperties(), d(new PropertiesPrivate()) { - read(file, streamLength); + read(data, streamLength); } TrueAudio::AudioProperties::~AudioProperties() @@ -72,6 +73,16 @@ TrueAudio::AudioProperties::~AudioProperties() } int TrueAudio::AudioProperties::length() const +{ + return lengthInSeconds(); +} + +int TrueAudio::AudioProperties::lengthInSeconds() const +{ + return d->length / 1000; +} + +int TrueAudio::AudioProperties::lengthInMilliseconds() const { return d->length; } @@ -110,11 +121,17 @@ int TrueAudio::AudioProperties::ttaVersion() const // private members //////////////////////////////////////////////////////////////////////////////// -void TrueAudio::AudioProperties::read(File *file, offset_t streamLength) +void TrueAudio::AudioProperties::read(const ByteVector &data, offset_t streamLength) { - const ByteVector data = file->readBlock(18); - if(!data.startsWith("TTA")) + if(data.size() < 4) { + debug("TrueAudio::Properties::read() -- data is too short."); return; + } + + if(!data.startsWith("TTA")) { + debug("TrueAudio::Properties::read() -- invalid header signature."); + return; + } size_t pos = 3; @@ -124,21 +141,30 @@ void TrueAudio::AudioProperties::read(File *file, offset_t streamLength) // According to http://en.true-audio.com/TTA_Lossless_Audio_Codec_-_Format_Description // TTA2 headers are in development, and have a different format if(1 == d->version) { + if(data.size() < 18) { + debug("TrueAudio::Properties::read() -- data is too short."); + return; + } + // Skip the audio format pos += 2; - d->channels = data.toInt16LE(pos); + d->channels = data.toUInt16LE(pos); pos += 2; - d->bitsPerSample = data.toInt16LE(pos); + d->bitsPerSample = data.toUInt16LE(pos); pos += 2; d->sampleRate = data.toUInt32LE(pos); pos += 4; d->sampleFrames = data.toUInt32LE(pos); - d->length = d->sampleRate > 0 ? d->sampleFrames / d->sampleRate : 0; + pos += 4; - d->bitrate = d->length > 0 ? static_cast(streamLength * 8L / d->length / 1000) : 0; + if(d->sampleFrames > 0 && d->sampleRate > 0) { + const double length = d->sampleFrames * 1000.0 / d->sampleRate; + d->length = static_cast(length + 0.5); + d->bitrate = static_cast(streamLength * 8.0 / length + 0.5); + } } } diff --git a/taglib/trueaudio/trueaudioproperties.h b/taglib/trueaudio/trueaudioproperties.h index 7f2c74cf..3a92ce2d 100644 --- a/taglib/trueaudio/trueaudioproperties.h +++ b/taglib/trueaudio/trueaudioproperties.h @@ -49,25 +49,60 @@ namespace TagLib { { public: /*! - * Creates an instance of TrueAudio::AudioProperties with the data read from + * Creates an instance of TrueAudio::AudioProperties with the data read from * the ByteVector \a data. */ - AudioProperties(File *file, offset_t streamLength, ReadStyle style = Average); + AudioProperties(const ByteVector &data, offset_t streamLength, ReadStyle style = Average); /*! * Destroys this TrueAudio::AudioProperties instance. */ virtual ~AudioProperties(); - // Reimplementations. - + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \note This method is just an alias of lengthInSeconds(). + * + * \deprecated + */ virtual int length() const; + + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \see lengthInMilliseconds() + */ + // BIC: make virtual + int lengthInSeconds() const; + + /*! + * Returns the length of the file in milliseconds. + * + * \see lengthInSeconds() + */ + // BIC: make virtual + int lengthInMilliseconds() const; + + /*! + * Returns the average bit rate of the file in kb/s. + */ virtual int bitrate() const; + + /*! + * Returns the sample rate in Hz. + */ virtual int sampleRate() const; + + /*! + * Returns the number of audio channels. + */ virtual int channels() const; /*! - * Returns number of bits per sample. + * Returns the number of bits per audio sample. */ int bitsPerSample() const; @@ -82,7 +117,7 @@ namespace TagLib { int ttaVersion() const; private: - void read(File *file, offset_t streamLength); + void read(const ByteVector &data, offset_t streamLength); class PropertiesPrivate; PropertiesPrivate *d; diff --git a/taglib/wavpack/wavpackfile.cpp b/taglib/wavpack/wavpackfile.cpp index a560e90d..66b51e67 100644 --- a/taglib/wavpack/wavpackfile.cpp +++ b/taglib/wavpack/wavpackfile.cpp @@ -82,20 +82,20 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -WavPack::File::File(FileName file, bool readProperties, - AudioProperties::ReadStyle propertiesStyle) : TagLib::File(file) +WavPack::File::File(FileName file, bool readProperties, AudioProperties::ReadStyle) : + TagLib::File(file), + d(new FilePrivate()) { - d = new FilePrivate; if(isOpen()) - read(readProperties, propertiesStyle); + read(readProperties); } -WavPack::File::File(IOStream *stream, bool readProperties, - AudioProperties::ReadStyle propertiesStyle) : TagLib::File(stream) +WavPack::File::File(IOStream *stream, bool readProperties, AudioProperties::ReadStyle) : + TagLib::File(stream), + d(new FilePrivate()) { - d = new FilePrivate; if(isOpen()) - read(readProperties, propertiesStyle); + read(readProperties); } WavPack::File::~File() @@ -228,7 +228,7 @@ bool WavPack::File::hasAPETag() const // private members //////////////////////////////////////////////////////////////////////////////// -void WavPack::File::read(bool readProperties, AudioProperties::ReadStyle /* propertiesStyle */) +void WavPack::File::read(bool readProperties) { // Look for an ID3v1 tag @@ -256,8 +256,17 @@ void WavPack::File::read(bool readProperties, AudioProperties::ReadStyle /* prop // Look for WavPack audio properties if(readProperties) { - seek(0); - d->properties = new AudioProperties(this, length() - d->APESize); + + offset_t streamLength; + + if(d->hasAPE) + streamLength = d->APELocation; + else if(d->hasID3v1) + streamLength = d->ID3v1Location; + else + streamLength = length(); + + d->properties = new AudioProperties(this, streamLength); } } diff --git a/taglib/wavpack/wavpackfile.h b/taglib/wavpack/wavpackfile.h index 6faa1580..2e0c7ddc 100644 --- a/taglib/wavpack/wavpackfile.h +++ b/taglib/wavpack/wavpackfile.h @@ -124,6 +124,11 @@ namespace TagLib { /*! * Saves the file. + * + * This returns true if the save was successful. + * + * \warning In the current implementation, it's dangerous to call save() + * repeatedly. At worst it will corrupt the file. */ virtual bool save(); @@ -134,8 +139,8 @@ namespace TagLib { * if there is no valid ID3v1 tag. If \a create is true it will create * an ID3v1 tag if one does not exist and returns a valid pointer. * - * \note This may return a valid pointer regardless of whether or not the - * file on disk has an ID3v1 tag. Use hasID3v1Tag() to check if the file + * \note This may return a valid pointer regardless of whether or not the + * file on disk has an ID3v1 tag. Use hasID3v1Tag() to check if the file * on disk actually has an ID3v1 tag. * * \note The Tag is still owned by the MPEG::File and should not be @@ -153,8 +158,8 @@ namespace TagLib { * if there is no valid APE tag. If \a create is true it will create * an APE tag if one does not exist and returns a valid pointer. * - * \note This may return a valid pointer regardless of whether or not the - * file on disk has an APE tag. Use hasAPETag() to check if the file + * \note This may return a valid pointer regardless of whether or not the + * file on disk has an APE tag. Use hasAPETag() to check if the file * on disk actually has an APE tag. * * \note The Tag is still owned by the MPEG::File and should not be @@ -174,7 +179,7 @@ namespace TagLib { * \note In order to make the removal permanent save() still needs to be called */ void strip(int tags = AllTags); - + /*! * Returns whether or not the file on disk actually has an ID3v1 tag. * @@ -188,13 +193,12 @@ namespace TagLib { * \see APETag() */ bool hasAPETag() const; - + private: File(const File &); File &operator=(const File &); - void read(bool readProperties, AudioProperties::ReadStyle propertiesStyle); - void scan(); + void read(bool readProperties); offset_t findID3v1(); offset_t findAPE(); diff --git a/taglib/wavpack/wavpackproperties.cpp b/taglib/wavpack/wavpackproperties.cpp index 941d7281..6d540b5f 100644 --- a/taglib/wavpack/wavpackproperties.cpp +++ b/taglib/wavpack/wavpackproperties.cpp @@ -33,6 +33,9 @@ #include "wavpackproperties.h" #include "wavpackfile.h" +// Implementation of this class is based on the information at: +// http://www.wavpack.com/file_format.txt + using namespace TagLib; class WavPack::AudioProperties::PropertiesPrivate @@ -45,6 +48,7 @@ public: channels(0), version(0), bitsPerSample(0), + lossless(false), sampleFrames(0) {} int length; @@ -53,6 +57,7 @@ public: int channels; int version; int bitsPerSample; + bool lossless; uint sampleFrames; }; @@ -60,10 +65,11 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -WavPack::AudioProperties::AudioProperties(File *file, offset_t streamLength, ReadStyle style) : +WavPack::AudioProperties::AudioProperties(File *file, offset_t streamLength, ReadStyle) : + TagLib::AudioProperties(), d(new PropertiesPrivate()) { - read(file, streamLength, style); + read(file, streamLength); } WavPack::AudioProperties::~AudioProperties() @@ -72,6 +78,16 @@ WavPack::AudioProperties::~AudioProperties() } int WavPack::AudioProperties::length() const +{ + return lengthInSeconds(); +} + +int WavPack::AudioProperties::lengthInSeconds() const +{ + return d->length / 1000; +} + +int WavPack::AudioProperties::lengthInMilliseconds() const { return d->length; } @@ -101,6 +117,11 @@ int WavPack::AudioProperties::bitsPerSample() const return d->bitsPerSample; } +bool WavPack::AudioProperties::isLossless() const +{ + return d->lossless; +} + TagLib::uint WavPack::AudioProperties::sampleFrames() const { return d->sampleFrames; @@ -112,13 +133,14 @@ TagLib::uint WavPack::AudioProperties::sampleFrames() const namespace { - static const unsigned int sample_rates[] = { - 6000, 8000, 9600, 11025, 12000, 16000, 22050, 24000, + 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 SHIFT_LSB 13 #define SHIFT_MASK (0x1fL << SHIFT_LSB) @@ -131,61 +153,77 @@ namespace #define FINAL_BLOCK 0x1000 -void WavPack::AudioProperties::read(File *file, offset_t streamLength, ReadStyle style) +void WavPack::AudioProperties::read(File *file, offset_t streamLength) { - const ByteVector data = file->readBlock(32); - if(!data.startsWith("wvpk")) - return; + long offset = 0; - d->version = data.toInt16LE(8); - if(d->version < MIN_STREAM_VERS || d->version > MAX_STREAM_VERS) - return; + while(true) { + file->seek(offset); + const ByteVector data = file->readBlock(32); + + if(data.size() < 32) { + debug("WavPack::Properties::read() -- data is too short."); + break; + } + + if(!data.startsWith("wvpk")) { + debug("WavPack::Properties::read() -- Block header not found."); + break; + } + + const uint flags = data.toUInt32LE(24); + + if(offset == 0) { + d->version = data.toUInt16LE(8); + 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.toUInt32LE(12); + } + + d->channels += (flags & MONO_FLAG) ? 1 : 2; + + if(flags & FINAL_BLOCK) + break; + + const uint blockSize = data.toUInt32LE(4); + offset += blockSize + 8; + } + + if(d->sampleFrames == ~0u) + d->sampleFrames = seekFinalIndex(file, streamLength); + + if(d->sampleFrames > 0 && d->sampleRate > 0) { + const double length = d->sampleFrames * 1000.0 / d->sampleRate; + d->length = static_cast(length + 0.5); + d->bitrate = static_cast(streamLength * 8.0 / length + 0.5); + } +} + +TagLib::uint WavPack::AudioProperties::seekFinalIndex(File *file, offset_t streamLength) +{ + const offset_t offset = file->rfind("wvpk", streamLength); + if(offset == -1) + return 0; + + file->seek(offset); + const ByteVector data = file->readBlock(32); + if(data.size() < 32) + return 0; + + const int version = data.toUInt16LE(8); + if(version < MIN_STREAM_VERS || version > MAX_STREAM_VERS) + return 0; const uint flags = data.toUInt32LE(24); - d->bitsPerSample = ((flags & BYTES_STORED) + 1) * 8 - - ((flags & SHIFT_MASK) >> SHIFT_LSB); - d->sampleRate = sample_rates[(flags & SRATE_MASK) >> SRATE_LSB]; - d->channels = (flags & MONO_FLAG) ? 1 : 2; + if(!(flags & FINAL_BLOCK)) + return 0; - uint samples = data.toUInt32LE(12); - if(samples == ~0u) { - if(style != Fast) { - samples = seekFinalIndex(file, streamLength); - } - else { - samples = 0; - } - } - d->length = d->sampleRate > 0 ? (samples + (d->sampleRate / 2)) / d->sampleRate : 0; - d->sampleFrames = samples; + const uint blockIndex = data.toUInt32LE(16); + const uint blockSamples = data.toUInt32LE(20); - d->bitrate = d->length > 0 ? static_cast(streamLength * 8L / d->length / 1000) : 0; + return blockIndex + blockSamples; } - -unsigned int WavPack::AudioProperties::seekFinalIndex(File *file, offset_t streamLength) -{ - ByteVector blockID("wvpk", 4); - - offset_t offset = streamLength; - while(offset > 0) { - offset = file->rfind(blockID, offset); - if(offset == -1) - return 0; - file->seek(offset); - ByteVector data = file->readBlock(32); - if(data.size() != 32) - return 0; - const int version = data.toInt16LE(8); - if(version < MIN_STREAM_VERS || version > MAX_STREAM_VERS) - continue; - const uint flags = data.toUInt32LE(24); - if(!(flags & FINAL_BLOCK)) - return 0; - const uint blockIndex = data.toUInt32LE(16); - const uint blockSamples = data.toUInt32LE(20); - return blockIndex + blockSamples; - } - - return 0; -} - diff --git a/taglib/wavpack/wavpackproperties.h b/taglib/wavpack/wavpackproperties.h index a02720e0..e62980ea 100644 --- a/taglib/wavpack/wavpackproperties.h +++ b/taglib/wavpack/wavpackproperties.h @@ -61,22 +61,61 @@ namespace TagLib { */ virtual ~AudioProperties(); - // Reimplementations. - + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \note This method is just an alias of lengthInSeconds(). + * + * \deprecated + */ virtual int length() const; + + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \see lengthInMilliseconds() + */ + // BIC: make virtual + int lengthInSeconds() const; + + /*! + * Returns the length of the file in milliseconds. + * + * \see lengthInSeconds() + */ + // BIC: make virtual + int lengthInMilliseconds() const; + + /*! + * Returns the average bit rate of the file in kb/s. + */ virtual int bitrate() const; /*! * Returns the sample rate in Hz. 0 means unknown or custom. */ virtual int sampleRate() const; - + + /*! + * Returns the number of audio channels. + */ virtual int channels() const; /*! - * Returns number of bits per sample. + * Returns the number of bits per audio sample. */ int bitsPerSample() const; + + /*! + * Returns whether or not the file is lossless encoded. + */ + bool isLossless() const; + + /*! + * Returns the total number of audio samples in file. + */ uint sampleFrames() const; /*! @@ -85,8 +124,8 @@ namespace TagLib { int version() const; private: - void read(File *file, offset_t streamLength, ReadStyle style); - unsigned int seekFinalIndex(File *file, offset_t streamLength); + void read(File *file, offset_t streamLength); + uint seekFinalIndex(File *file, offset_t streamLength); class PropertiesPrivate; PropertiesPrivate *d; diff --git a/taglib/xm/xmproperties.cpp b/taglib/xm/xmproperties.cpp index 60fd12ff..1610ebf8 100644 --- a/taglib/xm/xmproperties.cpp +++ b/taglib/xm/xmproperties.cpp @@ -55,7 +55,8 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -XM::AudioProperties::AudioProperties(AudioProperties::ReadStyle propertiesStyle) : +XM::AudioProperties::AudioProperties(AudioProperties::ReadStyle) : + TagLib::AudioProperties(), d(new PropertiesPrivate()) { } @@ -70,6 +71,16 @@ int XM::AudioProperties::length() const return 0; } +int XM::AudioProperties::lengthInSeconds() const +{ + return 0; +} + +int XM::AudioProperties::lengthInMilliseconds() const +{ + return 0; +} + int XM::AudioProperties::bitrate() const { return 0; diff --git a/taglib/xm/xmproperties.h b/taglib/xm/xmproperties.h index d956f307..587386fc 100644 --- a/taglib/xm/xmproperties.h +++ b/taglib/xm/xmproperties.h @@ -32,10 +32,8 @@ namespace TagLib { class File; - class AudioProperties : public TagLib::AudioProperties + class TAGLIB_EXPORT AudioProperties : public TagLib::AudioProperties { - friend class File; - public: /*! Flag bits. */ enum { @@ -45,10 +43,12 @@ namespace TagLib { AudioProperties(AudioProperties::ReadStyle propertiesStyle); virtual ~AudioProperties(); - int length() const; - int bitrate() const; - int sampleRate() const; - int channels() const; + int length() const; + int lengthInSeconds() const; + int lengthInMilliseconds() const; + int bitrate() const; + int sampleRate() const; + int channels() const; ushort lengthInPatterns() const; ushort version() const; @@ -73,6 +73,8 @@ namespace TagLib { void setTempo(ushort tempo); void setBpmSpeed(ushort bpmSpeed); + friend class File; + class PropertiesPrivate; PropertiesPrivate *d; }; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index a9b3f813..46bc9aec 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -42,6 +42,7 @@ SET(test_runner_SRCS test_bytevectorstream.cpp test_string.cpp test_propertymap.cpp + test_file.cpp test_fileref.cpp test_id3v1.cpp test_id3v2.cpp @@ -68,6 +69,7 @@ SET(test_runner_SRCS test_xm.cpp test_mpc.cpp test_opus.cpp + test_speex.cpp test_dsf.cpp test_matroska.cpp ) diff --git a/tests/data/alaw.wav b/tests/data/alaw.wav index 754ed856..cf548eff 100644 Binary files a/tests/data/alaw.wav and b/tests/data/alaw.wav differ diff --git a/tests/data/ape-id3v1.mp3 b/tests/data/ape-id3v1.mp3 new file mode 100644 index 00000000..a761d6ca Binary files /dev/null and b/tests/data/ape-id3v1.mp3 differ diff --git a/tests/data/ape-id3v2.mp3 b/tests/data/ape-id3v2.mp3 new file mode 100644 index 00000000..72c1291d Binary files /dev/null and b/tests/data/ape-id3v2.mp3 differ diff --git a/tests/data/ape.mp3 b/tests/data/ape.mp3 new file mode 100644 index 00000000..a17e2699 Binary files /dev/null and b/tests/data/ape.mp3 differ diff --git a/tests/data/bladeenc.mp3 b/tests/data/bladeenc.mp3 new file mode 100644 index 00000000..e3d1a4b5 Binary files /dev/null and b/tests/data/bladeenc.mp3 differ diff --git a/tests/data/duplicate_id3v2.aiff b/tests/data/duplicate_id3v2.aiff new file mode 100644 index 00000000..6703583f Binary files /dev/null and b/tests/data/duplicate_id3v2.aiff differ diff --git a/tests/data/duplicate_tags.wav b/tests/data/duplicate_tags.wav new file mode 100644 index 00000000..b9865bbd Binary files /dev/null and b/tests/data/duplicate_tags.wav differ diff --git a/tests/data/float64.wav b/tests/data/float64.wav new file mode 100644 index 00000000..d34f692b Binary files /dev/null and b/tests/data/float64.wav differ diff --git a/tests/data/four_channels.wv b/tests/data/four_channels.wv new file mode 100644 index 00000000..de682f24 Binary files /dev/null and b/tests/data/four_channels.wv differ diff --git a/tests/data/infloop.m4a b/tests/data/infloop.m4a new file mode 100644 index 00000000..bbf76db8 Binary files /dev/null and b/tests/data/infloop.m4a differ diff --git a/tests/data/infloop.wv b/tests/data/infloop.wv new file mode 100644 index 00000000..d8c720cf Binary files /dev/null and b/tests/data/infloop.wv differ diff --git a/tests/data/lame_cbr.mp3 b/tests/data/lame_cbr.mp3 new file mode 100644 index 00000000..b7badeb0 Binary files /dev/null and b/tests/data/lame_cbr.mp3 differ diff --git a/tests/data/lame_vbr.mp3 b/tests/data/lame_vbr.mp3 new file mode 100644 index 00000000..643056ef Binary files /dev/null and b/tests/data/lame_vbr.mp3 differ diff --git a/tests/data/lossless.wma b/tests/data/lossless.wma new file mode 100644 index 00000000..e29befcc Binary files /dev/null and b/tests/data/lossless.wma differ diff --git a/tests/data/mac-399-id3v2.ape b/tests/data/mac-399-id3v2.ape new file mode 100644 index 00000000..2ea97fc4 Binary files /dev/null and b/tests/data/mac-399-id3v2.ape differ diff --git a/tests/data/mac-399-tagged.ape b/tests/data/mac-399-tagged.ape new file mode 100644 index 00000000..3f5a656e Binary files /dev/null and b/tests/data/mac-399-tagged.ape differ diff --git a/tests/data/mac-399.ape b/tests/data/mac-399.ape index ae895ba2..3b0661ee 100644 Binary files a/tests/data/mac-399.ape and b/tests/data/mac-399.ape differ diff --git a/tests/data/sinewave.flac b/tests/data/sinewave.flac new file mode 100644 index 00000000..25d31b2d Binary files /dev/null and b/tests/data/sinewave.flac differ diff --git a/tests/data/tagged.tta b/tests/data/tagged.tta new file mode 100644 index 00000000..1677a7ed Binary files /dev/null and b/tests/data/tagged.tta differ diff --git a/tests/data/tagged.wv b/tests/data/tagged.wv new file mode 100644 index 00000000..333f8687 Binary files /dev/null and b/tests/data/tagged.wv differ diff --git a/tests/data/vbri.mp3 b/tests/data/vbri.mp3 new file mode 100644 index 00000000..ea14d61e Binary files /dev/null and b/tests/data/vbri.mp3 differ diff --git a/tests/data/zero-sized-padding.flac b/tests/data/zero-sized-padding.flac new file mode 100644 index 00000000..86ab8bf7 Binary files /dev/null and b/tests/data/zero-sized-padding.flac differ diff --git a/tests/main.cpp b/tests/main.cpp index ab89dc3e..c83c9d13 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -17,7 +17,7 @@ int main(int argc, char* argv[]) // Create the event manager and test controller CppUnit::TestResult controller; - // Add a listener that colllects test result + // Add a listener that collects test result CppUnit::TestResultCollector result; controller.addListener(&result); @@ -39,12 +39,20 @@ int main(int argc, char* argv[]) CppUnit::CompilerOutputter outputter(&result, std::cerr); outputter.write(); +#if defined(_MSC_VER) && _MSC_VER > 1500 + char *xml = NULL; + ::_dupenv_s(&xml, NULL, "CPPUNIT_XML"); +#else char *xml = ::getenv("CPPUNIT_XML"); +#endif if(xml && !::strcmp(xml, "1")) { std::ofstream xmlfileout("cpptestresults.xml"); CppUnit::XmlOutputter xmlout(&result, xmlfileout); xmlout.write(); } +#if defined(_MSC_VER) && _MSC_VER > 1500 + ::free(xml); +#endif } catch(std::invalid_argument &e){ std::cerr << std::endl diff --git a/tests/test_aiff.cpp b/tests/test_aiff.cpp index 4df8d893..386c7686 100644 --- a/tests/test_aiff.cpp +++ b/tests/test_aiff.cpp @@ -12,19 +12,48 @@ using namespace TagLib; class TestAIFF : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(TestAIFF); - CPPUNIT_TEST(testReading); - CPPUNIT_TEST(testSaveID3v2); + CPPUNIT_TEST(testAiffProperties); CPPUNIT_TEST(testAiffCProperties); + CPPUNIT_TEST(testSaveID3v2); + CPPUNIT_TEST(testDuplicateID3v2); CPPUNIT_TEST(testFuzzedFile1); CPPUNIT_TEST(testFuzzedFile2); CPPUNIT_TEST_SUITE_END(); public: - void testReading() + void testAiffProperties() { RIFF::AIFF::File f(TEST_FILE_PATH_C("empty.aiff")); - CPPUNIT_ASSERT_EQUAL(705, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(67, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(706, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->sampleWidth()); + CPPUNIT_ASSERT_EQUAL(2941U, f.audioProperties()->sampleFrames()); + CPPUNIT_ASSERT_EQUAL(false, f.audioProperties()->isAiffC()); + } + + void testAiffCProperties() + { + RIFF::AIFF::File f(TEST_FILE_PATH_C("alaw.aifc")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(37, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(355, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->sampleWidth()); + CPPUNIT_ASSERT_EQUAL(1622U, f.audioProperties()->sampleFrames()); + CPPUNIT_ASSERT_EQUAL(true, f.audioProperties()->isAiffC()); + CPPUNIT_ASSERT_EQUAL(ByteVector("ALAW"), f.audioProperties()->compressionType()); + CPPUNIT_ASSERT_EQUAL(String("SGI CCITT G.711 A-law"), f.audioProperties()->compressionName()); } void testSaveID3v2() @@ -46,12 +75,15 @@ public: } } - void testAiffCProperties() + void testDuplicateID3v2() { - RIFF::AIFF::File f(TEST_FILE_PATH_C("alaw.aifc")); - CPPUNIT_ASSERT(f.audioProperties()->isAiffC()); - CPPUNIT_ASSERT(f.audioProperties()->compressionType() == "ALAW"); - CPPUNIT_ASSERT(f.audioProperties()->compressionName() == "SGI CCITT G.711 A-law"); + RIFF::AIFF::File f(TEST_FILE_PATH_C("duplicate_id3v2.aiff")); + + // duplicate_id3v2.aiff has duplicate ID3v2 tags. + // title() returns "Title2" if can't skip the second tag. + + CPPUNIT_ASSERT(f.hasID3v2Tag()); + CPPUNIT_ASSERT_EQUAL(String("Title1"), f.tag()->title()); } void testFuzzedFile1() diff --git a/tests/test_ape.cpp b/tests/test_ape.cpp index 2f842f65..3ab3197b 100644 --- a/tests/test_ape.cpp +++ b/tests/test_ape.cpp @@ -14,6 +14,8 @@ class TestAPE : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(TestAPE); CPPUNIT_TEST(testProperties399); + CPPUNIT_TEST(testProperties399Tagged); + CPPUNIT_TEST(testProperties399Id3v2); CPPUNIT_TEST(testProperties396); CPPUNIT_TEST(testProperties390); CPPUNIT_TEST(testFuzzedFile1); @@ -25,28 +27,76 @@ public: void testProperties399() { APE::File f(TEST_FILE_PATH_C("mac-399.ape")); + CPPUNIT_ASSERT(f.audioProperties()); CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->length()); - CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3550, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(192, 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(156556U, f.audioProperties()->sampleFrames()); + CPPUNIT_ASSERT_EQUAL(3990, f.audioProperties()->version()); + } + + void testProperties399Tagged() + { + APE::File f(TEST_FILE_PATH_C("mac-399-tagged.ape")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3550, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(192, 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(156556U, f.audioProperties()->sampleFrames()); + CPPUNIT_ASSERT_EQUAL(3990, f.audioProperties()->version()); + } + + void testProperties399Id3v2() + { + APE::File f(TEST_FILE_PATH_C("mac-399-id3v2.ape")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3550, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(192, 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(156556U, f.audioProperties()->sampleFrames()); + CPPUNIT_ASSERT_EQUAL(3990, f.audioProperties()->version()); } void testProperties396() { APE::File f(TEST_FILE_PATH_C("mac-396.ape")); + CPPUNIT_ASSERT(f.audioProperties()); CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3685, f.audioProperties()->lengthInMilliseconds()); CPPUNIT_ASSERT_EQUAL(0, 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(162496U, f.audioProperties()->sampleFrames()); + CPPUNIT_ASSERT_EQUAL(3960, f.audioProperties()->version()); } void testProperties390() { APE::File f(TEST_FILE_PATH_C("mac-390-hdr.ape")); + CPPUNIT_ASSERT(f.audioProperties()); CPPUNIT_ASSERT_EQUAL(15, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(15, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(15630, f.audioProperties()->lengthInMilliseconds()); CPPUNIT_ASSERT_EQUAL(0, 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(689262U, f.audioProperties()->sampleFrames()); + CPPUNIT_ASSERT_EQUAL(3900, f.audioProperties()->version()); } void testFuzzedFile1() diff --git a/tests/test_asf.cpp b/tests/test_asf.cpp index 2876bfea..465e5a73 100644 --- a/tests/test_asf.cpp +++ b/tests/test_asf.cpp @@ -15,6 +15,7 @@ class TestASF : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(TestASF); CPPUNIT_TEST(testAudioProperties); + CPPUNIT_TEST(testLosslessProperties); CPPUNIT_TEST(testRead); CPPUNIT_TEST(testSaveMultipleValues); CPPUNIT_TEST(testSaveStream); @@ -31,10 +32,35 @@ public: void testAudioProperties() { ASF::File f(TEST_FILE_PATH_C("silence-1.wma")); - CPPUNIT_ASSERT_EQUAL(4, f.audioProperties()->length()); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3712, f.audioProperties()->lengthInMilliseconds()); CPPUNIT_ASSERT_EQUAL(64, f.audioProperties()->bitrate()); CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); CPPUNIT_ASSERT_EQUAL(48000, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(ASF::AudioProperties::WMA2, f.audioProperties()->codec()); + CPPUNIT_ASSERT_EQUAL(String("Windows Media Audio 9.1"), f.audioProperties()->codecName()); + CPPUNIT_ASSERT_EQUAL(String("64 kbps, 48 kHz, stereo 2-pass CBR"), f.audioProperties()->codecDescription()); + CPPUNIT_ASSERT_EQUAL(false, f.audioProperties()->isEncrypted()); + } + + void testLosslessProperties() + { + ASF::File f(TEST_FILE_PATH_C("lossless.wma")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3549, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(1152, 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(ASF::AudioProperties::WMA9Lossless, f.audioProperties()->codec()); + CPPUNIT_ASSERT_EQUAL(String("Windows Media Audio 9.2 Lossless"), f.audioProperties()->codecName()); + CPPUNIT_ASSERT_EQUAL(String("VBR Quality 100, 44 kHz, 2 channel 16 bit 1-pass VBR"), f.audioProperties()->codecDescription()); + CPPUNIT_ASSERT_EQUAL(false, f.audioProperties()->isEncrypted()); } void testRead() @@ -52,7 +78,7 @@ public: ASF::AttributeList values; values.append("Foo"); values.append("Bar"); - f->tag()->attributeListMap()["WM/AlbumTitle"] = values; + f->tag()->setAttribute("WM/AlbumTitle", values); f->save(); delete f; @@ -67,22 +93,24 @@ public: string newname = copy.fileName(); ASF::File *f = new ASF::File(newname.c_str()); - CPPUNIT_ASSERT(!f->tag()->attributeListMap().contains("WM/TrackNumber")); + CPPUNIT_ASSERT(!f->tag()->contains("WM/TrackNumber")); f->tag()->setAttribute("WM/TrackNumber", (unsigned int)(123)); f->save(); delete f; f = new ASF::File(newname.c_str()); - CPPUNIT_ASSERT(f->tag()->attributeListMap().contains("WM/TrackNumber")); - CPPUNIT_ASSERT_EQUAL(ASF::Attribute::DWordType, f->tag()->attributeListMap()["WM/TrackNumber"].front().type()); + CPPUNIT_ASSERT(f->tag()->contains("WM/TrackNumber")); + CPPUNIT_ASSERT_EQUAL(ASF::Attribute::DWordType, + f->tag()->attribute("WM/TrackNumber").front().type()); CPPUNIT_ASSERT_EQUAL(TagLib::uint(123), f->tag()->track()); f->tag()->setTrack(234); f->save(); delete f; f = new ASF::File(newname.c_str()); - CPPUNIT_ASSERT(f->tag()->attributeListMap().contains("WM/TrackNumber")); - CPPUNIT_ASSERT_EQUAL(ASF::Attribute::UnicodeType, f->tag()->attributeListMap()["WM/TrackNumber"].front().type()); + CPPUNIT_ASSERT(f->tag()->contains("WM/TrackNumber")); + CPPUNIT_ASSERT_EQUAL(ASF::Attribute::UnicodeType, + f->tag()->attribute("WM/TrackNumber").front().type()); CPPUNIT_ASSERT_EQUAL(TagLib::uint(234), f->tag()->track()); delete f; } @@ -93,16 +121,14 @@ public: string newname = copy.fileName(); ASF::File *f = new ASF::File(newname.c_str()); - ASF::AttributeList values; ASF::Attribute attr("Foo"); attr.setStream(43); - values.append(attr); - f->tag()->attributeListMap()["WM/AlbumTitle"] = values; + f->tag()->setAttribute("WM/AlbumTitle", attr); f->save(); delete f; f = new ASF::File(newname.c_str()); - CPPUNIT_ASSERT_EQUAL(43, f->tag()->attributeListMap()["WM/AlbumTitle"][0].stream()); + CPPUNIT_ASSERT_EQUAL(43, f->tag()->attribute("WM/AlbumTitle").front().stream()); delete f; } @@ -112,18 +138,16 @@ public: string newname = copy.fileName(); ASF::File *f = new ASF::File(newname.c_str()); - ASF::AttributeList values; ASF::Attribute attr("Foo"); attr.setStream(32); attr.setLanguage(56); - values.append(attr); - f->tag()->attributeListMap()["WM/AlbumTitle"] = values; + f->tag()->setAttribute("WM/AlbumTitle", attr); f->save(); delete f; f = new ASF::File(newname.c_str()); - CPPUNIT_ASSERT_EQUAL(32, f->tag()->attributeListMap()["WM/AlbumTitle"][0].stream()); - CPPUNIT_ASSERT_EQUAL(56, f->tag()->attributeListMap()["WM/AlbumTitle"][0].language()); + CPPUNIT_ASSERT_EQUAL(32, f->tag()->attribute("WM/AlbumTitle").front().stream()); + CPPUNIT_ASSERT_EQUAL(56, f->tag()->attribute("WM/AlbumTitle").front().language()); delete f; } @@ -133,15 +157,14 @@ public: string newname = copy.fileName(); ASF::File *f = new ASF::File(newname.c_str()); - ASF::AttributeList values; ASF::Attribute attr(ByteVector(70000, 'x')); - values.append(attr); - f->tag()->attributeListMap()["WM/Blob"] = values; + f->tag()->setAttribute("WM/Blob", attr); f->save(); delete f; f = new ASF::File(newname.c_str()); - CPPUNIT_ASSERT_EQUAL(ByteVector(70000, 'x'), f->tag()->attributeListMap()["WM/Blob"][0].toByteVector()); + CPPUNIT_ASSERT_EQUAL(ByteVector(70000, 'x'), + f->tag()->attribute("WM/Blob").front().toByteVector()); delete f; } @@ -151,20 +174,17 @@ public: string newname = copy.fileName(); ASF::File *f = new ASF::File(newname.c_str()); - ASF::AttributeList values; ASF::Picture picture; picture.setMimeType("image/jpeg"); picture.setType(ASF::Picture::FrontCover); picture.setDescription("description"); picture.setPicture("data"); - ASF::Attribute attr(picture); - values.append(attr); - f->tag()->attributeListMap()["WM/Picture"] = values; + f->tag()->setAttribute("WM/Picture", picture); f->save(); delete f; f = new ASF::File(newname.c_str()); - ASF::AttributeList values2 = f->tag()->attributeListMap()["WM/Picture"]; + ASF::AttributeList values2 = f->tag()->attribute("WM/Picture"); CPPUNIT_ASSERT_EQUAL(size_t(1), values2.size()); ASF::Attribute attr2 = values2.front(); ASF::Picture picture2 = attr2.toPicture(); @@ -195,12 +215,12 @@ public: picture2.setDescription("back cover"); picture2.setPicture("PNG data"); values.append(ASF::Attribute(picture2)); - f->tag()->attributeListMap()["WM/Picture"] = values; + f->tag()->setAttribute("WM/Picture", values); f->save(); delete f; f = new ASF::File(newname.c_str()); - ASF::AttributeList values2 = f->tag()->attributeListMap()["WM/Picture"]; + ASF::AttributeList values2 = f->tag()->attribute("WM/Picture"); CPPUNIT_ASSERT_EQUAL(size_t(2), values2.size()); ASF::Picture picture3 = values2[1].toPicture(); CPPUNIT_ASSERT(picture3.isValid()); @@ -220,7 +240,7 @@ public: void testProperties() { ASF::File f(TEST_FILE_PATH_C("silence-1.wma")); - + PropertyMap tags = f.properties(); tags["TRACKNUMBER"] = StringList("2"); @@ -234,19 +254,19 @@ public: CPPUNIT_ASSERT_EQUAL(String("Foo Bar"), f.tag()->artist()); CPPUNIT_ASSERT_EQUAL(StringList("Foo Bar"), tags["ARTIST"]); - CPPUNIT_ASSERT(f.tag()->attributeListMap().contains("WM/BeatsPerMinute")); - CPPUNIT_ASSERT_EQUAL((size_t)1u, f.tag()->attributeListMap()["WM/BeatsPerMinute"].size()); - CPPUNIT_ASSERT_EQUAL(String("123"), f.tag()->attributeListMap()["WM/BeatsPerMinute"].front().toString()); + CPPUNIT_ASSERT(f.tag()->contains("WM/BeatsPerMinute")); + CPPUNIT_ASSERT_EQUAL((size_t)1, f.tag()->attributeListMap()["WM/BeatsPerMinute"].size()); + CPPUNIT_ASSERT_EQUAL(String("123"), f.tag()->attribute("WM/BeatsPerMinute").front().toString()); CPPUNIT_ASSERT_EQUAL(StringList("123"), tags["BPM"]); - CPPUNIT_ASSERT(f.tag()->attributeListMap().contains("WM/TrackNumber")); - CPPUNIT_ASSERT_EQUAL((size_t)1u, f.tag()->attributeListMap()["WM/TrackNumber"].size()); - CPPUNIT_ASSERT_EQUAL(String("2"), f.tag()->attributeListMap()["WM/TrackNumber"].front().toString()); + CPPUNIT_ASSERT(f.tag()->contains("WM/TrackNumber")); + CPPUNIT_ASSERT_EQUAL((size_t)1, f.tag()->attributeListMap()["WM/TrackNumber"].size()); + CPPUNIT_ASSERT_EQUAL(String("2"), f.tag()->attribute("WM/TrackNumber").front().toString()); CPPUNIT_ASSERT_EQUAL(StringList("2"), tags["TRACKNUMBER"]); - CPPUNIT_ASSERT(f.tag()->attributeListMap().contains("WM/PartOfSet")); - CPPUNIT_ASSERT_EQUAL((size_t)1u, f.tag()->attributeListMap()["WM/PartOfSet"].size()); - CPPUNIT_ASSERT_EQUAL(String("3"), f.tag()->attributeListMap()["WM/PartOfSet"].front().toString()); + CPPUNIT_ASSERT(f.tag()->contains("WM/PartOfSet")); + CPPUNIT_ASSERT_EQUAL((size_t)1, f.tag()->attributeListMap()["WM/PartOfSet"].size()); + CPPUNIT_ASSERT_EQUAL(String("3"), f.tag()->attribute("WM/PartOfSet").front().toString()); CPPUNIT_ASSERT_EQUAL(StringList("3"), tags["DISCNUMBER"]); } diff --git a/tests/test_bytevector.cpp b/tests/test_bytevector.cpp index bca51748..5bfbadb9 100644 --- a/tests/test_bytevector.cpp +++ b/tests/test_bytevector.cpp @@ -42,6 +42,7 @@ class TestByteVector : public CppUnit::TestFixture CPPUNIT_TEST(testNumericCoversion); CPPUNIT_TEST(testReplace); CPPUNIT_TEST(testIterator); + CPPUNIT_TEST(testResize); CPPUNIT_TEST_SUITE_END(); public: @@ -303,8 +304,66 @@ public: *it4 = 'A'; CPPUNIT_ASSERT_EQUAL('a', *it3); CPPUNIT_ASSERT_EQUAL('A', *it4); + + ByteVector v3; + v3 = ByteVector("0123456789").mid(3, 4); + + it1 = v3.begin(); + it2 = v3.end() - 1; + CPPUNIT_ASSERT_EQUAL('3', *it1); + CPPUNIT_ASSERT_EQUAL('6', *it2); + + it3 = v3.rbegin(); + it4 = v3.rend() - 1; + CPPUNIT_ASSERT_EQUAL('6', *it3); + CPPUNIT_ASSERT_EQUAL('3', *it4); + } + + void testResize() + { + ByteVector a = ByteVector("0123456789"); + ByteVector b = a.mid(3, 4); + b.resize(6, 'A'); + CPPUNIT_ASSERT_EQUAL(size_t(6), b.size()); + CPPUNIT_ASSERT_EQUAL('6', b[3]); + CPPUNIT_ASSERT_EQUAL('A', b[4]); + CPPUNIT_ASSERT_EQUAL('A', b[5]); + b.resize(10, 'B'); + CPPUNIT_ASSERT_EQUAL(size_t(10), b.size()); + CPPUNIT_ASSERT_EQUAL('6', b[3]); + CPPUNIT_ASSERT_EQUAL('B', b[6]); + CPPUNIT_ASSERT_EQUAL('B', b[9]); + b.resize(3, 'C'); + CPPUNIT_ASSERT_EQUAL(size_t(3), b.size()); + CPPUNIT_ASSERT_EQUAL(ByteVector::npos, b.find('C')); + b.resize(3); + CPPUNIT_ASSERT_EQUAL(size_t(3), b.size()); + + // Check if a and b were properly detached. + + CPPUNIT_ASSERT_EQUAL(size_t(10), a.size()); + CPPUNIT_ASSERT_EQUAL('3', a[3]); + CPPUNIT_ASSERT_EQUAL('5', a[5]); + + // Special case that refCount == 1 and d->offset != 0. + + ByteVector c = ByteVector("0123456789").mid(3, 4); + c.resize(6, 'A'); + CPPUNIT_ASSERT_EQUAL(size_t(6), c.size()); + CPPUNIT_ASSERT_EQUAL('6', c[3]); + CPPUNIT_ASSERT_EQUAL('A', c[4]); + CPPUNIT_ASSERT_EQUAL('A', c[5]); + c.resize(10, 'B'); + CPPUNIT_ASSERT_EQUAL(size_t(10), c.size()); + CPPUNIT_ASSERT_EQUAL('6', c[3]); + CPPUNIT_ASSERT_EQUAL('B', c[6]); + CPPUNIT_ASSERT_EQUAL('B', c[9]); + c.resize(3, 'C'); + CPPUNIT_ASSERT_EQUAL(size_t(3), c.size()); + CPPUNIT_ASSERT_EQUAL(ByteVector::npos, c.find('C')); } }; CPPUNIT_TEST_SUITE_REGISTRATION(TestByteVector); + diff --git a/tests/test_file.cpp b/tests/test_file.cpp new file mode 100644 index 00000000..1ba99042 --- /dev/null +++ b/tests/test_file.cpp @@ -0,0 +1,131 @@ +/*************************************************************************** + copyright : (C) 2014 by Lukas Lalinsky + email : lukas@oxygene.sk + ***************************************************************************/ + +/*************************************************************************** + * 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 * + ***************************************************************************/ + +#include +#include +#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); + CPPUNIT_TEST(testFindInSmallFile); + CPPUNIT_TEST(testRFindInSmallFile); + CPPUNIT_TEST(testSeek); + CPPUNIT_TEST_SUITE_END(); + +public: + + void testFindInSmallFile() + { + ScopedFileCopy copy("empty", ".ogg"); + std::string name = copy.fileName(); + { + PlainFile file(name.c_str()); + file.seek(0); + file.writeBlock(ByteVector("0123456239", 10)); + file.truncate(10); + } + { + PlainFile file(name.c_str()); + CPPUNIT_ASSERT_EQUAL((offset_t)10, file.length()); + + CPPUNIT_ASSERT_EQUAL((offset_t)2, file.find(ByteVector("23", 2))); + CPPUNIT_ASSERT_EQUAL((offset_t)2, file.find(ByteVector("23", 2), 2)); + CPPUNIT_ASSERT_EQUAL((offset_t)7, file.find(ByteVector("23", 2), 3)); + + file.seek(0); + const ByteVector v = file.readBlock(static_cast(file.length())); + CPPUNIT_ASSERT_EQUAL((size_t)10, v.size()); + + CPPUNIT_ASSERT_EQUAL((offset_t)v.find("23"), file.find("23")); + CPPUNIT_ASSERT_EQUAL((offset_t)v.find("23", 2), file.find("23", 2)); + CPPUNIT_ASSERT_EQUAL((offset_t)v.find("23", 3), file.find("23", 3)); + } + } + + void testRFindInSmallFile() + { + ScopedFileCopy copy("empty", ".ogg"); + std::string name = copy.fileName(); + { + PlainFile file(name.c_str()); + file.seek(0); + file.writeBlock(ByteVector("0123456239", 10)); + file.truncate(10); + } + { + PlainFile file(name.c_str()); + CPPUNIT_ASSERT_EQUAL((offset_t)10, file.length()); + + CPPUNIT_ASSERT_EQUAL((offset_t)7, file.rfind(ByteVector("23", 2))); + CPPUNIT_ASSERT_EQUAL((offset_t)7, file.rfind(ByteVector("23", 2), 7)); + CPPUNIT_ASSERT_EQUAL((offset_t)2, file.rfind(ByteVector("23", 2), 6)); + + file.seek(0); + const ByteVector v = file.readBlock(file.length()); + CPPUNIT_ASSERT_EQUAL((size_t)10, v.size()); + + CPPUNIT_ASSERT_EQUAL((offset_t)v.rfind("23"), file.rfind("23")); + CPPUNIT_ASSERT_EQUAL((offset_t)v.rfind("23", 7), file.rfind("23", 7)); + CPPUNIT_ASSERT_EQUAL((offset_t)v.rfind("23", 6), file.rfind("23", 6)); + } + } + + void testSeek() + { + ScopedFileCopy copy("empty", ".ogg"); + std::string name = copy.fileName(); + + PlainFile f(name.c_str()); + CPPUNIT_ASSERT_EQUAL((offset_t)0, f.tell()); + CPPUNIT_ASSERT_EQUAL((offset_t)4328, f.length()); + + f.seek(100, File::Beginning); + CPPUNIT_ASSERT_EQUAL((offset_t)100, f.tell()); + f.seek(100, File::Current); + CPPUNIT_ASSERT_EQUAL((offset_t)200, f.tell()); + f.seek(-300, File::Current); + CPPUNIT_ASSERT_EQUAL((offset_t)200, f.tell()); + + f.seek(-100, File::End); + CPPUNIT_ASSERT_EQUAL((offset_t)4228, f.tell()); + f.seek(-100, File::Current); + CPPUNIT_ASSERT_EQUAL((offset_t)4128, f.tell()); + f.seek(300, File::Current); + CPPUNIT_ASSERT_EQUAL((offset_t)4428, f.tell()); + } + +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestFile); + diff --git a/tests/test_flac.cpp b/tests/test_flac.cpp index c54b298f..9c5b6d14 100644 --- a/tests/test_flac.cpp +++ b/tests/test_flac.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include "utils.h" @@ -25,6 +26,9 @@ class TestFLAC : public CppUnit::TestFixture CPPUNIT_TEST(testSaveMultipleValues); CPPUNIT_TEST(testDict); CPPUNIT_TEST(testInvalid); + CPPUNIT_TEST(testAudioProperties); + CPPUNIT_TEST(testZeroSizedPadding); + CPPUNIT_TEST(testSaveID3v1); CPPUNIT_TEST_SUITE_END(); public: @@ -254,6 +258,55 @@ public: CPPUNIT_ASSERT_EQUAL(size_t(0), f.properties().size()); } + void testAudioProperties() + { + FLAC::File f(TEST_FILE_PATH_C("sinewave.flac")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3550, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(145, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->sampleWidth()); + CPPUNIT_ASSERT_EQUAL(156556ULL, f.audioProperties()->sampleFrames()); + CPPUNIT_ASSERT_EQUAL( + ByteVector("\xcf\xe3\xd9\xda\xba\xde\xab\x2c\xbf\x2c\xa2\x35\x27\x4b\x7f\x76"), + f.audioProperties()->signature()); + } + + void testZeroSizedPadding() + { + ScopedFileCopy copy("zero-sized-padding", ".flac"); + + FLAC::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT(f.isValid()); + } + + void testSaveID3v1() + { + ScopedFileCopy copy("no-tags", ".flac"); + + ByteVector audioStream; + { + FLAC::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT(!f.hasID3v1Tag()); + CPPUNIT_ASSERT_EQUAL((offset_t)4692, f.length()); + + f.seek(0x0100); + audioStream = f.readBlock(4436); + + f.ID3v1Tag(true)->setTitle("01234 56789 ABCDE FGHIJ"); + f.save(); + CPPUNIT_ASSERT(f.hasID3v1Tag()); + CPPUNIT_ASSERT_EQUAL((offset_t)4820, f.length()); + + f.seek(0x0100); + CPPUNIT_ASSERT_EQUAL(audioStream, f.readBlock(4436)); + } + } + }; CPPUNIT_TEST_SUITE_REGISTRATION(TestFLAC); diff --git a/tests/test_id3v2.cpp b/tests/test_id3v2.cpp index 0037165f..ed96499d 100644 --- a/tests/test_id3v2.cpp +++ b/tests/test_id3v2.cpp @@ -94,6 +94,7 @@ class TestID3v2 : public CppUnit::TestFixture CPPUNIT_TEST(testParseTableOfContentsFrame); CPPUNIT_TEST(testRenderTableOfContentsFrame); CPPUNIT_TEST(testShrinkPadding); + CPPUNIT_TEST(testEmptyFrame); CPPUNIT_TEST_SUITE_END(); public: @@ -901,45 +902,58 @@ public: void testParseChapterFrame() { ID3v2::Header header; - ID3v2::ChapterFrame f( - &header, + + ByteVector chapterData = ByteVector("CHAP" // Frame ID "\x00\x00\x00\x20" // Frame size "\x00\x00" // Frame flags - "\x43\x00" // Element ID + "\x43\x00" // Element ID ("C") "\x00\x00\x00\x03" // Start time "\x00\x00\x00\x05" // End time "\x00\x00\x00\x02" // Start offset - "\x00\x00\x00\x03" // End offset - "TIT2" // Embedded frame ID + "\x00\x00\x00\x03", 28); // End offset + ByteVector embeddedFrameData = + ByteVector("TIT2" // Embedded frame ID "\x00\x00\x00\x04" // Embedded frame size "\x00\x00" // Embedded frame flags "\x00" // TIT2 frame text encoding - "CH1", 42)); // Chapter title - CPPUNIT_ASSERT_EQUAL(ByteVector("\x43\x00", 2), - f.elementID()); - CPPUNIT_ASSERT((uint)0x03 == f.startTime()); - CPPUNIT_ASSERT((uint)0x05 == f.endTime()); - CPPUNIT_ASSERT((uint)0x02 == f.startOffset()); - CPPUNIT_ASSERT((uint)0x03 == f.endOffset()); - CPPUNIT_ASSERT((uint)0x01 == f.embeddedFrameList().size()); - CPPUNIT_ASSERT(f.embeddedFrameList("TIT2").size() == 1); - CPPUNIT_ASSERT(f.embeddedFrameList("TIT2")[0]->toString() == "CH1"); + "CH1", 14); // Chapter title + + ID3v2::ChapterFrame f1(&header, chapterData); + + CPPUNIT_ASSERT_EQUAL(ByteVector("C"), f1.elementID()); + CPPUNIT_ASSERT((TagLib::uint)0x03 == f1.startTime()); + CPPUNIT_ASSERT((TagLib::uint)0x05 == f1.endTime()); + CPPUNIT_ASSERT((TagLib::uint)0x02 == f1.startOffset()); + CPPUNIT_ASSERT((TagLib::uint)0x03 == f1.endOffset()); + CPPUNIT_ASSERT((TagLib::uint)0x00 == f1.embeddedFrameList().size()); + + ID3v2::ChapterFrame f2(&header, chapterData + embeddedFrameData); + + CPPUNIT_ASSERT_EQUAL(ByteVector("C"), f2.elementID()); + CPPUNIT_ASSERT((TagLib::uint)0x03 == f2.startTime()); + CPPUNIT_ASSERT((TagLib::uint)0x05 == f2.endTime()); + CPPUNIT_ASSERT((TagLib::uint)0x02 == f2.startOffset()); + CPPUNIT_ASSERT((TagLib::uint)0x03 == f2.endOffset()); + CPPUNIT_ASSERT((TagLib::uint)0x01 == f2.embeddedFrameList().size()); + CPPUNIT_ASSERT(f2.embeddedFrameList("TIT2").size() == 1); + CPPUNIT_ASSERT(f2.embeddedFrameList("TIT2")[0]->toString() == "CH1"); } void testRenderChapterFrame() { ID3v2::Header header; - ID3v2::ChapterFrame f(&header, "CHAP"); - f.setElementID(ByteVector("\x43\x00", 2)); - f.setStartTime(3); - f.setEndTime(5); - f.setStartOffset(2); - f.setEndOffset(3); + ID3v2::ChapterFrame f1(&header, "CHAP"); + f1.setElementID(ByteVector("\x43\x00", 2)); + f1.setStartTime(3); + f1.setEndTime(5); + f1.setStartOffset(2); + f1.setEndOffset(3); ID3v2::TextIdentificationFrame *eF = new ID3v2::TextIdentificationFrame("TIT2"); eF->setText("CH1"); - f.addEmbeddedFrame(eF); - CPPUNIT_ASSERT_EQUAL( + f1.addEmbeddedFrame(eF); + + ByteVector expected = ByteVector("CHAP" // Frame ID "\x00\x00\x00\x20" // Frame size "\x00\x00" // Frame flags @@ -952,8 +966,45 @@ public: "\x00\x00\x00\x04" // Embedded frame size "\x00\x00" // Embedded frame flags "\x00" // TIT2 frame text encoding - "CH1", 42), // Chapter title - f.render()); + "CH1", 42); // Chapter title + + CPPUNIT_ASSERT_EQUAL(expected, f1.render()); + + f1.setElementID("C"); + + CPPUNIT_ASSERT_EQUAL(expected, f1.render()); + + ID3v2::FrameList frames; + eF = new ID3v2::TextIdentificationFrame("TIT2"); + eF->setText("CH1"); + frames.append(eF); + + ID3v2::ChapterFrame f2(ByteVector("\x43\x00", 2), 3, 5, 2, 3, frames); + CPPUNIT_ASSERT_EQUAL(expected, f2.render()); + + frames.clear(); + eF = new ID3v2::TextIdentificationFrame("TIT2"); + eF->setText("CH1"); + frames.append(eF); + + ID3v2::ChapterFrame f3(ByteVector("C\x00", 2), 3, 5, 2, 3, frames); + CPPUNIT_ASSERT_EQUAL(expected, f3.render()); + + frames.clear(); + eF = new ID3v2::TextIdentificationFrame("TIT2"); + eF->setText("CH1"); + frames.append(eF); + + ID3v2::ChapterFrame f4("C", 3, 5, 2, 3, frames); + CPPUNIT_ASSERT_EQUAL(expected, f4.render()); + + CPPUNIT_ASSERT(!f4.toString().isEmpty()); + + ID3v2::ChapterFrame f5("C", 3, 5, 2, 3); + eF = new ID3v2::TextIdentificationFrame("TIT2"); + eF->setText("CH1"); + f5.addEmbeddedFrame(eF); + CPPUNIT_ASSERT_EQUAL(expected, f5.render()); } void testParseTableOfContentsFrame() @@ -964,26 +1015,23 @@ public: ByteVector("CTOC" // Frame ID "\x00\x00\x00\x16" // Frame size "\x00\x00" // Frame flags - "\x54\x00" // Element ID + "\x54\x00" // Element ID ("T") "\x01" // CTOC flags "\x02" // Entry count - "\x43\x00" // First entry - "\x44\x00" // Second entry + "\x43\x00" // First entry ("C") + "\x44\x00" // Second entry ("D") "TIT2" // Embedded frame ID "\x00\x00\x00\x04" // Embedded frame size "\x00\x00" // Embedded frame flags "\x00" // TIT2 frame text encoding "TC1", 32)); // Table of contents title - CPPUNIT_ASSERT_EQUAL(ByteVector("\x54\x00", 2), - f.elementID()); + CPPUNIT_ASSERT_EQUAL(ByteVector("T"), f.elementID()); CPPUNIT_ASSERT(!f.isTopLevel()); CPPUNIT_ASSERT(f.isOrdered()); - CPPUNIT_ASSERT((uint)0x02 == f.entryCount()); - CPPUNIT_ASSERT_EQUAL(ByteVector("\x43\x00", 2), - f.childElements()[0]); - CPPUNIT_ASSERT_EQUAL(ByteVector("\x44\x00", 2), - f.childElements()[1]); - CPPUNIT_ASSERT((uint)0x01 == f.embeddedFrameList().size()); + CPPUNIT_ASSERT((TagLib::uint)0x02 == f.entryCount()); + CPPUNIT_ASSERT_EQUAL(ByteVector("C"), f.childElements()[0]); + CPPUNIT_ASSERT_EQUAL(ByteVector("D"), f.childElements()[1]); + CPPUNIT_ASSERT((TagLib::uint)0x01 == f.embeddedFrameList().size()); CPPUNIT_ASSERT(f.embeddedFrameList("TIT2").size() == 1); CPPUNIT_ASSERT(f.embeddedFrameList("TIT2")[0]->toString() == "TC1"); } @@ -1050,6 +1098,37 @@ public: } } + void testEmptyFrame() + { + ScopedFileCopy copy("xing", ".mp3"); + string newname = copy.fileName(); + + { + MPEG::File f(newname.c_str()); + ID3v2::Tag *tag = f.ID3v2Tag(true); + + ID3v2::UrlLinkFrame *frame1 = new ID3v2::UrlLinkFrame( + ByteVector("WOAF\x00\x00\x00\x01\x00\x00\x00", 11)); + tag->addFrame(frame1); + + ID3v2::TextIdentificationFrame *frame2 = new ID3v2::TextIdentificationFrame("TIT2"); + frame2->setText("Title"); + tag->addFrame(frame2); + + f.save(); + } + + { + MPEG::File f(newname.c_str()); + CPPUNIT_ASSERT_EQUAL(true, f.hasID3v2Tag()); + + ID3v2::Tag *tag = f.ID3v2Tag(); + CPPUNIT_ASSERT_EQUAL(String("Title"), tag->title()); + CPPUNIT_ASSERT_EQUAL(true, tag->frameListMap()["WOAF"].isEmpty()); + } + } + }; CPPUNIT_TEST_SUITE_REGISTRATION(TestID3v2); + diff --git a/tests/test_info.cpp b/tests/test_info.cpp index 8e0d7154..de1f4bbe 100644 --- a/tests/test_info.cpp +++ b/tests/test_info.cpp @@ -34,16 +34,17 @@ public: { RIFF::Info::Tag tag; - CPPUNIT_ASSERT_EQUAL((uint)0, tag.track()); + CPPUNIT_ASSERT_EQUAL((TagLib::uint)0, tag.track()); tag.setTrack(1234); - CPPUNIT_ASSERT_EQUAL((uint)1234, tag.track()); + CPPUNIT_ASSERT_EQUAL((TagLib::uint)1234, tag.track()); CPPUNIT_ASSERT_EQUAL(String("1234"), tag.fieldText("IPRT")); - CPPUNIT_ASSERT_EQUAL((uint)0, tag.year()); + CPPUNIT_ASSERT_EQUAL((TagLib::uint)0, tag.year()); tag.setYear(1234); - CPPUNIT_ASSERT_EQUAL((uint)1234, tag.year()); + CPPUNIT_ASSERT_EQUAL((TagLib::uint)1234, tag.year()); CPPUNIT_ASSERT_EQUAL(String("1234"), tag.fieldText("ICRD")); } }; CPPUNIT_TEST_SUITE_REGISTRATION(TestInfoTag); + diff --git a/tests/test_list.cpp b/tests/test_list.cpp index 1b3156b5..fc303220 100644 --- a/tests/test_list.cpp +++ b/tests/test_list.cpp @@ -22,8 +22,8 @@ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include #include +#include using namespace std; using namespace TagLib; @@ -51,7 +51,7 @@ public: l3.append(3); l3.append(4); CPPUNIT_ASSERT(l1 == l3); - + List l4 = l1; List::Iterator it = l4.find(3); *it = 33; diff --git a/tests/test_map.cpp b/tests/test_map.cpp index 5fdea157..c8f6b7f7 100644 --- a/tests/test_map.cpp +++ b/tests/test_map.cpp @@ -1,6 +1,6 @@ -#include #include #include +#include using namespace std; using namespace TagLib; diff --git a/tests/test_mp4.cpp b/tests/test_mp4.cpp index ac2d469d..c557e337 100644 --- a/tests/test_mp4.cpp +++ b/tests/test_mp4.cpp @@ -19,6 +19,7 @@ class TestMP4 : public CppUnit::TestFixture CPPUNIT_TEST(testPropertiesALAC); CPPUNIT_TEST(testFreeForm); CPPUNIT_TEST(testCheckValid); + CPPUNIT_TEST(testIsEmpty); CPPUNIT_TEST(testUpdateStco); CPPUNIT_TEST(testSaveExisingWhenIlstIsLast); CPPUNIT_TEST(test64BitAtom); @@ -27,6 +28,7 @@ class TestMP4 : public CppUnit::TestFixture CPPUNIT_TEST(testCovrWrite); CPPUNIT_TEST(testCovrRead2); CPPUNIT_TEST(testProperties); + CPPUNIT_TEST(testFuzzedFile); CPPUNIT_TEST_SUITE_END(); public: @@ -34,23 +36,31 @@ public: void testPropertiesAAC() { MP4::File f(TEST_FILE_PATH_C("has-tags.m4a")); + CPPUNIT_ASSERT(f.audioProperties()); CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3707, 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, ((MP4::AudioProperties *)f.audioProperties())->bitsPerSample()); - CPPUNIT_ASSERT_EQUAL(MP4::AudioProperties::AAC, ((MP4::AudioProperties *)f.audioProperties())->codec()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(false, f.audioProperties()->isEncrypted()); + CPPUNIT_ASSERT_EQUAL(MP4::AudioProperties::AAC, f.audioProperties()->codec()); } void testPropertiesALAC() { MP4::File f(TEST_FILE_PATH_C("empty_alac.m4a")); + CPPUNIT_ASSERT(f.audioProperties()); CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->length()); - CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3705, 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, ((MP4::AudioProperties *)f.audioProperties())->bitsPerSample()); - CPPUNIT_ASSERT_EQUAL(MP4::AudioProperties::ALAC, ((MP4::AudioProperties *)f.audioProperties())->codec()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(false, f.audioProperties()->isEncrypted()); + CPPUNIT_ASSERT_EQUAL(MP4::AudioProperties::ALAC, f.audioProperties()->codec()); } void testCheckValid() @@ -61,6 +71,18 @@ public: CPPUNIT_ASSERT(f2.isValid()); } + void testIsEmpty() + { + MP4::Tag t1; + CPPUNIT_ASSERT(t1.isEmpty()); + t1.setArtist("Foo"); + CPPUNIT_ASSERT(!t1.isEmpty()); + + MP4::Tag t2; + t2.setItem("foo", "bar"); + CPPUNIT_ASSERT(!t2.isEmpty()); + } + void testUpdateStco() { ScopedFileCopy copy("no-tags", ".3g2"); @@ -74,7 +96,7 @@ public: MP4::Atoms a(f); MP4::Atom *stco = a.find("moov")->findall("stco", true)[0]; f->seek(stco->offset + 12); - ByteVector data = f->readBlock(stco->length - 12); + ByteVector data = f->readBlock(static_cast(stco->length - 12)); unsigned int count = data.toUInt32BE(0); size_t pos = 4; while (count--) { @@ -93,7 +115,7 @@ public: MP4::Atoms a(f); MP4::Atom *stco = a.find("moov")->findall("stco", true)[0]; f->seek(stco->offset + 12); - ByteVector data = f->readBlock(stco->length - 12); + ByteVector data = f->readBlock(static_cast(stco->length - 12)); unsigned int count = data.toUInt32BE(0); size_t pos = 4, i = 0; while (count--) { @@ -114,14 +136,15 @@ public: string filename = copy.fileName(); MP4::File *f = new MP4::File(filename.c_str()); - CPPUNIT_ASSERT(f->tag()->itemListMap().contains("----:com.apple.iTunes:iTunNORM")); - f->tag()->itemListMap()["----:org.kde.TagLib:Foo"] = StringList("Bar"); + CPPUNIT_ASSERT(f->tag()->contains("----:com.apple.iTunes:iTunNORM")); + f->tag()->setItem("----:org.kde.TagLib:Foo", StringList("Bar")); f->save(); delete f; f = new MP4::File(filename.c_str()); - CPPUNIT_ASSERT(f->tag()->itemListMap().contains("----:org.kde.TagLib:Foo")); - CPPUNIT_ASSERT_EQUAL(String("Bar"), f->tag()->itemListMap()["----:org.kde.TagLib:Foo"].toStringList()[0]); + CPPUNIT_ASSERT(f->tag()->contains("----:org.kde.TagLib:Foo")); + CPPUNIT_ASSERT_EQUAL(String("Bar"), + f->tag()->item("----:org.kde.TagLib:Foo").toStringList().front()); f->save(); delete f; } @@ -132,14 +155,16 @@ public: string filename = copy.fileName(); MP4::File *f = new MP4::File(filename.c_str()); - CPPUNIT_ASSERT_EQUAL(String("82,164"), f->tag()->itemListMap()["----:com.apple.iTunes:replaygain_track_minmax"].toStringList()[0]); + CPPUNIT_ASSERT_EQUAL(String("82,164"), + f->tag()->item("----:com.apple.iTunes:replaygain_track_minmax").toStringList().front()); CPPUNIT_ASSERT_EQUAL(String("Pearl Jam"), f->tag()->artist()); f->tag()->setComment("foo"); f->save(); delete f; f = new MP4::File(filename.c_str()); - CPPUNIT_ASSERT_EQUAL(String("82,164"), f->tag()->itemListMap()["----:com.apple.iTunes:replaygain_track_minmax"].toStringList()[0]); + CPPUNIT_ASSERT_EQUAL(String("82,164"), + f->tag()->item("----:com.apple.iTunes:replaygain_track_minmax").toStringList().front()); CPPUNIT_ASSERT_EQUAL(String("Pearl Jam"), f->tag()->artist()); CPPUNIT_ASSERT_EQUAL(String("foo"), f->tag()->comment()); delete f; @@ -151,20 +176,20 @@ public: string filename = copy.fileName(); MP4::File *f = new MP4::File(filename.c_str()); - CPPUNIT_ASSERT_EQUAL(true, f->tag()->itemListMap()["cpil"].toBool()); + CPPUNIT_ASSERT_EQUAL(true, f->tag()->itemMap()["cpil"].toBool()); MP4::Atoms *atoms = new MP4::Atoms(f); MP4::Atom *moov = atoms->atoms[0]; CPPUNIT_ASSERT_EQUAL(offset_t(77), moov->length); - f->tag()->itemListMap()["pgap"] = true; + f->tag()->setItem("pgap", true); f->save(); delete atoms; delete f; f = new MP4::File(filename.c_str()); - CPPUNIT_ASSERT_EQUAL(true, f->tag()->itemListMap()["cpil"].toBool()); - CPPUNIT_ASSERT_EQUAL(true, f->tag()->itemListMap()["pgap"].toBool()); + CPPUNIT_ASSERT_EQUAL(true, f->tag()->item("cpil").toBool()); + CPPUNIT_ASSERT_EQUAL(true, f->tag()->item("pgap").toBool()); atoms = new MP4::Atoms(f); moov = atoms->atoms[0]; @@ -184,8 +209,8 @@ public: void testCovrRead() { MP4::File *f = new MP4::File(TEST_FILE_PATH_C("has-tags.m4a")); - CPPUNIT_ASSERT(f->tag()->itemListMap().contains("covr")); - MP4::CoverArtList l = f->tag()->itemListMap()["covr"].toCoverArtList(); + CPPUNIT_ASSERT(f->tag()->contains("covr")); + MP4::CoverArtList l = f->tag()->item("covr").toCoverArtList(); CPPUNIT_ASSERT_EQUAL(size_t(2), l.size()); CPPUNIT_ASSERT_EQUAL(MP4::CoverArt::PNG, l[0].format()); CPPUNIT_ASSERT_EQUAL(size_t(79), l[0].data().size()); @@ -200,16 +225,16 @@ public: string filename = copy.fileName(); MP4::File *f = new MP4::File(filename.c_str()); - CPPUNIT_ASSERT(f->tag()->itemListMap().contains("covr")); - MP4::CoverArtList l = f->tag()->itemListMap()["covr"].toCoverArtList(); + CPPUNIT_ASSERT(f->tag()->contains("covr")); + MP4::CoverArtList l = f->tag()->item("covr").toCoverArtList(); l.append(MP4::CoverArt(MP4::CoverArt::PNG, "foo")); - f->tag()->itemListMap()["covr"] = l; + f->tag()->setItem("covr", l); f->save(); delete f; f = new MP4::File(filename.c_str()); - CPPUNIT_ASSERT(f->tag()->itemListMap().contains("covr")); - l = f->tag()->itemListMap()["covr"].toCoverArtList(); + CPPUNIT_ASSERT(f->tag()->contains("covr")); + l = f->tag()->item("covr").toCoverArtList(); CPPUNIT_ASSERT_EQUAL(size_t(3), l.size()); CPPUNIT_ASSERT_EQUAL(MP4::CoverArt::PNG, l[0].format()); CPPUNIT_ASSERT_EQUAL(size_t(79), l[0].data().size()); @@ -223,8 +248,8 @@ public: void testCovrRead2() { MP4::File *f = new MP4::File(TEST_FILE_PATH_C("covr-junk.m4a")); - CPPUNIT_ASSERT(f->tag()->itemListMap().contains("covr")); - MP4::CoverArtList l = f->tag()->itemListMap()["covr"].toCoverArtList(); + CPPUNIT_ASSERT(f->tag()->contains("covr")); + MP4::CoverArtList l = f->tag()->item("covr").toCoverArtList(); CPPUNIT_ASSERT_EQUAL(size_t(2), l.size()); CPPUNIT_ASSERT_EQUAL(MP4::CoverArt::PNG, l[0].format()); CPPUNIT_ASSERT_EQUAL(size_t(79), l[0].data().size()); @@ -250,26 +275,26 @@ public: tags = f.properties(); - CPPUNIT_ASSERT(f.tag()->itemListMap().contains("trkn")); - CPPUNIT_ASSERT_EQUAL(2, f.tag()->itemListMap()["trkn"].toIntPair().first); - CPPUNIT_ASSERT_EQUAL(4, f.tag()->itemListMap()["trkn"].toIntPair().second); + CPPUNIT_ASSERT(f.tag()->contains("trkn")); + CPPUNIT_ASSERT_EQUAL(2, f.tag()->item("trkn").toIntPair().first); + CPPUNIT_ASSERT_EQUAL(4, f.tag()->item("trkn").toIntPair().second); CPPUNIT_ASSERT_EQUAL(StringList("2/4"), tags["TRACKNUMBER"]); - CPPUNIT_ASSERT(f.tag()->itemListMap().contains("disk")); - CPPUNIT_ASSERT_EQUAL(3, f.tag()->itemListMap()["disk"].toIntPair().first); - CPPUNIT_ASSERT_EQUAL(5, f.tag()->itemListMap()["disk"].toIntPair().second); + CPPUNIT_ASSERT(f.tag()->contains("disk")); + CPPUNIT_ASSERT_EQUAL(3, f.tag()->item("disk").toIntPair().first); + CPPUNIT_ASSERT_EQUAL(5, f.tag()->item("disk").toIntPair().second); CPPUNIT_ASSERT_EQUAL(StringList("3/5"), tags["DISCNUMBER"]); - CPPUNIT_ASSERT(f.tag()->itemListMap().contains("tmpo")); - CPPUNIT_ASSERT_EQUAL(123, f.tag()->itemListMap()["tmpo"].toInt()); + CPPUNIT_ASSERT(f.tag()->contains("tmpo")); + CPPUNIT_ASSERT_EQUAL(123, f.tag()->item("tmpo").toInt()); CPPUNIT_ASSERT_EQUAL(StringList("123"), tags["BPM"]); - CPPUNIT_ASSERT(f.tag()->itemListMap().contains("\251ART")); - CPPUNIT_ASSERT_EQUAL(StringList("Foo Bar"), f.tag()->itemListMap()["\251ART"].toStringList()); + CPPUNIT_ASSERT(f.tag()->contains("\251ART")); + CPPUNIT_ASSERT_EQUAL(StringList("Foo Bar"), f.tag()->item("\251ART").toStringList()); CPPUNIT_ASSERT_EQUAL(StringList("Foo Bar"), tags["ARTIST"]); - CPPUNIT_ASSERT(f.tag()->itemListMap().contains("cpil")); - CPPUNIT_ASSERT_EQUAL(true, f.tag()->itemListMap()["cpil"].toBool()); + CPPUNIT_ASSERT(f.tag()->contains("cpil")); + CPPUNIT_ASSERT_EQUAL(true, f.tag()->item("cpil").toBool()); CPPUNIT_ASSERT_EQUAL(StringList("1"), tags["COMPILATION"]); tags["COMPILATION"] = StringList("0"); @@ -277,11 +302,17 @@ public: tags = f.properties(); - CPPUNIT_ASSERT(f.tag()->itemListMap().contains("cpil")); - CPPUNIT_ASSERT_EQUAL(false, f.tag()->itemListMap()["cpil"].toBool()); + CPPUNIT_ASSERT(f.tag()->contains("cpil")); + CPPUNIT_ASSERT_EQUAL(false, f.tag()->item("cpil").toBool()); CPPUNIT_ASSERT_EQUAL(StringList("0"), tags["COMPILATION"]); } + void testFuzzedFile() + { + MP4::File f(TEST_FILE_PATH_C("infloop.m4a")); + CPPUNIT_ASSERT(f.isValid()); + } + }; CPPUNIT_TEST_SUITE_REGISTRATION(TestMP4); diff --git a/tests/test_mpc.cpp b/tests/test_mpc.cpp index 0589740f..50a62d22 100644 --- a/tests/test_mpc.cpp +++ b/tests/test_mpc.cpp @@ -28,41 +28,61 @@ public: void testPropertiesSV8() { MPC::File f(TEST_FILE_PATH_C("sv8_header.mpc")); + CPPUNIT_ASSERT(f.audioProperties()); CPPUNIT_ASSERT_EQUAL(8, f.audioProperties()->mpcVersion()); CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->length()); - CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(1497, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->bitrate()); CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(66014U, f.audioProperties()->sampleFrames()); } void testPropertiesSV7() { MPC::File f(TEST_FILE_PATH_C("click.mpc")); + CPPUNIT_ASSERT(f.audioProperties()); CPPUNIT_ASSERT_EQUAL(7, f.audioProperties()->mpcVersion()); CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->length()); - CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(40, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(318, f.audioProperties()->bitrate()); CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(1760U, f.audioProperties()->sampleFrames()); + CPPUNIT_ASSERT_EQUAL(14221, f.audioProperties()->trackGain()); + CPPUNIT_ASSERT_EQUAL(19848, f.audioProperties()->trackPeak()); + CPPUNIT_ASSERT_EQUAL(14221, f.audioProperties()->albumGain()); + CPPUNIT_ASSERT_EQUAL(19848, f.audioProperties()->albumPeak()); } void testPropertiesSV5() { MPC::File f(TEST_FILE_PATH_C("sv5_header.mpc")); + CPPUNIT_ASSERT(f.audioProperties()); CPPUNIT_ASSERT_EQUAL(5, f.audioProperties()->mpcVersion()); CPPUNIT_ASSERT_EQUAL(26, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(26, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(26371, f.audioProperties()->lengthInMilliseconds()); CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->bitrate()); CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(1162944U, f.audioProperties()->sampleFrames()); } void testPropertiesSV4() { MPC::File f(TEST_FILE_PATH_C("sv4_header.mpc")); + CPPUNIT_ASSERT(f.audioProperties()); CPPUNIT_ASSERT_EQUAL(4, f.audioProperties()->mpcVersion()); CPPUNIT_ASSERT_EQUAL(26, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(26, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(26371, f.audioProperties()->lengthInMilliseconds()); CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->bitrate()); CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(1162944U, f.audioProperties()->sampleFrames()); } void testFuzzedFile1() diff --git a/tests/test_mpeg.cpp b/tests/test_mpeg.cpp index 07b970ee..56450846 100644 --- a/tests/test_mpeg.cpp +++ b/tests/test_mpeg.cpp @@ -3,6 +3,9 @@ #include #include #include +#include +#include +#include #include #include "utils.h" @@ -12,20 +15,96 @@ using namespace TagLib; class TestMPEG : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(TestMPEG); + CPPUNIT_TEST(testAudioPropertiesXingHeaderCBR); + CPPUNIT_TEST(testAudioPropertiesXingHeaderVBR); + CPPUNIT_TEST(testAudioPropertiesVBRIHeader); + CPPUNIT_TEST(testAudioPropertiesNoVBRHeaders); CPPUNIT_TEST(testVersion2DurationWithXingHeader); CPPUNIT_TEST(testSaveID3v24); CPPUNIT_TEST(testSaveID3v24WrongParam); CPPUNIT_TEST(testSaveID3v23); CPPUNIT_TEST(testDuplicateID3v2); CPPUNIT_TEST(testFuzzedFile); + CPPUNIT_TEST(testFrameOffset); CPPUNIT_TEST_SUITE_END(); public: + void testAudioPropertiesXingHeaderCBR() + { + MPEG::File f(TEST_FILE_PATH_C("lame_cbr.mp3")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(1887, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(1887, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(1887164, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(64, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(MPEG::XingHeader::Xing, f.audioProperties()->xingHeader()->type()); + } + + void testAudioPropertiesXingHeaderVBR() + { + MPEG::File f(TEST_FILE_PATH_C("lame_vbr.mp3")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(1887, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(1887, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(1887164, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(70, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(MPEG::XingHeader::Xing, f.audioProperties()->xingHeader()->type()); + } + + void testAudioPropertiesVBRIHeader() + { + MPEG::File f(TEST_FILE_PATH_C("vbri.mp3")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(222, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(222, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(222198, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(233, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(MPEG::XingHeader::VBRI, f.audioProperties()->xingHeader()->type()); + } + + void testAudioPropertiesNoVBRHeaders() + { + MPEG::File f(TEST_FILE_PATH_C("bladeenc.mp3")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3553, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(64, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT(!f.audioProperties()->xingHeader()); + + offset_t last = f.lastFrameOffset(); + + f.seek(last); + MPEG::Header lastHeader(f.readBlock(4)); + + while (!lastHeader.isValid()) { + + last = f.previousFrameOffset(last); + + f.seek(last); + lastHeader = MPEG::Header(f.readBlock(4)); + } + + CPPUNIT_ASSERT_EQUAL((offset_t)28213, last); + CPPUNIT_ASSERT_EQUAL(209, lastHeader.frameLength()); + } + void testVersion2DurationWithXingHeader() { MPEG::File f(TEST_FILE_PATH_C("mpeg2.mp3")); + CPPUNIT_ASSERT(f.audioProperties()); CPPUNIT_ASSERT_EQUAL(5387, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(5387, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(5387285, f.audioProperties()->lengthInMilliseconds()); } void testSaveID3v24() @@ -96,14 +175,12 @@ public: void testDuplicateID3v2() { - ScopedFileCopy copy("duplicate_id3v2", ".mp3"); - string newname = copy.fileName(); - - MPEG::File f(newname.c_str()); + MPEG::File f(TEST_FILE_PATH_C("duplicate_id3v2.mp3")); // duplicate_id3v2.mp3 has duplicate ID3v2 tags. // Sample rate will be 32000 if can't skip the second tag. + CPPUNIT_ASSERT(f.hasID3v2Tag()); CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); } @@ -113,6 +190,28 @@ public: CPPUNIT_ASSERT(f.isValid()); } + void testFrameOffset() + { + { + MPEG::File f(TEST_FILE_PATH_C("ape.mp3")); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT_EQUAL((offset_t)0x0000, f.firstFrameOffset()); + CPPUNIT_ASSERT_EQUAL((offset_t)0x1FD6, f.lastFrameOffset()); + } + { + MPEG::File f(TEST_FILE_PATH_C("ape-id3v1.mp3")); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT_EQUAL((offset_t)0x0000, f.firstFrameOffset()); + CPPUNIT_ASSERT_EQUAL((offset_t)0x1FD6, f.lastFrameOffset()); + } + { + MPEG::File f(TEST_FILE_PATH_C("ape-id3v2.mp3")); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT_EQUAL((offset_t)0x041A, f.firstFrameOffset()); + CPPUNIT_ASSERT_EQUAL((offset_t)0x23F0, f.lastFrameOffset()); + } + } + }; CPPUNIT_TEST_SUITE_REGISTRATION(TestMPEG); diff --git a/tests/test_ogg.cpp b/tests/test_ogg.cpp index 61b96701..b59b6aa0 100644 --- a/tests/test_ogg.cpp +++ b/tests/test_ogg.cpp @@ -20,6 +20,7 @@ class TestOGG : public CppUnit::TestFixture CPPUNIT_TEST(testSplitPackets); CPPUNIT_TEST(testDictInterface1); CPPUNIT_TEST(testDictInterface2); + CPPUNIT_TEST(testAudioProperties); CPPUNIT_TEST_SUITE_END(); public: @@ -44,13 +45,27 @@ public: ScopedFileCopy copy("empty", ".ogg"); string newname = copy.fileName(); + String longText(std::string(128 * 1024, ' ').c_str()); + for (size_t i = 0; i < longText.length(); ++i) + longText[i] = static_cast(L'A' + (i % 26)); + Ogg::Vorbis::File *f = new Ogg::Vorbis::File(newname.c_str()); - f->tag()->addField("test", ByteVector(128 * 1024, 'x') + ByteVector(1, '\0')); + f->tag()->setTitle(longText); f->save(); delete f; f = new Ogg::Vorbis::File(newname.c_str()); + CPPUNIT_ASSERT(f->isValid()); CPPUNIT_ASSERT_EQUAL(19, f->lastPageHeader()->pageSequenceNumber()); + CPPUNIT_ASSERT_EQUAL(longText, f->tag()->title()); + f->tag()->setTitle("ABCDE"); + f->save(); + delete f; + + f = new Ogg::Vorbis::File(newname.c_str()); + CPPUNIT_ASSERT(f->isValid()); + CPPUNIT_ASSERT_EQUAL(3, f->lastPageHeader()->pageSequenceNumber()); + CPPUNIT_ASSERT_EQUAL(String("ABCDE"), f->tag()->title()); delete f; } @@ -104,6 +119,21 @@ public: delete f; } + void testAudioProperties() + { + Ogg::Vorbis::File f(TEST_FILE_PATH_C("empty.ogg")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3685, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(9, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->vorbisVersion()); + CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->bitrateMaximum()); + CPPUNIT_ASSERT_EQUAL(112000, f.audioProperties()->bitrateNominal()); + CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->bitrateMinimum()); + } }; CPPUNIT_TEST_SUITE_REGISTRATION(TestOGG); diff --git a/tests/test_oggflac.cpp b/tests/test_oggflac.cpp index 975af44e..32784b21 100644 --- a/tests/test_oggflac.cpp +++ b/tests/test_oggflac.cpp @@ -34,8 +34,8 @@ public: CPPUNIT_ASSERT_EQUAL(String("The Artist"), f->tag()->artist()); f->seek(0, File::End); - int size = f->tell(); - CPPUNIT_ASSERT_EQUAL(9134, size); + offset_t size = f->tell(); + CPPUNIT_ASSERT_EQUAL((offset_t)9134, size); delete f; } diff --git a/tests/test_opus.cpp b/tests/test_opus.cpp index aaca4c52..73863bf8 100644 --- a/tests/test_opus.cpp +++ b/tests/test_opus.cpp @@ -12,21 +12,25 @@ using namespace TagLib; class TestOpus : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(TestOpus); - CPPUNIT_TEST(testProperties); + CPPUNIT_TEST(testAudioProperties); CPPUNIT_TEST(testReadComments); CPPUNIT_TEST(testWriteComments); CPPUNIT_TEST_SUITE_END(); public: - void testProperties() + void testAudioProperties() { Ogg::Opus::File f(TEST_FILE_PATH_C("correctness_gain_silent_output.opus")); + CPPUNIT_ASSERT(f.audioProperties()); CPPUNIT_ASSERT_EQUAL(7, f.audioProperties()->length()); - CPPUNIT_ASSERT_EQUAL(41, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(7, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(7737, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(37, f.audioProperties()->bitrate()); CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->channels()); CPPUNIT_ASSERT_EQUAL(48000, f.audioProperties()->sampleRate()); - CPPUNIT_ASSERT_EQUAL(48000, ((Ogg::Opus::AudioProperties *)f.audioProperties())->inputSampleRate()); + CPPUNIT_ASSERT_EQUAL(48000, f.audioProperties()->inputSampleRate()); + CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->opusVersion()); } void testReadComments() diff --git a/tests/test_propertymap.cpp b/tests/test_propertymap.cpp index 941a1a22..aac7430e 100644 --- a/tests/test_propertymap.cpp +++ b/tests/test_propertymap.cpp @@ -1,25 +1,32 @@ -#include #include +#include +#include +#include +#include "utils.h" + +using namespace TagLib; + class TestPropertyMap : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(TestPropertyMap); CPPUNIT_TEST(testInvalidKeys); + CPPUNIT_TEST(testGetSet); CPPUNIT_TEST_SUITE_END(); public: void testInvalidKeys() { - TagLib::PropertyMap map1; + PropertyMap map1; CPPUNIT_ASSERT(map1.isEmpty()); map1[L"\x00c4\x00d6\x00dc"].append("test"); CPPUNIT_ASSERT_EQUAL(map1.size(), (size_t)1); - TagLib::PropertyMap map2; + PropertyMap map2; map2[L"\x00c4\x00d6\x00dc"].append("test"); CPPUNIT_ASSERT(map1 == map2); CPPUNIT_ASSERT(map1.contains(map2)); - map2["ARTIST"] = TagLib::String("Test Artist"); + map2["ARTIST"] = String("Test Artist"); CPPUNIT_ASSERT(map1 != map2); CPPUNIT_ASSERT(map2.contains(map1)); @@ -28,6 +35,43 @@ public: CPPUNIT_ASSERT(!map2.contains(map1)); } + + void testGetSet() + { + ID3v1::Tag tag; + + tag.setTitle("Test Title"); + tag.setArtist("Test Artist"); + tag.setAlbum("Test Album"); + tag.setYear(2015); + tag.setTrack(10); + + { + PropertyMap prop = tag.properties(); + CPPUNIT_ASSERT_EQUAL(String("Test Title"), prop["TITLE" ].front()); + CPPUNIT_ASSERT_EQUAL(String("Test Artist"), prop["ARTIST" ].front()); + CPPUNIT_ASSERT_EQUAL(String("Test Album"), prop["ALBUM" ].front()); + CPPUNIT_ASSERT_EQUAL(String("2015"), prop["DATE" ].front()); + CPPUNIT_ASSERT_EQUAL(String("10"), prop["TRACKNUMBER"].front()); + + prop["TITLE" ].front() = "Test Title 2"; + prop["ARTIST" ].front() = "Test Artist 2"; + prop["TRACKNUMBER"].front() = "5"; + + tag.setProperties(prop); + } + + CPPUNIT_ASSERT_EQUAL(String("Test Title 2"), tag.title()); + CPPUNIT_ASSERT_EQUAL(String("Test Artist 2"), tag.artist()); + CPPUNIT_ASSERT_EQUAL(5U, tag.track()); + + tag.setProperties(PropertyMap()); + + CPPUNIT_ASSERT_EQUAL(String(""), tag.title()); + CPPUNIT_ASSERT_EQUAL(String(""), tag.artist()); + CPPUNIT_ASSERT_EQUAL(0U, tag.track()); + } + }; CPPUNIT_TEST_SUITE_REGISTRATION(TestPropertyMap); diff --git a/tests/test_riff.cpp b/tests/test_riff.cpp index a5ed30bc..a5bce476 100644 --- a/tests/test_riff.cpp +++ b/tests/test_riff.cpp @@ -15,12 +15,12 @@ public: PublicRIFF(FileName file) : RIFF::File(file, BigEndian) {}; TagLib::uint riffSize() { return RIFF::File::riffSize(); }; TagLib::uint chunkCount() { return RIFF::File::chunkCount(); }; - TagLib::uint chunkOffset(TagLib::uint i) { return RIFF::File::chunkOffset(i); }; + offset_t chunkOffset(TagLib::uint i) { return RIFF::File::chunkOffset(i); }; TagLib::uint chunkPadding(TagLib::uint i) { return RIFF::File::chunkPadding(i); }; TagLib::uint chunkDataSize(TagLib::uint i) { return RIFF::File::chunkDataSize(i); }; ByteVector chunkName(TagLib::uint i) { return RIFF::File::chunkName(i); }; ByteVector chunkData(TagLib::uint i) { return RIFF::File::chunkData(i); }; - void setChunkData(uint i, const ByteVector &data) { + void setChunkData(TagLib::uint i, const ByteVector &data) { RIFF::File::setChunkData(i, data); } void setChunkData(const ByteVector &name, const ByteVector &data) { @@ -29,7 +29,7 @@ public: virtual TagLib::Tag* tag() const { return 0; }; virtual TagLib::AudioProperties* audioProperties() const { return 0;}; virtual bool save() { return false; }; - void removeChunk(uint i) { RIFF::File::removeChunk(i); } + void removeChunk(TagLib::uint i) { RIFF::File::removeChunk(i); } void removeChunk(const ByteVector &name) { RIFF::File::removeChunk(name); } }; @@ -52,7 +52,7 @@ public: PublicRIFF *f = new PublicRIFF(filename.c_str()); CPPUNIT_ASSERT_EQUAL(ByteVector("TEST"), f->chunkName(2)); - CPPUNIT_ASSERT_EQUAL(TagLib::uint(0x1728 + 8), f->chunkOffset(2)); + CPPUNIT_ASSERT_EQUAL(offset_t(0x1728 + 8), f->chunkOffset(2)); f->setChunkData("TEST", "foo"); delete f; @@ -61,7 +61,7 @@ public: CPPUNIT_ASSERT_EQUAL(ByteVector("TEST"), f->chunkName(2)); CPPUNIT_ASSERT_EQUAL(ByteVector("foo"), f->chunkData(2)); CPPUNIT_ASSERT_EQUAL(TagLib::uint(3), f->chunkDataSize(2)); - CPPUNIT_ASSERT_EQUAL(TagLib::uint(0x1728 + 8), f->chunkOffset(2)); + CPPUNIT_ASSERT_EQUAL(offset_t(0x1728 + 8), f->chunkOffset(2)); f->setChunkData("SSND", "abcd"); @@ -96,18 +96,18 @@ public: string filename = copy.fileName(); PublicRIFF *f = new PublicRIFF(filename.c_str()); - CPPUNIT_ASSERT_EQUAL(TagLib::uint(0xff0 + 8), f->chunkOffset(2)); + CPPUNIT_ASSERT_EQUAL(offset_t(0xff0 + 8), f->chunkOffset(2)); CPPUNIT_ASSERT_EQUAL(TagLib::uint(311), f->chunkDataSize(2)); CPPUNIT_ASSERT_EQUAL(ByteVector("SSND"), f->chunkName(2)); CPPUNIT_ASSERT_EQUAL(TagLib::uint(1), f->chunkPadding(2)); CPPUNIT_ASSERT_EQUAL(offset_t(4400), f->length()); CPPUNIT_ASSERT_EQUAL(TagLib::uint(4399 - 8), f->riffSize()); f->setChunkData("TEST", "abcd"); - CPPUNIT_ASSERT_EQUAL(TagLib::uint(4088), f->chunkOffset(2)); + CPPUNIT_ASSERT_EQUAL(offset_t(4088), f->chunkOffset(2)); CPPUNIT_ASSERT_EQUAL(TagLib::uint(311), f->chunkDataSize(2)); CPPUNIT_ASSERT_EQUAL(ByteVector("SSND"), f->chunkName(2)); CPPUNIT_ASSERT_EQUAL(TagLib::uint(1), f->chunkPadding(2)); - CPPUNIT_ASSERT_EQUAL(TagLib::uint(4408), f->chunkOffset(3)); + CPPUNIT_ASSERT_EQUAL(offset_t(4408), f->chunkOffset(3)); CPPUNIT_ASSERT_EQUAL(TagLib::uint(4), f->chunkDataSize(3)); CPPUNIT_ASSERT_EQUAL(ByteVector("TEST"), f->chunkName(3)); CPPUNIT_ASSERT_EQUAL(TagLib::uint(0), f->chunkPadding(3)); @@ -115,11 +115,11 @@ public: delete f; f = new PublicRIFF(filename.c_str()); - CPPUNIT_ASSERT_EQUAL(TagLib::uint(4088), f->chunkOffset(2)); + CPPUNIT_ASSERT_EQUAL(offset_t(4088), f->chunkOffset(2)); CPPUNIT_ASSERT_EQUAL(TagLib::uint(311), f->chunkDataSize(2)); CPPUNIT_ASSERT_EQUAL(ByteVector("SSND"), f->chunkName(2)); CPPUNIT_ASSERT_EQUAL(TagLib::uint(1), f->chunkPadding(2)); - CPPUNIT_ASSERT_EQUAL(TagLib::uint(4408), f->chunkOffset(3)); + CPPUNIT_ASSERT_EQUAL(offset_t(4408), f->chunkOffset(3)); CPPUNIT_ASSERT_EQUAL(TagLib::uint(4), f->chunkDataSize(3)); CPPUNIT_ASSERT_EQUAL(ByteVector("TEST"), f->chunkName(3)); CPPUNIT_ASSERT_EQUAL(TagLib::uint(0), f->chunkPadding(3)); @@ -133,18 +133,18 @@ public: string filename = copy.fileName(); PublicRIFF *f = new PublicRIFF(filename.c_str()); - CPPUNIT_ASSERT_EQUAL(TagLib::uint(0xff0 + 8), f->chunkOffset(2)); + CPPUNIT_ASSERT_EQUAL(offset_t(0xff0 + 8), f->chunkOffset(2)); CPPUNIT_ASSERT_EQUAL(TagLib::uint(311), f->chunkDataSize(2)); CPPUNIT_ASSERT_EQUAL(ByteVector("SSND"), f->chunkName(2)); CPPUNIT_ASSERT_EQUAL(TagLib::uint(0), f->chunkPadding(2)); CPPUNIT_ASSERT_EQUAL(offset_t(4399), f->length()); CPPUNIT_ASSERT_EQUAL(TagLib::uint(4399 - 8), f->riffSize()); f->setChunkData("TEST", "abcd"); - CPPUNIT_ASSERT_EQUAL(TagLib::uint(4088), f->chunkOffset(2)); + CPPUNIT_ASSERT_EQUAL(offset_t(4088), f->chunkOffset(2)); CPPUNIT_ASSERT_EQUAL(TagLib::uint(311), f->chunkDataSize(2)); CPPUNIT_ASSERT_EQUAL(ByteVector("SSND"), f->chunkName(2)); CPPUNIT_ASSERT_EQUAL(TagLib::uint(1), f->chunkPadding(2)); - CPPUNIT_ASSERT_EQUAL(TagLib::uint(4408), f->chunkOffset(3)); + CPPUNIT_ASSERT_EQUAL(offset_t(4408), f->chunkOffset(3)); CPPUNIT_ASSERT_EQUAL(TagLib::uint(4), f->chunkDataSize(3)); CPPUNIT_ASSERT_EQUAL(ByteVector("TEST"), f->chunkName(3)); CPPUNIT_ASSERT_EQUAL(TagLib::uint(0), f->chunkPadding(3)); @@ -152,11 +152,11 @@ public: delete f; f = new PublicRIFF(filename.c_str()); - CPPUNIT_ASSERT_EQUAL(TagLib::uint(4088), f->chunkOffset(2)); + CPPUNIT_ASSERT_EQUAL(offset_t(4088), f->chunkOffset(2)); CPPUNIT_ASSERT_EQUAL(TagLib::uint(311), f->chunkDataSize(2)); CPPUNIT_ASSERT_EQUAL(ByteVector("SSND"), f->chunkName(2)); CPPUNIT_ASSERT_EQUAL(TagLib::uint(1), f->chunkPadding(2)); - CPPUNIT_ASSERT_EQUAL(TagLib::uint(4408), f->chunkOffset(3)); + CPPUNIT_ASSERT_EQUAL(offset_t(4408), f->chunkOffset(3)); CPPUNIT_ASSERT_EQUAL(TagLib::uint(4), f->chunkDataSize(3)); CPPUNIT_ASSERT_EQUAL(ByteVector("TEST"), f->chunkName(3)); CPPUNIT_ASSERT_EQUAL(TagLib::uint(0), f->chunkPadding(3)); @@ -170,18 +170,18 @@ public: string filename = copy.fileName(); PublicRIFF *f = new PublicRIFF(filename.c_str()); - CPPUNIT_ASSERT_EQUAL(TagLib::uint(0xff0 + 8), f->chunkOffset(2)); + CPPUNIT_ASSERT_EQUAL(offset_t(0xff0 + 8), f->chunkOffset(2)); CPPUNIT_ASSERT_EQUAL(TagLib::uint(311), f->chunkDataSize(2)); CPPUNIT_ASSERT_EQUAL(ByteVector("SSND"), f->chunkName(2)); CPPUNIT_ASSERT_EQUAL(TagLib::uint(0), f->chunkPadding(2)); CPPUNIT_ASSERT_EQUAL(offset_t(4399), f->length()); CPPUNIT_ASSERT_EQUAL(TagLib::uint(4399 - 8), f->riffSize()); f->setChunkData("TEST", "abc"); - CPPUNIT_ASSERT_EQUAL(TagLib::uint(4088), f->chunkOffset(2)); + CPPUNIT_ASSERT_EQUAL(offset_t(4088), f->chunkOffset(2)); CPPUNIT_ASSERT_EQUAL(TagLib::uint(311), f->chunkDataSize(2)); CPPUNIT_ASSERT_EQUAL(ByteVector("SSND"), f->chunkName(2)); CPPUNIT_ASSERT_EQUAL(TagLib::uint(1), f->chunkPadding(2)); - CPPUNIT_ASSERT_EQUAL(TagLib::uint(4408), f->chunkOffset(3)); + CPPUNIT_ASSERT_EQUAL(offset_t(4408), f->chunkOffset(3)); CPPUNIT_ASSERT_EQUAL(TagLib::uint(3), f->chunkDataSize(3)); CPPUNIT_ASSERT_EQUAL(ByteVector("TEST"), f->chunkName(3)); CPPUNIT_ASSERT_EQUAL(TagLib::uint(1), f->chunkPadding(3)); @@ -189,11 +189,11 @@ public: delete f; f = new PublicRIFF(filename.c_str()); - CPPUNIT_ASSERT_EQUAL(TagLib::uint(4088), f->chunkOffset(2)); + CPPUNIT_ASSERT_EQUAL(offset_t(4088), f->chunkOffset(2)); CPPUNIT_ASSERT_EQUAL(TagLib::uint(311), f->chunkDataSize(2)); CPPUNIT_ASSERT_EQUAL(ByteVector("SSND"), f->chunkName(2)); CPPUNIT_ASSERT_EQUAL(TagLib::uint(1), f->chunkPadding(2)); - CPPUNIT_ASSERT_EQUAL(TagLib::uint(4408), f->chunkOffset(3)); + CPPUNIT_ASSERT_EQUAL(offset_t(4408), f->chunkOffset(3)); CPPUNIT_ASSERT_EQUAL(TagLib::uint(3), f->chunkDataSize(3)); CPPUNIT_ASSERT_EQUAL(ByteVector("TEST"), f->chunkName(3)); CPPUNIT_ASSERT_EQUAL(TagLib::uint(1), f->chunkPadding(3)); @@ -209,17 +209,17 @@ public: PublicRIFF *f = new PublicRIFF(filename.c_str()); CPPUNIT_ASSERT_EQUAL(ByteVector("COMM"), f->chunkName(0)); - CPPUNIT_ASSERT_EQUAL(TagLib::uint(0x000C + 8), f->chunkOffset(0)); + CPPUNIT_ASSERT_EQUAL(offset_t(0x000C + 8), f->chunkOffset(0)); CPPUNIT_ASSERT_EQUAL(ByteVector("SSND"), f->chunkName(1)); - CPPUNIT_ASSERT_EQUAL(TagLib::uint(0x0026 + 8), f->chunkOffset(1)); + CPPUNIT_ASSERT_EQUAL(offset_t(0x0026 + 8), f->chunkOffset(1)); CPPUNIT_ASSERT_EQUAL(ByteVector("TEST"), f->chunkName(2)); - CPPUNIT_ASSERT_EQUAL(TagLib::uint(0x1728 + 8), f->chunkOffset(2)); + CPPUNIT_ASSERT_EQUAL(offset_t(0x1728 + 8), f->chunkOffset(2)); const ByteVector data(0x400, ' '); f->setChunkData("SSND", data); - CPPUNIT_ASSERT_EQUAL(TagLib::uint(0x000C + 8), f->chunkOffset(0)); - CPPUNIT_ASSERT_EQUAL(TagLib::uint(0x0026 + 8), f->chunkOffset(1)); - CPPUNIT_ASSERT_EQUAL(TagLib::uint(0x042E + 8), f->chunkOffset(2)); + CPPUNIT_ASSERT_EQUAL(offset_t(0x000C + 8), f->chunkOffset(0)); + CPPUNIT_ASSERT_EQUAL(offset_t(0x0026 + 8), f->chunkOffset(1)); + CPPUNIT_ASSERT_EQUAL(offset_t(0x042E + 8), f->chunkOffset(2)); f->seek(f->chunkOffset(0) - 8); CPPUNIT_ASSERT_EQUAL(ByteVector("COMM"), f->readBlock(4)); @@ -229,9 +229,9 @@ public: CPPUNIT_ASSERT_EQUAL(ByteVector("TEST"), f->readBlock(4)); f->setChunkData(0, data); - CPPUNIT_ASSERT_EQUAL(TagLib::uint(0x000C + 8), f->chunkOffset(0)); - CPPUNIT_ASSERT_EQUAL(TagLib::uint(0x0414 + 8), f->chunkOffset(1)); - CPPUNIT_ASSERT_EQUAL(TagLib::uint(0x081C + 8), f->chunkOffset(2)); + CPPUNIT_ASSERT_EQUAL(offset_t(0x000C + 8), f->chunkOffset(0)); + CPPUNIT_ASSERT_EQUAL(offset_t(0x0414 + 8), f->chunkOffset(1)); + CPPUNIT_ASSERT_EQUAL(offset_t(0x081C + 8), f->chunkOffset(2)); f->seek(f->chunkOffset(0) - 8); CPPUNIT_ASSERT_EQUAL(ByteVector("COMM"), f->readBlock(4)); @@ -241,8 +241,8 @@ public: CPPUNIT_ASSERT_EQUAL(ByteVector("TEST"), f->readBlock(4)); f->removeChunk("SSND"); - CPPUNIT_ASSERT_EQUAL(TagLib::uint(0x000C + 8), f->chunkOffset(0)); - CPPUNIT_ASSERT_EQUAL(TagLib::uint(0x0414 + 8), f->chunkOffset(1)); + CPPUNIT_ASSERT_EQUAL(offset_t(0x000C + 8), f->chunkOffset(0)); + CPPUNIT_ASSERT_EQUAL(offset_t(0x0414 + 8), f->chunkOffset(1)); f->seek(f->chunkOffset(0) - 8); CPPUNIT_ASSERT_EQUAL(ByteVector("COMM"), f->readBlock(4)); @@ -250,7 +250,7 @@ public: CPPUNIT_ASSERT_EQUAL(ByteVector("TEST"), f->readBlock(4)); f->removeChunk(0); - CPPUNIT_ASSERT_EQUAL(TagLib::uint(0x000C + 8), f->chunkOffset(0)); + CPPUNIT_ASSERT_EQUAL(offset_t(0x000C + 8), f->chunkOffset(0)); f->seek(f->chunkOffset(0) - 8); CPPUNIT_ASSERT_EQUAL(ByteVector("TEST"), f->readBlock(4)); @@ -261,3 +261,4 @@ public: }; CPPUNIT_TEST_SUITE_REGISTRATION(TestRIFF); + diff --git a/tests/test_speex.cpp b/tests/test_speex.cpp new file mode 100644 index 00000000..577adb3e --- /dev/null +++ b/tests/test_speex.cpp @@ -0,0 +1,31 @@ +#include +#include +#include "utils.h" + +using namespace std; +using namespace TagLib; + +class TestSpeex : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(TestSpeex); + CPPUNIT_TEST(testAudioProperties); + CPPUNIT_TEST_SUITE_END(); + +public: + + void testAudioProperties() + { + Ogg::Speex::File f(TEST_FILE_PATH_C("empty.spx")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3685, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(53, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(-1, f.audioProperties()->bitrateNominal()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + } + +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestSpeex); diff --git a/tests/test_string.cpp b/tests/test_string.cpp index fede02f7..f0038826 100644 --- a/tests/test_string.cpp +++ b/tests/test_string.cpp @@ -38,6 +38,7 @@ class TestString : public CppUnit::TestFixture CPPUNIT_TEST(testUTF16Decode); CPPUNIT_TEST(testUTF16DecodeInvalidBOM); CPPUNIT_TEST(testUTF16DecodeEmptyWithBOM); + CPPUNIT_TEST(testSurrogatePair); CPPUNIT_TEST(testAppendCharDetach); CPPUNIT_TEST(testAppendStringDetach); CPPUNIT_TEST(testToInt); @@ -118,12 +119,10 @@ public: CPPUNIT_ASSERT(memcmp(String("foo").data(String::Latin1).data(), "foo", 3) == 0); CPPUNIT_ASSERT(memcmp(String("f").data(String::Latin1).data(), "f", 1) == 0); - ByteVector utf16 = unicode.data(String::UTF16); - - // Check to make sure that the BOM is there and that the data size is correct + // Check to make sure that the BOM is there and that the data size is correct + const ByteVector utf16 = unicode.data(String::UTF16); CPPUNIT_ASSERT(utf16.size() == 2 + (unicode.size() * 2)); - CPPUNIT_ASSERT(unicode == String(utf16, String::UTF16)); } @@ -170,6 +169,21 @@ public: CPPUNIT_ASSERT_EQUAL(String(), String(b, String::UTF16)); } + void testSurrogatePair() + { + // Make sure that a surrogate pair is converted into single UTF-8 char + // and vice versa. + + const ByteVector v1("\xff\xfe\x42\xd8\xb7\xdf\xce\x91\x4b\x5c"); + const ByteVector v2("\xf0\xa0\xae\xb7\xe9\x87\x8e\xe5\xb1\x8b"); + + const String s1(v1, String::UTF16); + CPPUNIT_ASSERT_EQUAL(s1.data(String::UTF8), v2); + + const String s2(v2, String::UTF8); + CPPUNIT_ASSERT_EQUAL(s2.data(String::UTF16), v1); + } + void testAppendStringDetach() { String a("abc"); @@ -336,3 +350,4 @@ public: }; CPPUNIT_TEST_SUITE_REGISTRATION(TestString); + diff --git a/tests/test_trueaudio.cpp b/tests/test_trueaudio.cpp index 5ff114cf..00b81ede 100644 --- a/tests/test_trueaudio.cpp +++ b/tests/test_trueaudio.cpp @@ -1,7 +1,7 @@ -#include #include #include #include +#include #include "utils.h" using namespace std; @@ -11,6 +11,7 @@ class TestTrueAudio : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(TestTrueAudio); CPPUNIT_TEST(testReadPropertiesWithoutID3v2); + CPPUNIT_TEST(testReadPropertiesWithTags); CPPUNIT_TEST_SUITE_END(); public: @@ -20,6 +21,29 @@ public: TrueAudio::File f(TEST_FILE_PATH_C("empty.tta")); CPPUNIT_ASSERT(f.audioProperties()); CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3685, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(173, 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(162496U, f.audioProperties()->sampleFrames()); + CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->ttaVersion()); + } + + void testReadPropertiesWithTags() + { + TrueAudio::File f(TEST_FILE_PATH_C("tagged.tta")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3685, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(173, 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(162496U, f.audioProperties()->sampleFrames()); + CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->ttaVersion()); } }; diff --git a/tests/test_wav.cpp b/tests/test_wav.cpp index 905a60de..bcb91753 100644 --- a/tests/test_wav.cpp +++ b/tests/test_wav.cpp @@ -14,23 +14,66 @@ using namespace TagLib; class TestWAV : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(TestWAV); - CPPUNIT_TEST(testLength); + CPPUNIT_TEST(testPCMProperties); + CPPUNIT_TEST(testALAWProperties); + CPPUNIT_TEST(testFloatProperties); CPPUNIT_TEST(testZeroSizeDataChunk); CPPUNIT_TEST(testID3v2Tag); CPPUNIT_TEST(testInfoTag); CPPUNIT_TEST(testStripTags); - CPPUNIT_TEST(testFormat); + CPPUNIT_TEST(testDuplicateTags); CPPUNIT_TEST(testFuzzedFile1); CPPUNIT_TEST(testFuzzedFile2); CPPUNIT_TEST_SUITE_END(); public: - void testLength() + void testPCMProperties() { RIFF::WAV::File f(TEST_FILE_PATH_C("empty.wav")); - CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT(f.audioProperties()); CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3675, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(32, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(1000, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->sampleWidth()); + CPPUNIT_ASSERT_EQUAL(3675U, f.audioProperties()->sampleFrames()); + CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->format()); + } + + void testALAWProperties() + { + RIFF::WAV::File f(TEST_FILE_PATH_C("alaw.wav")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3550, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(128, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(8000, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(8, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(8, f.audioProperties()->sampleWidth()); + CPPUNIT_ASSERT_EQUAL(28400U, f.audioProperties()->sampleFrames()); + CPPUNIT_ASSERT_EQUAL(6, f.audioProperties()->format()); + } + + void testFloatProperties() + { + RIFF::WAV::File f(TEST_FILE_PATH_C("float64.wav")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->length()); + 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(64, f.audioProperties()->sampleWidth()); + CPPUNIT_ASSERT_EQUAL(4281U, f.audioProperties()->sampleFrames()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->format()); } void testZeroSizeDataChunk() @@ -142,15 +185,18 @@ public: delete f; } - void testFormat() + void testDuplicateTags() { - RIFF::WAV::File f1(TEST_FILE_PATH_C("empty.wav")); - CPPUNIT_ASSERT_EQUAL(true, f1.isValid()); - CPPUNIT_ASSERT_EQUAL((uint)1, f1.audioProperties()->format()); + RIFF::WAV::File f(TEST_FILE_PATH_C("duplicate_tags.wav")); - RIFF::WAV::File f2(TEST_FILE_PATH_C("alaw.wav")); - CPPUNIT_ASSERT_EQUAL(true, f2.isValid()); - CPPUNIT_ASSERT_EQUAL((uint)6, f2.audioProperties()->format()); + // duplicate_tags.wav has duplicate ID3v2/INFO tags. + // title() returns "Title2" if can't skip the second tag. + + CPPUNIT_ASSERT(f.hasID3v2Tag()); + CPPUNIT_ASSERT_EQUAL(String("Title1"), f.ID3v2Tag()->title()); + + CPPUNIT_ASSERT(f.hasInfoTag()); + CPPUNIT_ASSERT_EQUAL(String("Title1"), f.InfoTag()->title()); } void testFuzzedFile1() diff --git a/tests/test_wavpack.cpp b/tests/test_wavpack.cpp index aa701631..5befbff3 100644 --- a/tests/test_wavpack.cpp +++ b/tests/test_wavpack.cpp @@ -1,9 +1,9 @@ -#include #include #include #include #include #include +#include #include "utils.h" using namespace std; @@ -12,29 +12,67 @@ using namespace TagLib; class TestWavPack : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(TestWavPack); - CPPUNIT_TEST(testBasic); - CPPUNIT_TEST(testLengthScan); + CPPUNIT_TEST(testNoLengthProperties); + CPPUNIT_TEST(testMultiChannelProperties); + CPPUNIT_TEST(testTaggedProperties); + CPPUNIT_TEST(testFuzzedFile); CPPUNIT_TEST_SUITE_END(); public: - void testBasic() + void testNoLengthProperties() { WavPack::File f(TEST_FILE_PATH_C("no_length.wv")); - WavPack::AudioProperties *props = f.audioProperties(); - CPPUNIT_ASSERT_EQUAL(44100, props->sampleRate()); - CPPUNIT_ASSERT_EQUAL(2, props->channels()); - CPPUNIT_ASSERT_EQUAL(1, props->bitrate()); - CPPUNIT_ASSERT_EQUAL(0x407, props->version()); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3705, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(1, 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(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(163392U, f.audioProperties()->sampleFrames()); + CPPUNIT_ASSERT_EQUAL(1031, f.audioProperties()->version()); } - void testLengthScan() + void testMultiChannelProperties() { - WavPack::File f(TEST_FILE_PATH_C("no_length.wv")); - WavPack::AudioProperties *props = f.audioProperties(); - CPPUNIT_ASSERT_EQUAL(4, props->length()); + WavPack::File f(TEST_FILE_PATH_C("four_channels.wv")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3833, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(112, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(4, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(false, f.audioProperties()->isLossless()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(169031U, f.audioProperties()->sampleFrames()); + CPPUNIT_ASSERT_EQUAL(1031, f.audioProperties()->version()); } + void testTaggedProperties() + { + WavPack::File f(TEST_FILE_PATH_C("tagged.wv")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3550, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(172, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(false, f.audioProperties()->isLossless()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(156556U, f.audioProperties()->sampleFrames()); + CPPUNIT_ASSERT_EQUAL(1031, f.audioProperties()->version()); + } + + void testFuzzedFile() + { + WavPack::File f(TEST_FILE_PATH_C("infloop.wv")); + CPPUNIT_ASSERT(f.isValid()); + } }; CPPUNIT_TEST_SUITE_REGISTRATION(TestWavPack); diff --git a/tests/utils.h b/tests/utils.h index dfe6c4c3..de51c04c 100644 --- a/tests/utils.h +++ b/tests/utils.h @@ -27,24 +27,27 @@ inline string testFilePath(const string &filename) inline string copyFile(const string &filename, const string &ext) { - char *newname_c = tempnam(NULL, NULL); - string newname = string(newname_c) + ext; - free(newname_c); - string oldname = testFilePath(filename) + ext; + char testFileName[1024]; + #ifdef _WIN32 - CopyFileA(oldname.c_str(), newname.c_str(), FALSE); - SetFileAttributesA(newname.c_str(), GetFileAttributesA(newname.c_str()) & ~FILE_ATTRIBUTE_READONLY); + GetTempPathA(sizeof(testFileName), testFileName); + GetTempFileNameA(testFileName, "tag", 0, testFileName); + DeleteFileA(testFileName); +# if defined(_MSC_VER) && _MSC_VER > 1500 + strcat_s(testFileName, ext.c_str()); +# else + strcat(testFileName, ext.c_str()); +# endif #else - char buffer[4096]; - int bytes; - int inf = open(oldname.c_str(), O_RDONLY); - int outf = open(newname.c_str(), O_CREAT | O_EXCL | O_RDWR, S_IRUSR | S_IWUSR); - while((bytes = read(inf, buffer, sizeof(buffer))) > 0) - write(outf, buffer, bytes); - close(outf); - close(inf); + snprintf(testFileName, sizeof(testFileName), "/%s/taglib-test-XXXXXX%s", P_tmpdir, ext.c_str()); + static_cast(mkstemps(testFileName, 6)); #endif - return newname; + + string sourceFileName = testFilePath(filename) + ext; + ifstream source(sourceFileName.c_str(), std::ios::binary); + ofstream destination(testFileName, std::ios::binary); + destination << source.rdbuf(); + return string(testFileName); } inline void deleteFile(const string &filename) @@ -75,7 +78,7 @@ inline bool fileEqual(const string &filename1, const string &filename2) if(n1 == 0) break; - if(memcmp(buf1, buf2, n1) != 0) return false; + if(memcmp(buf1, buf2, static_cast(n1)) != 0) return false; } return stream1.good() == stream2.good();