diff --git a/CMakeLists.txt b/CMakeLists.txt index ea031b6b..6b018ff2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,6 +6,8 @@ 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) + option(ENABLE_STATIC "Make static version of libtag" OFF) if(ENABLE_STATIC) add_definitions(-DTAGLIB_STATIC) @@ -85,6 +87,11 @@ if(NOT WIN32 AND NOT BUILD_FRAMEWORK) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/taglib.pc DESTINATION ${LIB_INSTALL_DIR}/pkgconfig) endif() +if(NOT HAVE_ZLIB AND ZLIB_SOURCE) + set(HAVE_ZLIB 1) + set(HAVE_ZLIB_SOURCE 1) +endif() + include_directories(${CMAKE_CURRENT_BINARY_DIR}) configure_file(config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config.h) diff --git a/ConfigureChecks.cmake b/ConfigureChecks.cmake index c22fce5b..0ad56209 100644 --- a/ConfigureChecks.cmake +++ b/ConfigureChecks.cmake @@ -6,8 +6,9 @@ include(CheckLibraryExists) include(CheckTypeSize) include(CheckCXXSourceCompiles) include(TestBigEndian) +include(TestFloatFormat) -# Check if the size of integral types are suitable. +# Check if the size of numeric types are suitable. check_type_size("short" SIZEOF_SHORT) if(NOT ${SIZEOF_SHORT} EQUAL 2) @@ -29,6 +30,16 @@ if(${SIZEOF_WCHAR_T} LESS 2) 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.") +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) @@ -39,6 +50,17 @@ 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.") +endif() + # Determine which kind of byte swap functions your compiler supports. # GCC's __builtin_bswap* should be checked individually @@ -254,11 +276,13 @@ check_cxx_source_compiles(" # Determine whether zlib is installed. -find_package(ZLIB) -if(ZLIB_FOUND) - set(HAVE_ZLIB 1) -else() - set(HAVE_ZLIB 0) +if(NOT ZLIB_SOURCE) + find_package(ZLIB) + if(ZLIB_FOUND) + set(HAVE_ZLIB 1) + else() + set(HAVE_ZLIB 0) + endif() endif() # Determine whether CppUnit is installed. diff --git a/INSTALL b/INSTALL index 5e36d30c..205b4d31 100644 --- a/INSTALL +++ b/INSTALL @@ -72,6 +72,8 @@ Useful configuration options used with CMake (GUI and/or Command line): Assumes parent of: \include and \lib. ZLIB_INCLUDE_DIR= Where to find ZLib's Include directory. ZLIB_LIBRARY= Where to find ZLib's Library. + ZLIB_SOURCE= Where to find ZLib's Source Code. + Alternative to ZLIB_INCLUDE_DIR and ZLIB_LIBRARY. CMAKE_INSTALL_PREFIX= Where to install Taglib. CMAKE_BUILD_TYPE= Release, Debug, etc ... (Not available in MSVC) diff --git a/cmake/TestFloatFormat.c b/cmake/TestFloatFormat.c new file mode 100644 index 00000000..5f18aafb --- /dev/null +++ b/cmake/TestFloatFormat.c @@ -0,0 +1,13 @@ +int main() +{ + 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 + }; + + return 0; +} diff --git a/cmake/modules/TestFloatFormat.cmake b/cmake/modules/TestFloatFormat.cmake new file mode 100644 index 00000000..0e90cde6 --- /dev/null +++ b/cmake/modules/TestFloatFormat.cmake @@ -0,0 +1,60 @@ +# 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_${FP_IEEE754} "${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_${FP_IEEE754}) + + # 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 0384fa31..3efbfa72 100644 --- a/config.h.cmake +++ b/config.h.cmake @@ -1,9 +1,13 @@ /* config.h. Generated by cmake from config.h.cmake */ -/* Indicates the byte order of your target system */ +/* 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 your compiler supports some byte swap functions */ #cmakedefine HAVE_GCC_BYTESWAP_16 1 #cmakedefine HAVE_GCC_BYTESWAP_32 1 diff --git a/taglib/CMakeLists.txt b/taglib/CMakeLists.txt index 8694777b..ccda648c 100644 --- a/taglib/CMakeLists.txt +++ b/taglib/CMakeLists.txt @@ -31,6 +31,8 @@ include_directories( if(ZLIB_FOUND) include_directories(${ZLIB_INCLUDE_DIR}) +elseif(HAVE_ZLIB_SOURCE) + include_directories(${ZLIB_SOURCE}) endif() set(tag_HDRS @@ -82,6 +84,8 @@ set(tag_HDRS mpeg/id3v2/frames/unknownframe.h mpeg/id3v2/frames/unsynchronizedlyricsframe.h mpeg/id3v2/frames/urllinkframe.h + mpeg/id3v2/frames/chapterframe.h + mpeg/id3v2/frames/tableofcontentsframe.h ogg/oggfile.h ogg/oggpage.h ogg/oggpageheader.h @@ -182,6 +186,8 @@ set(frames_SRCS mpeg/id3v2/frames/unknownframe.cpp mpeg/id3v2/frames/unsynchronizedlyricsframe.cpp mpeg/id3v2/frames/urllinkframe.cpp + mpeg/id3v2/frames/chapterframe.cpp + mpeg/id3v2/frames/tableofcontentsframe.cpp ) set(ogg_SRCS @@ -300,6 +306,16 @@ set(dsf_SRCS dsf/dsfproperties.cpp ) +set(ebml_SRCS + ebml/ebmlfile.cpp + ebml/ebmlelement.cpp +) + +set(matroska_SRCS + ebml/matroska/ebmlmatroskafile.cpp + ebml/matroska/ebmlmatroskaaudio.cpp +) + set(toolkit_SRCS toolkit/tstring.cpp toolkit/tstringlist.cpp @@ -317,15 +333,17 @@ set(toolkit_SRCS toolkit/unicode.cpp ) -set(ebml_SRCS - ebml/ebmlfile.cpp - ebml/ebmlelement.cpp -) - -set(matroska_SRCS - ebml/matroska/ebmlmatroskafile.cpp - ebml/matroska/ebmlmatroskaaudio.cpp -) +if(HAVE_ZLIB_SOURCE) + set(zlib_SRCS + ${ZLIB_SOURCE}/adler32.c + ${ZLIB_SOURCE}/crc32.c + ${ZLIB_SOURCE}/inffast.c + ${ZLIB_SOURCE}/inflate.c + ${ZLIB_SOURCE}/inftrees.c + ${ZLIB_SOURCE}/uncompr.c + ${ZLIB_SOURCE}/zutil.c + ) +endif() set(tag_LIB_SRCS ${mpeg_SRCS} ${id3v1_SRCS} ${id3v2_SRCS} ${frames_SRCS} ${ogg_SRCS} @@ -339,10 +357,10 @@ set(tag_LIB_SRCS audioproperties.cpp ) -add_library(tag ${tag_LIB_SRCS} ${tag_HDRS}) +add_library(tag ${tag_LIB_SRCS} ${zlib_SRCS} ${tag_HDRS}) if(ZLIB_FOUND) - target_link_libraries(tag ${ZLIB_LIBRARIES}) + target_link_libraries(tag ${ZLIB_LIBRARIES}) endif() set_target_properties(tag PROPERTIES @@ -358,10 +376,9 @@ if(BUILD_FRAMEWORK) endif() install(TARGETS tag - FRAMEWORK DESTINATION ${FRAMEWORK_INSTALL_DIR} - LIBRARY DESTINATION ${LIB_INSTALL_DIR} - RUNTIME DESTINATION ${BIN_INSTALL_DIR} - ARCHIVE DESTINATION ${LIB_INSTALL_DIR} - PUBLIC_HEADER DESTINATION ${INCLUDE_INSTALL_DIR}/taglib + FRAMEWORK DESTINATION ${FRAMEWORK_INSTALL_DIR} + LIBRARY DESTINATION ${LIB_INSTALL_DIR} + RUNTIME DESTINATION ${BIN_INSTALL_DIR} + ARCHIVE DESTINATION ${LIB_INSTALL_DIR} + PUBLIC_HEADER DESTINATION ${INCLUDE_INSTALL_DIR}/taglib ) - diff --git a/taglib/ape/apetag.cpp b/taglib/ape/apetag.cpp index 3352a5f9..f7bdcf3d 100644 --- a/taglib/ape/apetag.cpp +++ b/taglib/ape/apetag.cpp @@ -89,35 +89,35 @@ String APE::Tag::title() const { if(d->itemListMap["TITLE"].isEmpty()) return String::null; - return d->itemListMap["TITLE"].toString(); + return d->itemListMap["TITLE"].values().toString(); } String APE::Tag::artist() const { if(d->itemListMap["ARTIST"].isEmpty()) return String::null; - return d->itemListMap["ARTIST"].toString(); + return d->itemListMap["ARTIST"].values().toString(); } String APE::Tag::album() const { if(d->itemListMap["ALBUM"].isEmpty()) return String::null; - return d->itemListMap["ALBUM"].toString(); + return d->itemListMap["ALBUM"].values().toString(); } String APE::Tag::comment() const { if(d->itemListMap["COMMENT"].isEmpty()) return String::null; - return d->itemListMap["COMMENT"].toString(); + return d->itemListMap["COMMENT"].values().toString(); } String APE::Tag::genre() const { if(d->itemListMap["GENRE"].isEmpty()) return String::null; - return d->itemListMap["GENRE"].toString(); + return d->itemListMap["GENRE"].values().toString(); } TagLib::uint APE::Tag::year() const diff --git a/taglib/mpeg/id3v1/id3v1genres.cpp b/taglib/mpeg/id3v1/id3v1genres.cpp index eba4c526..074c8bff 100644 --- a/taglib/mpeg/id3v1/id3v1genres.cpp +++ b/taglib/mpeg/id3v1/id3v1genres.cpp @@ -30,7 +30,7 @@ using namespace TagLib; namespace TagLib { namespace ID3v1 { - static const int genresSize = 148; + static const int genresSize = 192; static const String genres[] = { "Blues", "Classic Rock", @@ -179,7 +179,51 @@ namespace TagLib { "Thrash Metal", "Anime", "Jpop", - "Synthpop" + "Synthpop", + "Abstract", + "Art Rock", + "Baroque", + "Bhangra", + "Big Beat", + "Breakbeat", + "Chillout", + "Downtempo", + "Dub", + "EBM", + "Eclectic", + "Electro", + "Electroclash", + "Emo", + "Experimental", + "Garage", + "Global", + "IDM", + "Illbient", + "Industro-Goth", + "Jam Band", + "Krautrock", + "Leftfield", + "Lounge", + "Math Rock", + "New Romantic", + "Nu-Breakz", + "Post-Punk", + "Post-Rock", + "Psytrance", + "Shoegaze", + "Space Rock", + "Trop Rock", + "World Music", + "Neoclassical", + "Audiobook", + "Audio Theatre", + "Neue Deutsche Welle", + "Podcast", + "Indie Rock", + "G-Funk", + "Dubstep", + "Garage Rock", + "Psybient" }; } } diff --git a/taglib/mpeg/id3v1/id3v1genres.h b/taglib/mpeg/id3v1/id3v1genres.h index 271f7259..0a0dd970 100644 --- a/taglib/mpeg/id3v1/id3v1genres.h +++ b/taglib/mpeg/id3v1/id3v1genres.h @@ -49,7 +49,7 @@ namespace TagLib { /*! * Returns the name of the genre at \a index in the ID3v1 genre list. If - * \a index is out of range -- less than zero or greater than 146 -- a null + * \a index is out of range -- less than zero or greater than 191 -- a null * string will be returned. */ String TAGLIB_EXPORT genre(int index); diff --git a/taglib/mpeg/id3v2/frames/chapterframe.cpp b/taglib/mpeg/id3v2/frames/chapterframe.cpp new file mode 100644 index 00000000..1f7a4a07 --- /dev/null +++ b/taglib/mpeg/id3v2/frames/chapterframe.cpp @@ -0,0 +1,266 @@ +/*************************************************************************** + copyright : (C) 2013 by Lukas Krejci + email : krejclu6@fel.cvut.cz + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include +#include +#include +#include + +#include "chapterframe.h" + +using namespace TagLib; +using namespace ID3v2; + +class ChapterFrame::ChapterFramePrivate +{ +public: + ByteVector elementID; + uint startTime; + uint endTime; + uint startOffset; + uint endOffset; + const FrameFactory *factory; + FrameListMap embeddedFrameListMap; + FrameList embeddedFrameList; +}; + +//////////////////////////////////////////////////////////////////////////////// +// public methods +//////////////////////////////////////////////////////////////////////////////// + +ChapterFrame::ChapterFrame(const ByteVector &data) : + ID3v2::Frame(data) +{ + d = new ChapterFramePrivate; + d->factory = FrameFactory::instance(); + setData(data); +} + +ChapterFrame::ChapterFrame(const ByteVector &eID, const uint &sT, const uint &eT, const uint &sO, const uint &eO, const FrameList &eF) : + 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::Iterator it = l.begin(); it != l.end(); ++it) + addEmbeddedFrame(*it); + d->factory = FrameFactory::instance(); +} + +ChapterFrame::~ChapterFrame() +{ + delete d; +} + +ByteVector ChapterFrame::elementID() const +{ + return d->elementID; +} + +uint ChapterFrame::startTime() const +{ + return d->startTime; +} + +uint ChapterFrame::endTime() const +{ + return d->endTime; +} + +uint ChapterFrame::startOffset() const +{ + return d->startOffset; +} + +uint ChapterFrame::endOffset() const +{ + return d->endOffset; +} + +void ChapterFrame::setElementID(const ByteVector &eID) +{ + d->elementID = eID; + if(eID.at(eID.size() - 1) != char(0)) + d->elementID.append(char(0)); +} + +void ChapterFrame::setStartTime(const uint &sT) +{ + d->startTime = sT; +} + +void ChapterFrame::setEndTime(const uint &eT) +{ + d->endTime = eT; +} + +void ChapterFrame::setStartOffset(const uint &sO) +{ + d->startOffset = sO; +} + +void ChapterFrame::setEndOffset(const uint &eO) +{ + d->endOffset = eO; +} + +const FrameListMap &ChapterFrame::embeddedFrameListMap() const +{ + return d->embeddedFrameListMap; +} + +const FrameList &ChapterFrame::embeddedFrameList() const +{ + return d->embeddedFrameList; +} + +const FrameList &ChapterFrame::embeddedFrameList(const ByteVector &frameID) const +{ + return d->embeddedFrameListMap[frameID]; +} + +void ChapterFrame::addEmbeddedFrame(Frame *frame) +{ + d->embeddedFrameList.append(frame); + d->embeddedFrameListMap[frame->frameID()].append(frame); +} + +void ChapterFrame::removeEmbeddedFrame(Frame *frame, bool del) +{ + // remove the frame from the frame list + FrameList::Iterator it = d->embeddedFrameList.find(frame); + d->embeddedFrameList.erase(it); + + // ...and from the frame list map + it = d->embeddedFrameListMap[frame->frameID()].find(frame); + d->embeddedFrameListMap[frame->frameID()].erase(it); + + // ...and delete as desired + if(del) + delete frame; +} + +void ChapterFrame::removeEmbeddedFrames(const ByteVector &id) +{ + FrameList l = d->embeddedFrameListMap[id]; + for(FrameList::Iterator it = l.begin(); it != l.end(); ++it) + removeEmbeddedFrame(*it, true); +} + +String ChapterFrame::toString() const +{ + return String::null; +} + +PropertyMap ChapterFrame::asProperties() const +{ + PropertyMap map; + + map.unsupportedData().append(frameID() + String("/") + d->elementID); + + return map; +} + +ChapterFrame *ChapterFrame::findByElementID(const ID3v2::Tag *tag, const ByteVector &eID) // static +{ + ID3v2::FrameList comments = tag->frameList("CHAP"); + + for(ID3v2::FrameList::ConstIterator it = comments.begin(); + it != comments.end(); + ++it) + { + ChapterFrame *frame = dynamic_cast(*it); + if(frame && frame->elementID() == eID) + return frame; + } + + return 0; +} + +void ChapterFrame::parseFields(const ByteVector &data) +{ + uint size = data.size(); + if(size < 18) { + debug("A CHAP frame must contain at least 18 bytes (1 byte element ID terminated by null and 4x4 bytes for start and end time and offset)."); + return; + } + + size_t pos = 0, 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); + pos += 4; + d->startOffset = data.toUInt32BE(pos); + pos += 4; + d->endOffset = data.toUInt32BE(pos); + pos += 4; + size -= pos; + while((uint)embPos < size - Frame::headerSize(4)) + { + Frame *frame = d->factory->createFrame(data.mid(pos + embPos)); + + if(!frame) + return; + + // Checks to make sure that frame parsed correctly. + if(frame->size() <= 0) { + delete frame; + return; + } + + embPos += frame->size() + Frame::headerSize(4); + addEmbeddedFrame(frame); + } +} + +ByteVector ChapterFrame::renderFields() const +{ + ByteVector data; + + data.append(d->elementID); + data.append(ByteVector::fromUInt32BE(d->startTime)); + data.append(ByteVector::fromUInt32BE(d->endTime)); + data.append(ByteVector::fromUInt32BE(d->startOffset)); + data.append(ByteVector::fromUInt32BE(d->endOffset)); + FrameList l = d->embeddedFrameList; + for(FrameList::Iterator it = l.begin(); it != l.end(); ++it) + data.append((*it)->render()); + + return data; +} + +ChapterFrame::ChapterFrame(const ByteVector &data, Header *h) : + Frame(h) +{ + d = new ChapterFramePrivate; + d->factory = FrameFactory::instance(); + parseFields(fieldData(data)); +} diff --git a/taglib/mpeg/id3v2/frames/chapterframe.h b/taglib/mpeg/id3v2/frames/chapterframe.h new file mode 100644 index 00000000..84b42137 --- /dev/null +++ b/taglib/mpeg/id3v2/frames/chapterframe.h @@ -0,0 +1,242 @@ +/*************************************************************************** + copyright : (C) 2013 by Lukas Krejci + email : krejclu6@fel.cvut.cz + ***************************************************************************/ + +/*************************************************************************** + * 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_CHAPTERFRAME +#define TAGLIB_CHAPTERFRAME + +#include "id3v2tag.h" +#include "id3v2frame.h" +#include "taglib_export.h" + +namespace TagLib { + + namespace ID3v2 { + + /*! + * This is an implementation of ID3v2 chapter frames. The purpose of this + * frame is to describe a single chapter within an audio file. + */ + + //! An implementation of ID3v2 chapter frames + + class TAGLIB_EXPORT ChapterFrame : public ID3v2::Frame + { + friend class FrameFactory; + + public: + /*! + * Creates a chapter frame based on \a data. + */ + ChapterFrame(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. + */ + ChapterFrame(const ByteVector &eID, const uint &sT, const uint &eT, const uint &sO, const uint &eO, const FrameList &eF); + + /*! + * Destroys the frame. + */ + ~ChapterFrame(); + + /*! + * Returns the element ID of the frame. Element ID + * is a null terminated string, however it's not human-readable. + * + * \see setElementID() + */ + ByteVector elementID() const; + + /*! + * Returns time of chapter's start (in miliseconds). + * + * \see setStartTime() + */ + uint startTime() const; + + /*! + * Returns time of chapter's end (in miliseconds). + * + * \see setEndTime() + */ + uint endTime() const; + + /*! + * Returns zero based byte offset (count of bytes from the beginning + * of the audio file) of chapter's start. + * + * \note If returned value is 0xFFFFFFFF, start time should be used instead. + * \see setStartOffset() + */ + uint startOffset() const; + + /*! + * Returns zero based byte offset (count of bytes from the beginning + * of the audio file) of chapter's end. + * + * \note If returned value is 0xFFFFFFFF, end time should be used instead. + * \see setEndOffset() + */ + uint endOffset() const; + + /*! + * Sets the element ID of the frame to \a eID. If \a eID isn't + * null terminated, a null char is appended automatically. + * + * \see elementID() + */ + void setElementID(const ByteVector &eID); + + /*! + * Sets time of chapter's start (in miliseconds) to \a sT. + * + * \see startTime() + */ + void setStartTime(const uint &sT); + + /*! + * Sets time of chapter's end (in miliseconds) to \a eT. + * + * \see endTime() + */ + void setEndTime(const uint &eT); + + /*! + * Sets zero based byte offset (count of bytes from the beginning + * of the audio file) of chapter's start to \a sO. + * + * \see startOffset() + */ + void setStartOffset(const uint &sO); + + /*! + * Sets zero based byte offset (count of bytes from the beginning + * of the audio file) of chapter's end to \a eO. + * + * \see endOffset() + */ + void setEndOffset(const uint &eO); + + /*! + * Returns a reference to the frame list map. This is an FrameListMap of + * all of the frames embedded in the CHAP frame. + * + * This is the most convenient structure for accessing the CHAP frame's + * embedded frames. Many frame types allow multiple instances of the same + * frame type so this is a map of lists. In most cases however there will + * only be a single frame of a certain type. + * + * \warning You should not modify this data structure directly, instead + * use addEmbeddedFrame() and removeEmbeddedFrame(). + * + * \see embeddedFrameList() + */ + const FrameListMap &embeddedFrameListMap() const; + + /*! + * Returns a reference to the embedded frame list. This is an FrameList + * of all of the frames embedded in the CHAP frame in the order that they + * were parsed. + * + * This can be useful if for example you want iterate over the CHAP frame's + * embedded frames in the order that they occur in the CHAP frame. + * + * \warning You should not modify this data structure directly, instead + * use addEmbeddedFrame() and removeEmbeddedFrame(). + */ + const FrameList &embeddedFrameList() const; + + /*! + * Returns the embedded frame list for frames with the id \a frameID + * or an empty list if there are no embedded frames of that type. This + * is just a convenience and is equivalent to: + * + * \code + * embeddedFrameListMap()[frameID]; + * \endcode + * + * \see embeddedFrameListMap() + */ + const FrameList &embeddedFrameList(const ByteVector &frameID) const; + + /*! + * Add an embedded frame to the CHAP frame. At this point the CHAP frame + * takes ownership of the embedded frame and will handle freeing its memory. + * + * \note Using this method will invalidate any pointers on the list + * returned by embeddedFrameList() + */ + void addEmbeddedFrame(Frame *frame); + + /*! + * Remove an embedded frame from the CHAP frame. If \a del is true the frame's + * memory will be freed; if it is false, it must be deleted by the user. + * + * \note Using this method will invalidate any pointers on the list + * returned by embeddedFrameList() + */ + void removeEmbeddedFrame(Frame *frame, bool del = true); + + /*! + * Remove all embedded frames of type \a id from the CHAP frame and free their + * memory. + * + * \note Using this method will invalidate any pointers on the list + * returned by embeddedFrameList() + */ + void removeEmbeddedFrames(const ByteVector &id); + + virtual String toString() const; + + PropertyMap asProperties() const; + + /*! + * CHAP frames each have a unique element ID. This searches for a CHAP + * frame with the element ID \a eID and returns a pointer to it. This + * can be used to link CTOC and CHAP frames together. + * + * \see elementID() + */ + static ChapterFrame *findByElementID(const Tag *tag, const ByteVector &eID); + + protected: + virtual void parseFields(const ByteVector &data); + virtual ByteVector renderFields() const; + + private: + ChapterFrame(const ChapterFrame &); + ChapterFrame &operator=(const ChapterFrame &); + + ChapterFrame(const ByteVector &data, Header *h); + + class ChapterFramePrivate; + ChapterFramePrivate *d; + }; + } +} + +#endif diff --git a/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp b/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp new file mode 100644 index 00000000..ad50c654 --- /dev/null +++ b/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp @@ -0,0 +1,293 @@ +/*************************************************************************** + copyright : (C) 2013 by Lukas Krejci + email : krejclu6@fel.cvut.cz + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include +#include +#include + +#include "tableofcontentsframe.h" + +using namespace TagLib; +using namespace ID3v2; + +class TableOfContentsFrame::TableOfContentsFramePrivate +{ +public: + ByteVector elementID; + bool isTopLevel; + bool isOrdered; + ByteVectorList childElements; + const FrameFactory *factory; + FrameListMap embeddedFrameListMap; + FrameList embeddedFrameList; +}; + +//////////////////////////////////////////////////////////////////////////////// +// public methods +//////////////////////////////////////////////////////////////////////////////// + +TableOfContentsFrame::TableOfContentsFrame(const ByteVector &data) : + ID3v2::Frame(data) +{ + d = new TableOfContentsFramePrivate; + d->factory = FrameFactory::instance(); + setData(data); +} + +TableOfContentsFrame::TableOfContentsFrame(const ByteVector &eID, const ByteVectorList &ch, const FrameList &eF) : + ID3v2::Frame("CTOC") +{ + d = new TableOfContentsFramePrivate; + d->elementID = eID; + d->childElements = ch; + FrameList l = eF; + for(FrameList::Iterator it = l.begin(); it != l.end(); ++it) + addEmbeddedFrame(*it); + d->factory = FrameFactory::instance(); +} + +TableOfContentsFrame::~TableOfContentsFrame() +{ + delete d; +} + +ByteVector TableOfContentsFrame::elementID() const +{ + return d->elementID; +} + +bool TableOfContentsFrame::isTopLevel() const +{ + return d->isTopLevel; +} + +bool TableOfContentsFrame::isOrdered() const +{ + return d->isOrdered; +} + +uint TableOfContentsFrame::entryCount() const +{ + return d->childElements.size(); +} + +ByteVectorList TableOfContentsFrame::childElements() const +{ + return d->childElements; +} + +void TableOfContentsFrame::setElementID(const ByteVector &eID) +{ + d->elementID = eID; + if(eID.at(eID.size() - 1) != char(0)) + d->elementID.append(char(0)); +} + +void TableOfContentsFrame::setIsTopLevel(const bool &t) +{ + d->isTopLevel = t; +} + +void TableOfContentsFrame::setIsOrdered(const bool &o) +{ + d->isOrdered = o; +} + +void TableOfContentsFrame::setChildElements(const ByteVectorList &l) +{ + d->childElements = l; +} + +void TableOfContentsFrame::addChildElement(const ByteVector &cE) +{ + d->childElements.append(cE); +} + +void TableOfContentsFrame::removeChildElement(const ByteVector &cE) +{ + ByteVectorList::Iterator it = d->childElements.find(cE); + d->childElements.erase(it); +} + +const FrameListMap &TableOfContentsFrame::embeddedFrameListMap() const +{ + return d->embeddedFrameListMap; +} + +const FrameList &TableOfContentsFrame::embeddedFrameList() const +{ + return d->embeddedFrameList; +} + +const FrameList &TableOfContentsFrame::embeddedFrameList(const ByteVector &frameID) const +{ + return d->embeddedFrameListMap[frameID]; +} + +void TableOfContentsFrame::addEmbeddedFrame(Frame *frame) +{ + d->embeddedFrameList.append(frame); + d->embeddedFrameListMap[frame->frameID()].append(frame); +} + +void TableOfContentsFrame::removeEmbeddedFrame(Frame *frame, bool del) +{ + // remove the frame from the frame list + FrameList::Iterator it = d->embeddedFrameList.find(frame); + d->embeddedFrameList.erase(it); + + // ...and from the frame list map + it = d->embeddedFrameListMap[frame->frameID()].find(frame); + d->embeddedFrameListMap[frame->frameID()].erase(it); + + // ...and delete as desired + if(del) + delete frame; +} + +void TableOfContentsFrame::removeEmbeddedFrames(const ByteVector &id) +{ + FrameList l = d->embeddedFrameListMap[id]; + for(FrameList::Iterator it = l.begin(); it != l.end(); ++it) + removeEmbeddedFrame(*it, true); +} + +String TableOfContentsFrame::toString() const +{ + return String::null; +} + +PropertyMap TableOfContentsFrame::asProperties() const +{ + PropertyMap map; + + map.unsupportedData().append(frameID() + String("/") + d->elementID); + + return map; +} + +TableOfContentsFrame *TableOfContentsFrame::findByElementID(const ID3v2::Tag *tag, const ByteVector &eID) // static +{ + ID3v2::FrameList tablesOfContents = tag->frameList("CTOC"); + + for(ID3v2::FrameList::ConstIterator it = tablesOfContents.begin(); + it != tablesOfContents.end(); + ++it) + { + TableOfContentsFrame *frame = dynamic_cast(*it); + if(frame && frame->elementID() == eID) + return frame; + } + + return 0; +} + +TableOfContentsFrame *TableOfContentsFrame::findTopLevel(const ID3v2::Tag *tag) // static +{ + ID3v2::FrameList tablesOfContents = tag->frameList("CTOC"); + + for(ID3v2::FrameList::ConstIterator it = tablesOfContents.begin(); + it != tablesOfContents.end(); + ++it) + { + TableOfContentsFrame *frame = dynamic_cast(*it); + if(frame && frame->isTopLevel() == true) + return frame; + } + + return 0; +} + +void TableOfContentsFrame::parseFields(const ByteVector &data) +{ + uint size = data.size(); + if(size < 6) { + debug("A CTOC frame must contain at least 6 bytes (1 byte element ID terminated by null, 1 byte flags, 1 byte entry count and 1 byte child element ID terminated by null."); + return; + } + + size_t pos = 0, 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; + uint entryCount = data.at(pos++); + for(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; + while((uint)embPos < size - Frame::headerSize(4)) + { + Frame *frame = d->factory->createFrame(data.mid(pos + embPos)); + + if(!frame) + return; + + // Checks to make sure that frame parsed correctly. + if(frame->size() <= 0) { + delete frame; + return; + } + + embPos += frame->size() + Frame::headerSize(4); + addEmbeddedFrame(frame); + } +} + +ByteVector TableOfContentsFrame::renderFields() const +{ + ByteVector data; + + data.append(d->elementID); + char flags = 0; + if(d->isTopLevel) + flags += 2; + if(d->isOrdered) + flags += 1; + data.append(flags); + data.append((char)(entryCount())); + ByteVectorList::ConstIterator it = d->childElements.begin(); + while(it != d->childElements.end()) { + data.append(*it); + it++; + } + FrameList l = d->embeddedFrameList; + for(FrameList::Iterator it = l.begin(); it != l.end(); ++it) + data.append((*it)->render()); + + return data; +} + +TableOfContentsFrame::TableOfContentsFrame(const ByteVector &data, Header *h) : + Frame(h) +{ + d = new TableOfContentsFramePrivate; + d->factory = FrameFactory::instance(); + parseFields(fieldData(data)); +} diff --git a/taglib/mpeg/id3v2/frames/tableofcontentsframe.h b/taglib/mpeg/id3v2/frames/tableofcontentsframe.h new file mode 100644 index 00000000..bf578d42 --- /dev/null +++ b/taglib/mpeg/id3v2/frames/tableofcontentsframe.h @@ -0,0 +1,255 @@ +/*************************************************************************** + copyright : (C) 2013 by Lukas Krejci + email : krejclu6@fel.cvut.cz + ***************************************************************************/ + +/*************************************************************************** + * 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_TABLEOFCONTENTSFRAME +#define TAGLIB_TABLEOFCONTENTSFRAME + +#include "id3v2tag.h" +#include "id3v2frame.h" + +namespace TagLib { + + namespace ID3v2 { + + /*! + * This is an implementation of ID3v2 table of contents frames. Purpose + * of this frame is to allow a table of contents to be defined. + */ + + //! An implementation of ID3v2 table of contents frames + + class TAGLIB_EXPORT TableOfContentsFrame : public ID3v2::Frame + { + friend class FrameFactory; + + public: + /*! + * Creates a table of contents frame based on \a data. + */ + TableOfContentsFrame(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. + */ + TableOfContentsFrame(const ByteVector &eID, const ByteVectorList &ch, const FrameList &eF); + + /*! + * Destroys the frame. + */ + ~TableOfContentsFrame(); + + /*! + * Returns the elementID of the frame. Element ID + * is a null terminated string, however it's not human-readable. + * + * \see setElementID() + */ + ByteVector elementID() const; + + /*! + * Returns true, if the frame is top-level (doen't have + * any parent CTOC frame). + * + * \see setIsTopLevel() + */ + bool isTopLevel() const; + + /*! + * Returns true, if the child elements list entries + * are ordered. + * + * \see setIsOrdered() + */ + bool isOrdered() const; + + /*! + * Returns count of child elements of the frame. It allways + * corresponds to size of child elements list. + * + * \see childElements() + */ + uint entryCount() const; + + /*! + * Returns list of child elements of the frame. + * + * \see setChildElements() + */ + ByteVectorList childElements() const; + + /*! + * Sets the elementID of the frame to \a eID. If \a eID isn't + * null terminated, a null char is appended automatically. + * + * \see elementID() + */ + void setElementID(const ByteVector &eID); + + /*! + * Sets, if the frame is top-level (doen't have + * any parent CTOC frame). + * + * \see isTopLevel() + */ + void setIsTopLevel(const bool &t); + + /*! + * Sets, if the child elements list entries + * are ordered. + * + * \see isOrdered() + */ + void setIsOrdered(const bool &o); + + /*! + * Sets list of child elements of the frame to \a l. + * + * \see childElements() + */ + void setChildElements(const ByteVectorList &l); + + /*! + * Adds \a cE to list of child elements of the frame. + * + * \see childElements() + */ + void addChildElement(const ByteVector &cE); + + /*! + * Removes \a cE to list of child elements of the frame. + * + * \see childElements() + */ + void removeChildElement(const ByteVector &cE); + + /*! + * Returns a reference to the frame list map. This is an FrameListMap of + * all of the frames embedded in the CTOC frame. + * + * This is the most convenient structure for accessing the CTOC frame's + * embedded frames. Many frame types allow multiple instances of the same + * frame type so this is a map of lists. In most cases however there will + * only be a single frame of a certain type. + * + * \warning You should not modify this data structure directly, instead + * use addEmbeddedFrame() and removeEmbeddedFrame(). + * + * \see embeddedFrameList() + */ + const FrameListMap &embeddedFrameListMap() const; + + /*! + * Returns a reference to the embedded frame list. This is an FrameList + * of all of the frames embedded in the CTOC frame in the order that they + * were parsed. + * + * This can be useful if for example you want iterate over the CTOC frame's + * embedded frames in the order that they occur in the CTOC frame. + * + * \warning You should not modify this data structure directly, instead + * use addEmbeddedFrame() and removeEmbeddedFrame(). + */ + const FrameList &embeddedFrameList() const; + + /*! + * Returns the embedded frame list for frames with the id \a frameID + * or an empty list if there are no embedded frames of that type. This + * is just a convenience and is equivalent to: + * + * \code + * embeddedFrameListMap()[frameID]; + * \endcode + * + * \see embeddedFrameListMap() + */ + const FrameList &embeddedFrameList(const ByteVector &frameID) const; + + /*! + * Add an embedded frame to the CTOC frame. At this point the CTOC frame + * takes ownership of the embedded frame and will handle freeing its memory. + * + * \note Using this method will invalidate any pointers on the list + * returned by embeddedFrameList() + */ + void addEmbeddedFrame(Frame *frame); + + /*! + * Remove an embedded frame from the CTOC frame. If \a del is true the frame's + * memory will be freed; if it is false, it must be deleted by the user. + * + * \note Using this method will invalidate any pointers on the list + * returned by embeddedFrameList() + */ + void removeEmbeddedFrame(Frame *frame, bool del = true); + + /*! + * Remove all embedded frames of type \a id from the CTOC frame and free their + * memory. + * + * \note Using this method will invalidate any pointers on the list + * returned by embeddedFrameList() + */ + void removeEmbeddedFrames(const ByteVector &id); + + virtual String toString() const; + + PropertyMap asProperties() const; + + /*! + * CTOC frames each have a unique element ID. This searches for a CTOC + * frame with the element ID \a eID and returns a pointer to it. This + * can be used to link together parent and child CTOC frames. + * + * \see elementID() + */ + static TableOfContentsFrame *findByElementID(const Tag *tag, const ByteVector &eID); + + /*! + * CTOC frames each contain a flag that indicates, if CTOC frame is top-level (there isn't + * any frame, which contains this frame in its child elements list). Only a single frame + * within tag can be top-level. This searches for a top-level CTOC frame. + * + * \see isTopLevel() + */ + static TableOfContentsFrame *findTopLevel(const Tag *tag); + + protected: + virtual void parseFields(const ByteVector &data); + virtual ByteVector renderFields() const; + + private: + TableOfContentsFrame(const TableOfContentsFrame &); + TableOfContentsFrame &operator=(const TableOfContentsFrame &); + + TableOfContentsFrame(const ByteVector &data, Header *h); + + class TableOfContentsFramePrivate; + TableOfContentsFramePrivate *d; + }; + } +} + +#endif diff --git a/taglib/mpeg/id3v2/id3v2framefactory.cpp b/taglib/mpeg/id3v2/id3v2framefactory.cpp index 9e289e07..219971d7 100644 --- a/taglib/mpeg/id3v2/id3v2framefactory.cpp +++ b/taglib/mpeg/id3v2/id3v2framefactory.cpp @@ -47,6 +47,8 @@ #include "frames/ownershipframe.h" #include "frames/synchronizedlyricsframe.h" #include "frames/eventtimingcodesframe.h" +#include "frames/chapterframe.h" +#include "frames/tableofcontentsframe.h" using namespace TagLib; using namespace ID3v2; @@ -274,6 +276,16 @@ Frame *FrameFactory::createFrame(const ByteVector &origData, Header *tagHeader) d->setTextEncoding(f); return f; } + + // Chapter (ID3v2 chapters 1.0) + + if(frameID == "CHAP") + return new ChapterFrame(data, header); + + // Table of contents (ID3v2 chapters 1.0) + + if(frameID == "CTOC") + return new TableOfContentsFrame(data, header); return new UnknownFrame(data, header); } diff --git a/taglib/ogg/xiphcomment.cpp b/taglib/ogg/xiphcomment.cpp index ecb40522..e124bd8a 100644 --- a/taglib/ogg/xiphcomment.cpp +++ b/taglib/ogg/xiphcomment.cpp @@ -63,33 +63,33 @@ String Ogg::XiphComment::title() const { if(d->fieldListMap["TITLE"].isEmpty()) return String::null; - return d->fieldListMap["TITLE"].front(); + return d->fieldListMap["TITLE"].toString(); } String Ogg::XiphComment::artist() const { if(d->fieldListMap["ARTIST"].isEmpty()) return String::null; - return d->fieldListMap["ARTIST"].front(); + return d->fieldListMap["ARTIST"].toString(); } String Ogg::XiphComment::album() const { if(d->fieldListMap["ALBUM"].isEmpty()) return String::null; - return d->fieldListMap["ALBUM"].front(); + return d->fieldListMap["ALBUM"].toString(); } String Ogg::XiphComment::comment() const { if(!d->fieldListMap["DESCRIPTION"].isEmpty()) { d->commentField = "DESCRIPTION"; - return d->fieldListMap["DESCRIPTION"].front(); + return d->fieldListMap["DESCRIPTION"].toString(); } if(!d->fieldListMap["COMMENT"].isEmpty()) { d->commentField = "COMMENT"; - return d->fieldListMap["COMMENT"].front(); + return d->fieldListMap["COMMENT"].toString(); } return String::null; @@ -99,7 +99,7 @@ String Ogg::XiphComment::genre() const { if(d->fieldListMap["GENRE"].isEmpty()) return String::null; - return d->fieldListMap["GENRE"].front(); + return d->fieldListMap["GENRE"].toString(); } TagLib::uint Ogg::XiphComment::year() const diff --git a/taglib/toolkit/tbytevector.cpp b/taglib/toolkit/tbytevector.cpp index 0b6a8add..b1669a99 100644 --- a/taglib/toolkit/tbytevector.cpp +++ b/taglib/toolkit/tbytevector.cpp @@ -102,7 +102,7 @@ static const uint crcTable[256] = { }; /*! - * A templatized straightforward find that works with the types + * A templatized straightforward find that works with the types * std::vector::iterator and std::vector::reverse_iterator. */ template @@ -111,7 +111,7 @@ size_t findChar( char c, size_t offset, size_t byteAlign) { const size_t dataSize = dataEnd - dataBegin; - if(dataSize == 0 || offset > dataSize - 1) + if(offset + 1 > dataSize) return ByteVector::npos; // n % 0 is invalid @@ -128,7 +128,7 @@ size_t findChar( } /*! - * A templatized KMP find that works with the types + * A templatized KMP find that works with the types * std::vector::iterator and std::vector::reverse_iterator. */ template @@ -139,7 +139,7 @@ size_t findVector( { const size_t dataSize = dataEnd - dataBegin; const size_t patternSize = patternEnd - patternBegin; - if(patternSize > dataSize || offset > dataSize - 1) + if(patternSize == 0 || offset + patternSize > dataSize) return ByteVector::npos; // n % 0 is invalid @@ -195,7 +195,7 @@ inline T toNumber(const ByteVector &v, size_t offset) { static const bool swap = (ENDIAN != Utils::SystemByteOrder); - if(LENGTH >= sizeof(T) && offset + LENGTH <= v.size()) + if(LENGTH >= sizeof(T) && offset + LENGTH <= v.size()) { // Uses memcpy instead of reinterpret_cast to avoid an alignment exception. T tmp; @@ -227,56 +227,125 @@ inline T toNumber(const ByteVector &v, size_t offset) template inline ByteVector fromNumber(T value) { - static const bool swap = (ENDIAN != Utils::SystemByteOrder); - - if(swap) + if (ENDIAN != Utils::SystemByteOrder) value = Utils::byteSwap(value); return ByteVector(reinterpret_cast(&value), sizeof(T)); } -class ByteVector::ByteVectorPrivate +template +TFloat toFloat(const ByteVector &v, size_t offset) +{ + if (offset > v.size() - sizeof(TInt)) { + debug("toFloat() - offset is out of range. Returning 0."); + return 0.0; + } + + union { + TInt i; + TFloat f; + } tmp; + ::memcpy(&tmp, v.data() + offset, sizeof(TInt)); + + if(ENDIAN != Utils::FloatByteOrder) + tmp.i = Utils::byteSwap(tmp.i); + + return tmp.f; +} + +template +ByteVector fromFloat(TFloat value) +{ + union { + TInt i; + TFloat f; + } tmp; + tmp.f = value; + + if(ENDIAN != Utils::FloatByteOrder) + tmp.i = Utils::byteSwap(tmp.i); + + return ByteVector(reinterpret_cast(&tmp), sizeof(TInt)); +} + +template +long double toFloat80(const ByteVector &v, size_t offset) +{ + if(offset > v.size() - 10) { + debug("toFloat80() - offset is out of range. Returning 0."); + return 0.0; + } + + uchar bytes[10]; + ::memcpy(bytes, v.data() + offset, 10); + + if(ENDIAN == LittleEndian) { + std::swap(bytes[0], bytes[9]); + std::swap(bytes[1], bytes[8]); + std::swap(bytes[2], bytes[7]); + std::swap(bytes[3], bytes[6]); + std::swap(bytes[4], bytes[5]); + } + + // 1-bit sign + const bool negative = ((bytes[0] & 0x80) != 0); + + // 15-bit exponent + const int exponent = ((bytes[0] & 0x7F) << 8) | bytes[1]; + + // 64-bit fraction. Leading 1 is explicit. + const ulonglong fraction + = (static_cast(bytes[2]) << 56) + | (static_cast(bytes[3]) << 48) + | (static_cast(bytes[4]) << 40) + | (static_cast(bytes[5]) << 32) + | (static_cast(bytes[6]) << 24) + | (static_cast(bytes[7]) << 16) + | (static_cast(bytes[8]) << 8) + | (static_cast(bytes[9])); + + long double val; + if(exponent == 0 && fraction == 0) + val = 0; + else { + if(exponent == 0x7FFF) { + debug("toFloat80() - can't handle the infinity or NaN. Returning 0."); + return 0.0; + } + else + val = ::ldexp(static_cast(fraction), exponent - 16383 - 63); + } + + if(negative) + return -val; + else + return val; +} + +class ByteVector::ByteVectorPrivate { public: - ByteVectorPrivate() - : data(new std::vector()) - , offset(0) - , length(0) - { - } + ByteVectorPrivate() : + data(new std::vector()), + offset(0), + length(0) {} - ByteVectorPrivate(ByteVectorPrivate* d, size_t o, size_t l) - : data(d->data) - , offset(d->offset + o) - , length(l) - { - } + ByteVectorPrivate(ByteVectorPrivate* d, size_t o, size_t l) : + data(d->data), + offset(d->offset + o), + length(l) {} - ByteVectorPrivate(size_t l, char c) - : data(new std::vector(l, c)) - , offset(0) - , length(l) - { - } + ByteVectorPrivate(size_t l, char c) : + data(new std::vector(l, c)), + offset(0), + length(l) {} - ByteVectorPrivate(const char *s, size_t l) - : data(new std::vector(s, s + l)) - , offset(0) - , length(l) - { - } - - void detach() - { - if(!data.unique()) { - data.reset(new std::vector(data->begin() + offset, data->begin() + offset + length)); - offset = 0; - } - } + ByteVectorPrivate(const char *s, size_t l) : + data(new std::vector(s, s + l)), + offset(0), + length(l) {} - ~ByteVectorPrivate() - { - } + ~ByteVectorPrivate() {} SHARED_PTR > data; size_t offset; @@ -329,42 +398,62 @@ ByteVector ByteVector::fromUInt64BE(ulonglong value) return fromNumber(value); } +ByteVector ByteVector::fromFloat32LE(float value) +{ + return fromFloat(value); +} + +ByteVector ByteVector::fromFloat32BE(float value) +{ + return fromFloat(value); +} + +ByteVector ByteVector::fromFloat64LE(double value) +{ + return fromFloat(value); +} + +ByteVector ByteVector::fromFloat64BE(double value) +{ + return fromFloat(value); +} + //////////////////////////////////////////////////////////////////////////////// // public members //////////////////////////////////////////////////////////////////////////////// -ByteVector::ByteVector() - : d(new ByteVectorPrivate()) +ByteVector::ByteVector() : + d(new ByteVectorPrivate()) { } -ByteVector::ByteVector(size_t size, char value) - : d(new ByteVectorPrivate(size, value)) +ByteVector::ByteVector(size_t size, char value) : + d(new ByteVectorPrivate(size, value)) { } -ByteVector::ByteVector(const ByteVector &v) - : d(new ByteVectorPrivate(*v.d)) +ByteVector::ByteVector(const ByteVector &v) : + d(new ByteVectorPrivate(*v.d)) { } -ByteVector::ByteVector(const ByteVector &v, size_t offset, size_t length) - : d(new ByteVectorPrivate(v.d, offset, length)) +ByteVector::ByteVector(const ByteVector &v, size_t offset, size_t length) : + d(new ByteVectorPrivate(v.d, offset, length)) { } -ByteVector::ByteVector(char c) - : d(new ByteVectorPrivate(1, c)) +ByteVector::ByteVector(char c) : + d(new ByteVectorPrivate(1, c)) { } -ByteVector::ByteVector(const char *data, size_t length) - : d(new ByteVectorPrivate(data, length)) +ByteVector::ByteVector(const char *data, size_t length) : + d(new ByteVectorPrivate(data, length)) { } -ByteVector::ByteVector(const char *data) - : d(new ByteVectorPrivate(data, ::strlen(data))) +ByteVector::ByteVector(const char *data) : + d(new ByteVectorPrivate(data, ::strlen(data))) { } @@ -445,9 +534,9 @@ bool ByteVector::containsAt( // do some sanity checking -- all of these things are needed for the search to be valid const size_t compareLength = patternLength - patternOffset; - if(offset + compareLength > size() || patternOffset >= pattern.size() || patternLength == 0) + if(offset + compareLength > size() || patternOffset >= pattern.size() || patternLength == 0) return false; - + return (::memcmp(data() + offset, pattern.data() + patternOffset, compareLength) == 0); } @@ -469,7 +558,7 @@ ByteVector &ByteVector::replace(const ByteVector &pattern, const ByteVector &wit const size_t withSize = with.size(); const size_t patternSize = pattern.size(); const ptrdiff_t diff = withSize - patternSize; - + size_t offset = 0; while (true) { @@ -481,16 +570,16 @@ ByteVector &ByteVector::replace(const ByteVector &pattern, const ByteVector &wit if(diff < 0) { ::memmove( - data() + offset + withSize, - data() + offset + patternSize, + data() + offset + withSize, + data() + offset + patternSize, size() - offset - patternSize); resize(size() + diff); } else if(diff > 0) { resize(size() + diff); ::memmove( - data() + offset + withSize, - data() + offset + patternSize, + data() + offset + withSize, + data() + offset + patternSize, size() - diff - offset - patternSize); } @@ -545,7 +634,7 @@ ByteVector &ByteVector::append(char c) ByteVector &ByteVector::clear() { detach(); - *d = *ByteVector::null.d; + *d = *ByteVector::null.d; return *this; } @@ -676,206 +765,36 @@ long long ByteVector::toInt64LE(size_t offset) const long long ByteVector::toInt64BE(size_t offset) const { return static_cast(toNumber(*this, offset)); -} +} + +float ByteVector::toFloat32LE(size_t offset) const +{ + return toFloat(*this, offset); +} float ByteVector::toFloat32BE(size_t offset) const { - typedef std::numeric_limits Limits; + return toFloat(*this, offset); +} - if(offset > size() - 4) { - debug("ByteVector::toFloat32BE() - offset is out of range. Returning 0."); - return 0.0; - } - - if(Limits::is_iec559 && Limits::digits == 24) { - - // float is 32-bit wide and IEEE754 compliant. - - union { - uint i; - float f; - } tmp = {}; - ::memcpy(&tmp, data() + offset, 4); - - if(Utils::SystemByteOrder == LittleEndian) - tmp.i = Utils::byteSwap(tmp.i); - - return tmp.f; - } - else { - - // float is not 32-bit or not IEEE754. Very unlikely in practice. - - const uchar *bytes = reinterpret_cast(data() + offset); - - // 1-bit sign - const bool negative = ((bytes[0] & 0x80) != 0); - - // 8-bit exponent - const int exponent = ((bytes[0] & 0x7F) << 1) | (bytes[1] >> 7); - - // 24-bit fraction. Leading 1 is implied. - const uint fraction - = (1U << 23) - | (static_cast(bytes[1] & 0x7f) << 16) - | (static_cast(bytes[2]) << 8) - | (static_cast(bytes[3])); - - float val; - if(exponent == 0 && fraction == 0) - val = 0; - else { - if(exponent == 0xFF) { - debug("ByteVector::toFloat32BE() - can't handle the infinity or NaN. Returning 0."); - return 0.0; - } - else - val = ::ldexp(static_cast(fraction), exponent - 127 - 23); - } - - if(negative) - return -val; - else - return val; - } +double ByteVector::toFloat64LE(size_t offset) const +{ + return toFloat(*this, offset); } double ByteVector::toFloat64BE(size_t offset) const { - typedef std::numeric_limits Limits; + return toFloat(*this, offset); +} - if(offset > size() - 8) { - debug("ByteVector::toFloat64BE() - offset is out of range. Returning 0."); - return 0.0; - } - - if(Limits::is_iec559 && Limits::digits == 53) { - - // double is 64-bit wide and IEEE754 compliant. - - union { - ulonglong i; - double f; - } tmp = {}; - ::memcpy(&tmp, data() + offset, 8); - - if(Utils::SystemByteOrder == LittleEndian) - tmp.i = Utils::byteSwap(tmp.i); - - return tmp.f; - } - else { - - // double is not 64-bit or not IEEE754. Very unlikely in practice. - - const uchar *bytes = reinterpret_cast(data() + offset); - - // 1-bit sign - const bool negative = ((bytes[0] & 0x80) != 0); - - // 11-bit exponent - const int exponent = ((bytes[0] & 0x7F) << 4) | (bytes[1] >> 4); - - // 53-bit fraction. Leading 1 is implied. - const ulonglong fraction - = (1ULL << 52) - | (static_cast(bytes[1] & 0x0F) << 48) - | (static_cast(bytes[2]) << 40) - | (static_cast(bytes[3]) << 32) - | (static_cast(bytes[4]) << 24) - | (static_cast(bytes[5]) << 16) - | (static_cast(bytes[6]) << 8) - | (static_cast(bytes[7])); - - double val; - if(exponent == 0 && fraction == 0) - val = 0; - else { - if(exponent == 0x7FF) { - debug("ByteVector::toFloat64BE() - can't handle the infinity or NaN. Returning 0."); - return 0.0; - } - else - val = ::ldexp(static_cast(fraction), exponent - 1023 - 52); - } - - if(negative) - return -val; - else - return val; - } +long double ByteVector::toFloat80LE(size_t offset) const +{ + return toFloat80(*this, offset); } long double ByteVector::toFloat80BE(size_t offset) const { - typedef std::numeric_limits Limits; - - if(offset > size() - 10) { - debug("ByteVector::toFloat80BE() - offset is out of range. Returning 0."); - return 0.0; - } - - if(Limits::is_iec559 && Limits::digits == 64) { - - // long double is 80-bit wide and IEEE754 compliant. - - union { - uchar c[10]; - long double f; - } tmp = {}; - ::memcpy(&tmp, data() + offset, 10); - - if(Utils::SystemByteOrder == LittleEndian) { - std::swap(tmp.c[0], tmp.c[9]); - std::swap(tmp.c[1], tmp.c[8]); - std::swap(tmp.c[2], tmp.c[7]); - std::swap(tmp.c[3], tmp.c[6]); - std::swap(tmp.c[4], tmp.c[5]); - } - - return tmp.f; - } - else { - - // long double is not 80-bit or not IEEE754. - // GCC on ARM, MSVC, etc. will go this way. - - const uchar *bytes = reinterpret_cast(data() + offset); - - // 1-bit sign - const bool negative = ((bytes[0] & 0x80) != 0); - - // 15-bit exponent - const int exponent = ((bytes[0] & 0x7F) << 8) | bytes[1]; - - // 64-bit fraction. Leading 1 is explicit. - const ulonglong fraction - = (static_cast(bytes[2]) << 56) - | (static_cast(bytes[3]) << 48) - | (static_cast(bytes[4]) << 40) - | (static_cast(bytes[5]) << 32) - | (static_cast(bytes[6]) << 24) - | (static_cast(bytes[7]) << 16) - | (static_cast(bytes[8]) << 8) - | (static_cast(bytes[9])); - - long double val; - if(exponent == 0 && fraction == 0) - val = 0; - else { - if(exponent == 0x7FFF) { - debug("ByteVector::toFloat80BE() - can't handle the infinity or NaN. Returning 0."); - return 0.0; - } - else - val = ::ldexp(static_cast(fraction), exponent - 16383 - 63); - } - - if(negative) - return -val; - else - return val; - } + return toFloat80(*this, offset); } const char &ByteVector::operator[](size_t index) const @@ -939,7 +858,6 @@ ByteVector ByteVector::operator+(const ByteVector &v) const ByteVector &ByteVector::operator=(const ByteVector &v) { *d = *v.d; - return *this; } @@ -975,7 +893,12 @@ ByteVector ByteVector::toHex() const void ByteVector::detach() { - d->detach(); + if(!d->data.unique()) { + std::vector::const_iterator begin = d->data->begin() + d->offset; + std::vector::const_iterator end = begin + d->length; + d->data.reset(new std::vector(begin, end)); + d->offset = 0; + } } //////////////////////////////////////////////////////////////////////////////// diff --git a/taglib/toolkit/tbytevector.h b/taglib/toolkit/tbytevector.h index af8d1db8..3cb98bae 100644 --- a/taglib/toolkit/tbytevector.h +++ b/taglib/toolkit/tbytevector.h @@ -326,7 +326,7 @@ namespace TagLib { * 24-bit big-endian integer. */ uint toUInt24BE(size_t offset) const; - + /*! * Converts the 4 bytes at \a offset of the vector to a uint as an unsigned * 32-bit little-endian integer. @@ -334,7 +334,7 @@ namespace TagLib { * \see fromUInt32LE() */ uint toUInt32LE(size_t offset) const; - + /*! * Converts the 4 bytes at \a offset of the vector to a ushort as an unsigned * 32-bit big-endian integer. @@ -342,7 +342,7 @@ namespace TagLib { * \see fromUInt32BE() */ uint toUInt32BE(size_t offset) const; - + /*! * Converts the 8 bytes at \a offset of the vector to a long long as a signed * 64-bit little-endian integer. @@ -350,7 +350,7 @@ namespace TagLib { * \see fromUInt64LE() */ long long toInt64LE(size_t offset) const; - + /*! * Converts the 8 bytes at \a offset of the vector to a long long as a signed * 64-bit big-endian integer. @@ -359,12 +359,24 @@ namespace TagLib { */ long long toInt64BE(size_t offset) const; + /* + * Converts the 4 bytes at \a offset of the vector to a float as an IEEE754 + * 32-bit little-endian floating point number. + */ + float toFloat32LE(size_t offset) const; + /* * Converts the 4 bytes at \a offset of the vector to a float as an IEEE754 * 32-bit big-endian floating point number. */ float toFloat32BE(size_t offset) const; + /* + * Converts the 8 bytes at \a offset of the vector to a double as an IEEE754 + * 64-bit little-endian floating point number. + */ + double toFloat64LE(size_t offset) const; + /* * Converts the 8 bytes at \a offset of the vector to a double as an IEEE754 * 64-bit big-endian floating point number. @@ -372,8 +384,16 @@ namespace TagLib { double toFloat64BE(size_t offset) const; /* - * Converts the 10 bytes at \a offset of the vector to a long double as an IEEE754 - * 80-bit big-endian floating point number. + * Converts the 10 bytes at \a offset of the vector to a long double as an + * IEEE754 80-bit little-endian floating point number. + * + * \note This may compromise the precision depends on the size of long double. + */ + long double toFloat80LE(size_t offset) const; + + /* + * Converts the 10 bytes at \a offset of the vector to a long double as an + * IEEE754 80-bit big-endian floating point number. * * \note This may compromise the precision depends on the size of long double. */ @@ -383,7 +403,7 @@ namespace TagLib { * Creates a 2 byte ByteVector based on \a value as an unsigned 16-bit * little-endian integer. * - * \note If \a value is larger than 16-bit, the lowest 16 bits are used. + * \note If \a value is larger than 16-bit, the lowest 16 bits are used. * \see toUInt16LE() */ static ByteVector fromUInt16LE(size_t value); @@ -392,7 +412,7 @@ namespace TagLib { * Creates a 2 byte ByteVector based on \a value as an unsigned 16-bit * big-endian integer. * - * \note If \a value is larger than 16-bit, the lowest 16 bits are used. + * \note If \a value is larger than 16-bit, the lowest 16 bits are used. * \see toUInt16BE() */ static ByteVector fromUInt16BE(size_t value); @@ -401,7 +421,7 @@ namespace TagLib { * Creates a 4 byte ByteVector based on \a value as an unsigned 32-bit * little-endian integer. * - * \note If \a value is larger than 32-bit, the lowest 32 bits are used. + * \note If \a value is larger than 32-bit, the lowest 32 bits are used. * \see toUInt32LE() */ static ByteVector fromUInt32LE(size_t value); @@ -410,7 +430,7 @@ namespace TagLib { * Creates a 4 byte ByteVector based on \a value as an unsigned 32-bit * big-endian integer. * - * \note If \a value is larger than 32-bit, the lowest 32 bits are used. + * \note If \a value is larger than 32-bit, the lowest 32 bits are used. * \see toUInt32BE() */ static ByteVector fromUInt32BE(size_t value); @@ -431,6 +451,38 @@ namespace TagLib { */ static ByteVector fromUInt64BE(ulonglong value); + /*! + * Creates a 4 byte ByteVector based on \a value as an IEEE754 32-bit + * little-endian floating point number. + * + * \see fromFloat32BE() + */ + static ByteVector fromFloat32LE(float value); + + /*! + * Creates a 4 byte ByteVector based on \a value as an IEEE754 32-bit + * big-endian floating point number. + * + * \see fromFloat32LE() + */ + static ByteVector fromFloat32BE(float value); + + /*! + * Creates a 8 byte ByteVector based on \a value as an IEEE754 64-bit + * little-endian floating point number. + * + * \see fromFloat64BE() + */ + static ByteVector fromFloat64LE(double value); + + /*! + * Creates a 8 byte ByteVector based on \a value as an IEEE754 64-bit + * big-endian floating point number. + * + * \see fromFloat64LE() + */ + static ByteVector fromFloat64BE(double value); + /*! * Returns a ByteVector based on the CString \a s. */ @@ -507,7 +559,7 @@ namespace TagLib { static const ByteVector null; /*! - * When used as the value for a \a length or \a patternLength parameter + * When used as the value for a \a length or \a patternLength parameter * in ByteVector's member functions, means "until the end of the data". * As a return value, it is usually used to indicate no matches. */ diff --git a/taglib/toolkit/tfilestream.cpp b/taglib/toolkit/tfilestream.cpp index b90dada1..d0464006 100644 --- a/taglib/toolkit/tfilestream.cpp +++ b/taglib/toolkit/tfilestream.cpp @@ -36,7 +36,7 @@ using namespace TagLib; -namespace +namespace { #ifdef _WIN32 @@ -79,7 +79,7 @@ namespace DWORD length; if(WriteFile(file, buffer.data(), static_cast(buffer.size()), &length, NULL)) return static_cast(length); - else + else return 0; } @@ -122,12 +122,10 @@ namespace class FileStream::FileStreamPrivate { public: - FileStreamPrivate(const FileName &fileName) - : file(InvalidFileHandle) - , name(fileName) - , readOnly(true) - { - } + FileStreamPrivate(const FileName &fileName) : + file(InvalidFileHandle), + name(fileName), + readOnly(true) {} FileHandle file; FileNameHandle name; @@ -138,8 +136,8 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -FileStream::FileStream(FileName fileName, bool openReadOnly) - : d(new FileStreamPrivate(fileName)) +FileStream::FileStream(FileName fileName, bool openReadOnly) : + d(new FileStreamPrivate(fileName)) { // First try with read / write mode, if that fails, fall back to read only. @@ -151,13 +149,13 @@ FileStream::FileStream(FileName fileName, bool openReadOnly) else d->file = openFile(fileName, true); - if(d->file == InvalidFileHandle) + if(d->file == InvalidFileHandle) { # ifdef _WIN32 debug("Could not open file " + fileName.toString()); # else debug("Could not open file " + String(static_cast(d->name))); -# endif +# endif } } @@ -192,7 +190,7 @@ ByteVector FileStream::readBlock(size_t length) const size_t count = readFile(d->file, buffer); buffer.resize(count); - + return buffer; } @@ -262,7 +260,7 @@ void FileStream::insert(const ByteVector &data, offset_t start, size_t replace) { // Seek to the current read position and read the data that we're about // to overwrite. Appropriately increment the readPosition. - + seek(readPosition); const size_t bytesRead = readFile(d->file, aboutToOverwrite); aboutToOverwrite.resize(bytesRead); @@ -288,7 +286,7 @@ void FileStream::insert(const ByteVector &data, offset_t start, size_t replace) writePosition += buffer.size(); // Make the current buffer the data that we read in the beginning. - + buffer = aboutToOverwrite; } } @@ -368,7 +366,12 @@ void FileStream::seek(offset_t offset, Position p) LARGE_INTEGER liOffset; liOffset.QuadPart = offset; + 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) { debug("File::seek() -- Failed to set the file pointer."); } @@ -399,7 +402,7 @@ void FileStream::seek(offset_t offset, Position p) fseek(d->file, static_cast(offset), whence); -# endif +# endif #endif } @@ -424,6 +427,7 @@ offset_t FileStream::tell() const LARGE_INTEGER position; position.QuadPart = 0; + SetLastError(NO_ERROR); position.LowPart = SetFilePointer( d->file, position.LowPart, &position.HighPart, FILE_CURRENT); if(GetLastError() == NO_ERROR) { @@ -444,7 +448,7 @@ offset_t FileStream::tell() const return static_cast(ftell(d->file)); -# endif +# endif #endif } @@ -461,6 +465,7 @@ offset_t FileStream::length() LARGE_INTEGER fileSize; fileSize.QuadPart = 0; + SetLastError(NO_ERROR); fileSize.LowPart = GetFileSize(d->file, reinterpret_cast(&fileSize.HighPart)); if(GetLastError() == NO_ERROR) { return fileSize.QuadPart; @@ -500,6 +505,8 @@ void FileStream::truncate(offset_t length) const offset_t currentPos = tell(); seek(length); + + SetLastError(NO_ERROR); SetEndOfFile(d->file); if(GetLastError() != NO_ERROR) { debug("File::truncate() -- Failed to truncate the file."); diff --git a/taglib/toolkit/tiostream.cpp b/taglib/toolkit/tiostream.cpp index d847ad60..4d7e943b 100644 --- a/taglib/toolkit/tiostream.cpp +++ b/taglib/toolkit/tiostream.cpp @@ -34,10 +34,10 @@ using namespace TagLib; # include "tsmartptr.h" # include -namespace +namespace { // Check if the running system has CreateFileW() function. - // Windows9x systems don't have CreateFileW() or can't accept Unicode file names. + // Windows9x systems don't have CreateFileW() or can't accept Unicode file names. bool supportsUnicode() { @@ -50,11 +50,11 @@ namespace } // Indicates whether the system supports Unicode file names. - - const bool SystemSupportsUnicode = supportsUnicode(); + + const bool SystemSupportsUnicode = supportsUnicode(); // Converts a UTF-16 string into a local encoding. - // This function should only be used in Windows9x systems which don't support + // This function should only be used in Windows9x systems which don't support // Unicode file names. std::string unicodeToAnsi(const wchar_t *wstr) @@ -83,21 +83,19 @@ namespace class FileName::FileNamePrivate { public: - FileNamePrivate() - : data(new FileNameData()) - { - } + FileNamePrivate() : + data(new FileNameData()) {} SHARED_PTR data; }; -FileName::FileName() - : d(new FileNamePrivate()) +FileName::FileName() : + d(new FileNamePrivate()) { } -FileName::FileName(const wchar_t *name) - : d(new FileNamePrivate()) +FileName::FileName(const wchar_t *name) : + d(new FileNamePrivate()) { // If WinNT, stores a Unicode string into wname directly. // If Win9x, converts and stores it into name to avoid calling Unicode version functions. @@ -108,14 +106,14 @@ FileName::FileName(const wchar_t *name) d->data->name = unicodeToAnsi(name); } -FileName::FileName(const char *name) - : d(new FileNamePrivate()) +FileName::FileName(const char *name) : + d(new FileNamePrivate()) { d->data->name = name; } -FileName::FileName(const FileName &name) - : d(new FileNamePrivate()) +FileName::FileName(const FileName &name) : + d(new FileNamePrivate()) { *d = *name.d; } @@ -131,21 +129,21 @@ FileName &FileName::operator=(const FileName &name) return *this; } -const std::wstring &FileName::wstr() const -{ - return d->data->wname; +const std::wstring &FileName::wstr() const +{ + return d->data->wname; } -const std::string &FileName::str() const -{ +const std::string &FileName::str() const +{ return d->data->name; -} +} String FileName::toString() const { if(!d->data->wname.empty()) { return String(d->data->wname); - } + } else if(!d->data->name.empty()) { const int len = MultiByteToWideChar(CP_ACP, 0, d->data->name.c_str(), -1, NULL, 0); if(len == 0) diff --git a/taglib/toolkit/tstring.cpp b/taglib/toolkit/tstring.cpp index 67e6e058..389c6eb9 100644 --- a/taglib/toolkit/tstring.cpp +++ b/taglib/toolkit/tstring.cpp @@ -48,11 +48,6 @@ namespace { - inline unsigned short combine(unsigned char c1, unsigned char c2) - { - return (c1 << 8) | c2; - } - void UTF16toUTF8(const wchar_t *src, size_t srcLength, char *dst, size_t dstLength) { #ifdef HAVE_STD_CODECVT @@ -151,10 +146,8 @@ namespace TagLib { class String::StringPrivate { public: - StringPrivate() - : data(new std::wstring()) - { - } + StringPrivate() : + data(new std::wstring()) {} /*! * Stores string in UTF-16. The byte order depends on the CPU endian. @@ -174,18 +167,18 @@ const size_t String::npos = std::wstring::npos; //////////////////////////////////////////////////////////////////////////////// -String::String() - : d(new StringPrivate()) +String::String() : + d(new StringPrivate()) { } -String::String(const String &s) - : d(new StringPrivate(*s.d)) +String::String(const String &s) : + d(new StringPrivate(*s.d)) { } -String::String(const std::string &s, Type t) - : d(new StringPrivate()) +String::String(const std::string &s, Type t) : + d(new StringPrivate()) { if(t == Latin1) copyFromLatin1(s.c_str(), s.length()); @@ -196,8 +189,8 @@ String::String(const std::string &s, Type t) } } -String::String(const std::wstring &s, Type t) - : d(new StringPrivate()) +String::String(const std::wstring &s, Type t) : + d(new StringPrivate()) { if(t == UTF16 || t == UTF16BE || t == UTF16LE) copyFromUTF16(s.c_str(), s.length(), t); @@ -206,8 +199,8 @@ String::String(const std::wstring &s, Type t) } } -String::String(const wchar_t *s, Type t) - : d(new StringPrivate()) +String::String(const wchar_t *s, Type t) : + d(new StringPrivate()) { if(t == UTF16 || t == UTF16BE || t == UTF16LE) copyFromUTF16(s, ::wcslen(s), t); @@ -216,8 +209,8 @@ String::String(const wchar_t *s, Type t) } } -String::String(const char *s, Type t) - : d(new StringPrivate()) +String::String(const char *s, Type t) : + d(new StringPrivate()) { if(t == Latin1) copyFromLatin1(s, ::strlen(s)); @@ -228,8 +221,8 @@ String::String(const char *s, Type t) } } -String::String(wchar_t c, Type t) - : d(new StringPrivate()) +String::String(wchar_t c, Type t) : + d(new StringPrivate()) { if(t == UTF16 || t == UTF16BE || t == UTF16LE) copyFromUTF16(&c, 1, t); @@ -238,8 +231,8 @@ String::String(wchar_t c, Type t) } } -String::String(char c, Type t) - : d(new StringPrivate()) +String::String(char c, Type t) : + d(new StringPrivate()) { if(t == Latin1) copyFromLatin1(&c, 1); @@ -250,8 +243,8 @@ String::String(char c, Type t) } } -String::String(const ByteVector &v, Type t) - : d(new StringPrivate()) +String::String(const ByteVector &v, Type t) : + d(new StringPrivate()) { if(v.isEmpty()) return; @@ -746,7 +739,12 @@ void String::copyFromUTF16(const char *s, size_t length, Type t) d->data->resize(length / 2); for(size_t i = 0; i < length / 2; ++i) { - (*d->data)[i] = swap ? combine(*s, *(s + 1)) : combine(*(s + 1), *s); + ushort c; + ::memcpy(&c, s, 2); + if(swap) + c = Utils::byteSwap(c); + + (*d->data)[i] = static_cast(c); s += 2; } } diff --git a/taglib/toolkit/tutils.h b/taglib/toolkit/tutils.h index e4e6c953..155a5be1 100644 --- a/taglib/toolkit/tutils.h +++ b/taglib/toolkit/tutils.h @@ -47,6 +47,7 @@ #include "tstring.h" #include #include +#include namespace TagLib { @@ -148,13 +149,13 @@ namespace TagLib #endif } - + inline String formatString(const char *format, ...) { // Sufficient buffer size for the current internal uses. // Consider changing this value when you use this function. - static const size_t BufferSize = 128; + static const size_t BufferSize = 128; va_list args; va_start(args, format); @@ -194,11 +195,11 @@ namespace TagLib # if SYSTEM_BYTEORDER == 1 - const ByteOrder SystemByteOrder = LittleEndian; + const ByteOrder SystemByteOrder = LittleEndian; # else - const ByteOrder SystemByteOrder = BigEndian; + const ByteOrder SystemByteOrder = BigEndian; # endif @@ -217,8 +218,40 @@ namespace TagLib else return BigEndian; } - - const ByteOrder SystemByteOrder = systemByteOrder(); + + const ByteOrder SystemByteOrder = systemByteOrder(); + +#endif + +#ifdef FLOAT_BYTEORDER + +# if FLOAT_BYTEORDER == 1 + + const ByteOrder FloatByteOrder = LittleEndian; + +# else + + const ByteOrder FloatByteOrder = BigEndian; + +# endif + +#else + + inline ByteOrder floatByteOrder() + { + double bin[] = { + // "*TAGLIB*" encoded as a little-endian floating-point number + (double) 3.9865557444897601e-105, (double) 0.0 + }; + + char *str = (char*)&bin[0]; + if(strncmp(&str[1], "TAGLIB", 6) == 0) + return LittleEndian; + else + return BigEndian; + } + + const ByteOrder FloatByteOrder = floatByteOrder(); #endif } diff --git a/tests/test_apetag.cpp b/tests/test_apetag.cpp index c7516994..17a7c4d9 100644 --- a/tests/test_apetag.cpp +++ b/tests/test_apetag.cpp @@ -51,8 +51,8 @@ public: dict["TRACKNUMBER"].append("17"); tag.setProperties(dict); CPPUNIT_ASSERT_EQUAL(String("17"), tag.itemListMap()["TRACK"].values()[0]); - CPPUNIT_ASSERT_EQUAL((size_t)2u, tag.itemListMap()["ARTIST"].values().size()); - CPPUNIT_ASSERT_EQUAL(String("artist 1"), tag.artist()); + CPPUNIT_ASSERT_EQUAL((size_t)2, tag.itemListMap()["ARTIST"].values().size()); + CPPUNIT_ASSERT_EQUAL(String("artist 1 artist 2"), tag.artist()); CPPUNIT_ASSERT_EQUAL(17u, tag.track()); } @@ -98,19 +98,19 @@ public: CPPUNIT_ASSERT(unsuccessful.contains("A")); CPPUNIT_ASSERT(unsuccessful.contains("MP+")); } - + void testTextBinary() { APE::Item item = APE::Item("DUMMY", "Test Text"); CPPUNIT_ASSERT_EQUAL(String("Test Text"), item.toString()); CPPUNIT_ASSERT_EQUAL(ByteVector::null, item.binaryData()); - + ByteVector data("Test Data"); item.setBinaryData(data); CPPUNIT_ASSERT(item.values().isEmpty()); CPPUNIT_ASSERT_EQUAL(String::null, item.toString()); CPPUNIT_ASSERT_EQUAL(data, item.binaryData()); - + item.setValue("Test Text 2"); CPPUNIT_ASSERT_EQUAL(String("Test Text 2"), item.toString()); CPPUNIT_ASSERT_EQUAL(ByteVector::null, item.binaryData()); diff --git a/tests/test_bytevector.cpp b/tests/test_bytevector.cpp index 6a9e7ea2..ed46c2ed 100644 --- a/tests/test_bytevector.cpp +++ b/tests/test_bytevector.cpp @@ -22,6 +22,7 @@ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#include #include #include #include @@ -79,7 +80,7 @@ public: CPPUNIT_ASSERT(i.containsAt(j, 6, 1)); CPPUNIT_ASSERT(i.containsAt(j, 6, 1, 3)); } - + void testFind1() { CPPUNIT_ASSERT_EQUAL((size_t)4, ByteVector("....SggO."). find("SggO")); @@ -92,6 +93,12 @@ public: CPPUNIT_ASSERT_EQUAL(ByteVector::npos, ByteVector("....SggO."). find("SggO", 6)); CPPUNIT_ASSERT_EQUAL(ByteVector::npos, ByteVector("....SggO."). find("SggO", 7)); CPPUNIT_ASSERT_EQUAL(ByteVector::npos, ByteVector("....SggO."). find("SggO", 8)); + + // Intentional out-of-bounds access. + ByteVector v("0123456789x"); + v.resize(10); + v.data()[10] = 'x'; + CPPUNIT_ASSERT_EQUAL(ByteVector::npos, v.find("789x", 7)); } void testFind2() @@ -147,6 +154,7 @@ public: void testNumericCoversion() { + // n = { 0x00, 0x88, 0x11, 0x99, ..., 0x77, 0xFF } ByteVector n(16, 0); for(size_t i = 0; i < 8; ++i) { n[i * 2 ] = static_cast(0x11 * i); @@ -167,7 +175,7 @@ public: CPPUNIT_ASSERT(n.toInt64BE(3) == -7412174897536512939ll); CPPUNIT_ASSERT(n.toInt64LE(6) == -1268082884489200845ll); CPPUNIT_ASSERT(n.toInt64BE(4) == 2497865822736504285ll); - + CPPUNIT_ASSERT(ByteVector::fromUInt16LE(n.toInt16LE(5)) == n.mid(5, 2)); CPPUNIT_ASSERT(ByteVector::fromUInt16BE(n.toInt16BE(9)) == n.mid(9, 2)); CPPUNIT_ASSERT(ByteVector::fromUInt32LE(n.toUInt32LE(4)) == n.mid(4, 4)); @@ -179,18 +187,42 @@ public: CPPUNIT_ASSERT(ByteVector::fromUInt32LE(287454020) == ByteVector::fromUInt32BE(1144201745)); CPPUNIT_ASSERT(ByteVector::fromUInt64LE(1234605615291183940) == ByteVector::fromUInt64BE(4914309075945333265)); - const uchar PI32[] = { 0x00, 0x40, 0x49, 0x0f, 0xdb }; - const uchar PI64[] = { 0x00, 0x40, 0x09, 0x21, 0xfb, 0x54, 0x44, 0x2d, 0x18 }; - const uchar PI80[] = { 0x00, 0x40, 0x00, 0xc9, 0x0f, 0xda, 0xa2, 0x21, 0x68, 0xc0, 0x00 }; + const uchar PI32LE[] = { 0x00, 0xdb, 0x0f, 0x49, 0x40 }; + const uchar PI32BE[] = { 0x00, 0x40, 0x49, 0x0f, 0xdb }; + const uchar PI64LE[] = { 0x00, 0x18, 0x2d, 0x44, 0x54, 0xfb, 0x21, 0x09, 0x40 }; + const uchar PI64BE[] = { 0x00, 0x40, 0x09, 0x21, 0xfb, 0x54, 0x44, 0x2d, 0x18 }; + const uchar PI80LE[] = { 0x00, 0x00, 0xc0, 0x68, 0x21, 0xa2, 0xda, 0x0f, 0xc9, 0x00, 0x40 }; + const uchar PI80BE[] = { 0x00, 0x40, 0x00, 0xc9, 0x0f, 0xda, 0xa2, 0x21, 0x68, 0xc0, 0x00 }; - ByteVector pi32(reinterpret_cast(PI32), 5); - CPPUNIT_ASSERT_EQUAL(31415, static_cast(pi32.toFloat32BE(1) * 10000)); + ByteVector pi32le(reinterpret_cast(PI32LE), 5); + CPPUNIT_ASSERT_EQUAL(31415, static_cast(pi32le.toFloat32LE(1) * 10000)); - ByteVector pi64(reinterpret_cast(PI64), 9); - CPPUNIT_ASSERT_EQUAL(31415, static_cast(pi64.toFloat64BE(1) * 10000)); + ByteVector pi32be(reinterpret_cast(PI32BE), 5); + CPPUNIT_ASSERT_EQUAL(31415, static_cast(pi32be.toFloat32BE(1) * 10000)); - ByteVector pi80(reinterpret_cast(PI80), 11); - CPPUNIT_ASSERT_EQUAL(31415, static_cast(pi80.toFloat80BE(1) * 10000)); + ByteVector pi64le(reinterpret_cast(PI64LE), 9); + CPPUNIT_ASSERT_EQUAL(31415, static_cast(pi64le.toFloat64LE(1) * 10000)); + + ByteVector pi64be(reinterpret_cast(PI64BE), 9); + CPPUNIT_ASSERT_EQUAL(31415, static_cast(pi64be.toFloat64BE(1) * 10000)); + + ByteVector pi80le(reinterpret_cast(PI80LE), 11); + CPPUNIT_ASSERT_EQUAL(31415, static_cast(pi80le.toFloat80LE(1) * 10000)); + + ByteVector pi80be(reinterpret_cast(PI80BE), 11); + CPPUNIT_ASSERT_EQUAL(31415, static_cast(pi80be.toFloat80BE(1) * 10000)); + + ByteVector pi32le2 = ByteVector::fromFloat32LE(pi32le.toFloat32LE(1)); + CPPUNIT_ASSERT(memcmp(pi32le.data() + 1, pi32le2.data(), 4) == 0); + + ByteVector pi32be2 = ByteVector::fromFloat32BE(pi32be.toFloat32BE(1)); + CPPUNIT_ASSERT(memcmp(pi32be.data() + 1, pi32be2.data(), 4) == 0); + + ByteVector pi64le2 = ByteVector::fromFloat64LE(pi64le.toFloat64LE(1)); + CPPUNIT_ASSERT(memcmp(pi64le.data() + 1, pi64le2.data(), 8) == 0); + + ByteVector pi64be2 = ByteVector::fromFloat64BE(pi64be.toFloat64BE(1)); + CPPUNIT_ASSERT(memcmp(pi64be.data() + 1, pi64be2.data(), 8) == 0); } void testReplace() diff --git a/tests/test_flac.cpp b/tests/test_flac.cpp index d6224f96..8b09acb8 100644 --- a/tests/test_flac.cpp +++ b/tests/test_flac.cpp @@ -91,6 +91,7 @@ public: newpic->setData("JPEG data"); f->addPicture(newpic); f->save(); + delete f; f = new FLAC::File(newname.c_str()); lst = f->pictureList(); @@ -115,6 +116,7 @@ public: CPPUNIT_ASSERT_EQUAL(String("image/jpeg"), pic->mimeType()); CPPUNIT_ASSERT_EQUAL(String("new image"), pic->description()); CPPUNIT_ASSERT_EQUAL(ByteVector("JPEG data"), pic->data()); + delete f; } void testReplacePicture() @@ -138,6 +140,7 @@ public: f->removePictures(); f->addPicture(newpic); f->save(); + delete f; f = new FLAC::File(newname.c_str()); lst = f->pictureList(); @@ -152,6 +155,7 @@ public: CPPUNIT_ASSERT_EQUAL(String("image/jpeg"), pic->mimeType()); CPPUNIT_ASSERT_EQUAL(String("new image"), pic->description()); CPPUNIT_ASSERT_EQUAL(ByteVector("JPEG data"), pic->data()); + delete f; } void testRemoveAllPictures() @@ -165,10 +169,12 @@ public: f->removePictures(); f->save(); + delete f; f = new FLAC::File(newname.c_str()); lst = f->pictureList(); CPPUNIT_ASSERT_EQUAL(size_t(0), lst.size()); + delete f; } void testRepeatedSave() @@ -185,10 +191,12 @@ public: tag->setTitle("NEW TITLE 2"); f->save(); CPPUNIT_ASSERT_EQUAL(String("NEW TITLE 2"), tag->title()); + delete f; f = new FLAC::File(newname.c_str()); tag = f->tag(); CPPUNIT_ASSERT_EQUAL(String("NEW TITLE 2"), tag->title()); + delete f; } void testSaveMultipleValues() @@ -209,6 +217,7 @@ public: CPPUNIT_ASSERT_EQUAL(size_t(2), m["ARTIST"].size()); CPPUNIT_ASSERT_EQUAL(String("artist 1"), m["ARTIST"][0]); CPPUNIT_ASSERT_EQUAL(String("artist 2"), m["ARTIST"][1]); + delete f; } void testDict() @@ -230,13 +239,14 @@ public: CPPUNIT_ASSERT_EQUAL(size_t(2), dict["ARTIST"].size()); CPPUNIT_ASSERT_EQUAL(String("artøst 1"), dict["ARTIST"][0]); CPPUNIT_ASSERT_EQUAL(String("artöst 2"), dict["ARTIST"][1]); + delete f; } void testInvalid() { ScopedFileCopy copy("silence-44-s", ".flac"); PropertyMap map; - map["H\xc4\xd6"] = String("bla"); + map[L"H\x00c4\x00d6"] = String("bla"); FLAC::File f(copy.fileName().c_str()); PropertyMap invalid = f.setProperties(map); CPPUNIT_ASSERT_EQUAL(size_t(1), invalid.size()); diff --git a/tests/test_id3v2.cpp b/tests/test_id3v2.cpp index 3bf1c3d9..b0f4275e 100644 --- a/tests/test_id3v2.cpp +++ b/tests/test_id3v2.cpp @@ -4,12 +4,9 @@ #include #include -// so evil :( -#define protected public #include #include #include -#undef protected #include #include #include @@ -22,6 +19,8 @@ #include #include #include +#include +#include #include #include #include @@ -36,8 +35,8 @@ class PublicFrame : public ID3v2::Frame PublicFrame() : ID3v2::Frame(ByteVector("XXXX\0\0\0\0\0\0", 10)) {} String readStringField(const ByteVector &data, String::Type encoding) { - size_t position = 0; - return ID3v2::Frame::readStringField(data, encoding, position); + size_t position = 0; + return ID3v2::Frame::readStringField(data, encoding, position); } virtual String toString() const { return String::null; } virtual void parseFields(const ByteVector &) {} @@ -90,6 +89,10 @@ class TestID3v2 : public CppUnit::TestFixture CPPUNIT_TEST(testPropertyInterface2); CPPUNIT_TEST(testDeleteFrame); CPPUNIT_TEST(testSaveAndStripID3v1ShouldNotAddFrameFromID3v1ToId3v2); + CPPUNIT_TEST(testParseChapterFrame); + CPPUNIT_TEST(testRenderChapterFrame); + CPPUNIT_TEST(testParseTableOfContentsFrame); + CPPUNIT_TEST(testRenderTableOfContentsFrame); CPPUNIT_TEST_SUITE_END(); public: @@ -103,13 +106,23 @@ public: void testDowngradeUTF8ForID3v23() { - ID3v2::TextIdentificationFrame f(ByteVector("TPE1"), String::UTF8); + ScopedFileCopy copy("xing", ".mp3"); + string newname = copy.fileName(); + + ID3v2::TextIdentificationFrame *f + = new ID3v2::TextIdentificationFrame(ByteVector("TPE1"), String::UTF8); StringList sl; sl.append("Foo"); - f.setText(sl); - f.header()->setVersion(3); - ByteVector data = f.render(); + f->setText(sl); + + MPEG::File file(newname.c_str()); + file.ID3v2Tag(true)->addFrame(f); + file.save(MPEG::File::ID3v2, true, 3); + CPPUNIT_ASSERT_EQUAL(true, file.hasID3v2Tag()); + + ByteVector data = f->render(); CPPUNIT_ASSERT_EQUAL((size_t)(4+4+2+1+6+2), data.size()); + ID3v2::TextIdentificationFrame f2(data); CPPUNIT_ASSERT_EQUAL(sl, f2.fieldList()); CPPUNIT_ASSERT_EQUAL(String::UTF16, f2.textEncoding()); @@ -295,13 +308,16 @@ public: f->setRating(200); f->setCounter(3); - MPEG::File foo(newname.c_str()); - foo.ID3v2Tag()->addFrame(f); - foo.save(); - - MPEG::File bar(newname.c_str()); - CPPUNIT_ASSERT_EQUAL(String("email@example.com"), dynamic_cast(bar.ID3v2Tag()->frameList("POPM").front())->email()); - CPPUNIT_ASSERT_EQUAL(200, dynamic_cast(bar.ID3v2Tag()->frameList("POPM").front())->rating()); + { + MPEG::File foo(newname.c_str()); + foo.ID3v2Tag()->addFrame(f); + foo.save(); + } + { + MPEG::File bar(newname.c_str()); + CPPUNIT_ASSERT_EQUAL(String("email@example.com"), dynamic_cast(bar.ID3v2Tag()->frameList("POPM").front())->email()); + CPPUNIT_ASSERT_EQUAL(200, dynamic_cast(bar.ID3v2Tag()->frameList("POPM").front())->rating()); + } } // http://bugs.kde.org/show_bug.cgi?id=150481 @@ -402,7 +418,7 @@ public: "http://example.com", 33), // URL f.render()); } - + void testParseOwnershipFrame() { ID3v2::OwnershipFrame f( @@ -547,13 +563,17 @@ public: ScopedFileCopy copy("xing", ".mp3"); string newname = copy.fileName(); ID3v2::FrameFactory::instance()->setDefaultTextEncoding(String::UTF16); - MPEG::File foo(newname.c_str()); - foo.strip(); - foo.tag()->setComment("Test comment!"); - foo.save(); - MPEG::File bar(newname.c_str()); - CPPUNIT_ASSERT_EQUAL(String("Test comment!"), bar.tag()->comment()); - ID3v2::FrameFactory::instance()->setDefaultTextEncoding(defaultEncoding); + { + MPEG::File foo(newname.c_str()); + foo.strip(); + foo.tag()->setComment("Test comment!"); + foo.save(); + } + { + MPEG::File bar(newname.c_str()); + CPPUNIT_ASSERT_EQUAL(String("Test comment!"), bar.tag()->comment()); + ID3v2::FrameFactory::instance()->setDefaultTextEncoding(defaultEncoding); + } } void testUpdateGenre23_1() @@ -634,57 +654,60 @@ public: string newname = copy.fileName(); ID3v2::TextIdentificationFrame *tf; - MPEG::File foo(newname.c_str()); - tf = new ID3v2::TextIdentificationFrame("TDOR", String::Latin1); - tf->setText("2011-03-16"); - foo.ID3v2Tag()->addFrame(tf); - tf = new ID3v2::TextIdentificationFrame("TDRC", String::Latin1); - tf->setText("2012-04-17T12:01"); - foo.ID3v2Tag()->addFrame(tf); - tf = new ID3v2::TextIdentificationFrame("TMCL", String::Latin1); - tf->setText(StringList().append("Guitar").append("Artist 1").append("Drums").append("Artist 2")); - foo.ID3v2Tag()->addFrame(tf); - tf = new ID3v2::TextIdentificationFrame("TIPL", String::Latin1); - tf->setText(StringList().append("Producer").append("Artist 3").append("Mastering").append("Artist 4")); - foo.ID3v2Tag()->addFrame(tf); - foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TDRL", String::Latin1)); - foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TDTG", String::Latin1)); - foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TMOO", String::Latin1)); - foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TPRO", String::Latin1)); - foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TSOA", String::Latin1)); - foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TSOT", String::Latin1)); - foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TSST", String::Latin1)); - foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TSOP", String::Latin1)); - foo.save(MPEG::File::AllTags, true, 3); - - MPEG::File bar(newname.c_str()); - tf = static_cast(bar.ID3v2Tag()->frameList("TDOR").front()); - CPPUNIT_ASSERT(tf); - CPPUNIT_ASSERT_EQUAL(size_t(1), tf->fieldList().size()); - CPPUNIT_ASSERT_EQUAL(String("2011"), tf->fieldList().front()); - tf = static_cast(bar.ID3v2Tag()->frameList("TDRC").front()); - CPPUNIT_ASSERT(tf); - CPPUNIT_ASSERT_EQUAL(size_t(1), tf->fieldList().size()); - CPPUNIT_ASSERT_EQUAL(String("2012"), tf->fieldList().front()); - tf = dynamic_cast(bar.ID3v2Tag()->frameList("TIPL").front()); - CPPUNIT_ASSERT(tf); - CPPUNIT_ASSERT_EQUAL(size_t(8), tf->fieldList().size()); - CPPUNIT_ASSERT_EQUAL(String("Guitar"), tf->fieldList()[0]); - CPPUNIT_ASSERT_EQUAL(String("Artist 1"), tf->fieldList()[1]); - CPPUNIT_ASSERT_EQUAL(String("Drums"), tf->fieldList()[2]); - CPPUNIT_ASSERT_EQUAL(String("Artist 2"), tf->fieldList()[3]); - CPPUNIT_ASSERT_EQUAL(String("Producer"), tf->fieldList()[4]); - CPPUNIT_ASSERT_EQUAL(String("Artist 3"), tf->fieldList()[5]); - CPPUNIT_ASSERT_EQUAL(String("Mastering"), tf->fieldList()[6]); - CPPUNIT_ASSERT_EQUAL(String("Artist 4"), tf->fieldList()[7]); - CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TDRL")); - CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TDTG")); - CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TMOO")); - CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TPRO")); - CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TSOA")); - CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TSOT")); - CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TSST")); - CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TSOP")); + { + MPEG::File foo(newname.c_str()); + tf = new ID3v2::TextIdentificationFrame("TDOR", String::Latin1); + tf->setText("2011-03-16"); + foo.ID3v2Tag()->addFrame(tf); + tf = new ID3v2::TextIdentificationFrame("TDRC", String::Latin1); + tf->setText("2012-04-17T12:01"); + foo.ID3v2Tag()->addFrame(tf); + tf = new ID3v2::TextIdentificationFrame("TMCL", String::Latin1); + tf->setText(StringList().append("Guitar").append("Artist 1").append("Drums").append("Artist 2")); + foo.ID3v2Tag()->addFrame(tf); + tf = new ID3v2::TextIdentificationFrame("TIPL", String::Latin1); + tf->setText(StringList().append("Producer").append("Artist 3").append("Mastering").append("Artist 4")); + foo.ID3v2Tag()->addFrame(tf); + foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TDRL", String::Latin1)); + foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TDTG", String::Latin1)); + foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TMOO", String::Latin1)); + foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TPRO", String::Latin1)); + foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TSOA", String::Latin1)); + foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TSOT", String::Latin1)); + foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TSST", String::Latin1)); + foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TSOP", String::Latin1)); + foo.save(MPEG::File::AllTags, true, 3); + } + { + MPEG::File bar(newname.c_str()); + tf = static_cast(bar.ID3v2Tag()->frameList("TDOR").front()); + CPPUNIT_ASSERT(tf); + CPPUNIT_ASSERT_EQUAL(size_t(1), tf->fieldList().size()); + CPPUNIT_ASSERT_EQUAL(String("2011"), tf->fieldList().front()); + tf = static_cast(bar.ID3v2Tag()->frameList("TDRC").front()); + CPPUNIT_ASSERT(tf); + CPPUNIT_ASSERT_EQUAL(size_t(1), tf->fieldList().size()); + CPPUNIT_ASSERT_EQUAL(String("2012"), tf->fieldList().front()); + tf = dynamic_cast(bar.ID3v2Tag()->frameList("TIPL").front()); + CPPUNIT_ASSERT(tf); + CPPUNIT_ASSERT_EQUAL(size_t(8), tf->fieldList().size()); + CPPUNIT_ASSERT_EQUAL(String("Guitar"), tf->fieldList()[0]); + CPPUNIT_ASSERT_EQUAL(String("Artist 1"), tf->fieldList()[1]); + CPPUNIT_ASSERT_EQUAL(String("Drums"), tf->fieldList()[2]); + CPPUNIT_ASSERT_EQUAL(String("Artist 2"), tf->fieldList()[3]); + CPPUNIT_ASSERT_EQUAL(String("Producer"), tf->fieldList()[4]); + CPPUNIT_ASSERT_EQUAL(String("Artist 3"), tf->fieldList()[5]); + CPPUNIT_ASSERT_EQUAL(String("Mastering"), tf->fieldList()[6]); + CPPUNIT_ASSERT_EQUAL(String("Artist 4"), tf->fieldList()[7]); + CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TDRL")); + CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TDTG")); + CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TMOO")); + CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TPRO")); + CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TSOA")); + CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TSOT")); + CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TSST")); + CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TSOP")); + } } void testCompressedFrameWithBrokenLength() @@ -694,7 +717,7 @@ public: #ifdef HAVE_ZLIB - ID3v2::AttachedPictureFrame *frame + ID3v2::AttachedPictureFrame *frame = dynamic_cast(f.ID3v2Tag()->frameListMap()["APIC"].front()); CPPUNIT_ASSERT(frame); CPPUNIT_ASSERT_EQUAL(String("image/bmp"), frame->mimeType()); @@ -707,13 +730,13 @@ public: // Skip the test if ZLIB is not installed. // The message "Compressed frames are currently not supported." will be displayed. - ID3v2::UnknownFrame *frame + ID3v2::UnknownFrame *frame = dynamic_cast(f.ID3v2Tag()->frameListMap()["APIC"].front()); CPPUNIT_ASSERT(frame); #endif } - + void testW000() { MPEG::File f(TEST_FILE_PATH_C("w000.mp3"), false); @@ -825,40 +848,157 @@ public: { ScopedFileCopy copy("rare_frames", ".mp3"); string newname = copy.fileName(); - MPEG::File f(newname.c_str()); - ID3v2::Tag *t = f.ID3v2Tag(); - ID3v2::Frame *frame = t->frameList("TCON")[0]; - CPPUNIT_ASSERT_EQUAL((size_t)1u, t->frameList("TCON").size()); - t->removeFrame(frame, true); - f.save(MPEG::File::ID3v2); - - MPEG::File f2(newname.c_str()); - t = f2.ID3v2Tag(); - CPPUNIT_ASSERT(t->frameList("TCON").isEmpty()); + + { + MPEG::File f(newname.c_str()); + ID3v2::Tag *t = f.ID3v2Tag(); + ID3v2::Frame *frame = t->frameList("TCON")[0]; + CPPUNIT_ASSERT_EQUAL(size_t(1), t->frameList("TCON").size()); + t->removeFrame(frame, true); + f.save(MPEG::File::ID3v2); + } + { + MPEG::File f2(newname.c_str()); + ID3v2::Tag *t = f2.ID3v2Tag(); + CPPUNIT_ASSERT(t->frameList("TCON").isEmpty()); + } } - + void testSaveAndStripID3v1ShouldNotAddFrameFromID3v1ToId3v2() { ScopedFileCopy copy("xing", ".mp3"); string newname = copy.fileName(); - + { MPEG::File foo(newname.c_str()); foo.tag()->setArtist("Artist"); foo.save(MPEG::File::ID3v1 | MPEG::File::ID3v2); } - + { MPEG::File bar(newname.c_str()); bar.ID3v2Tag()->removeFrames("TPE1"); // Should strip ID3v1 here and not add old values to ID3v2 again bar.save(MPEG::File::ID3v2, true); } - + MPEG::File f(newname.c_str()); CPPUNIT_ASSERT(!f.ID3v2Tag()->frameListMap().contains("TPE1")); } + void testParseChapterFrame() + { + ID3v2::ChapterFrame f( + ByteVector("CHAP" // Frame ID + "\x00\x00\x00\x20" // Frame size + "\x00\x00" // Frame flags + "\x43\x00" // Element ID + "\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\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"); + } + + void testRenderChapterFrame() + { + ID3v2::ChapterFrame f("CHAP"); + f.setElementID(ByteVector("\x43\x00", 2)); + f.setStartTime(3); + f.setEndTime(5); + f.setStartOffset(2); + f.setEndOffset(3); + ID3v2::TextIdentificationFrame eF("TIT2"); + eF.setText("CH1"); + f.addEmbeddedFrame(&eF); + CPPUNIT_ASSERT_EQUAL( + ByteVector("CHAP" // Frame ID + "\x00\x00\x00\x20" // Frame size + "\x00\x00" // Frame flags + "\x43\x00" // Element ID + "\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\x04" // Embedded frame size + "\x00\x00" // Embedded frame flags + "\x00" // TIT2 frame text encoding + "CH1", 42), // Chapter title + f.render()); + } + + void testParseTableOfContentsFrame() + { + ID3v2::TableOfContentsFrame f( + ByteVector("CTOC" // Frame ID + "\x00\x00\x00\x16" // Frame size + "\x00\x00" // Frame flags + "\x54\x00" // Element ID + "\x01" // CTOC flags + "\x02" // Entry count + "\x43\x00" // First entry + "\x44\x00" // Second entry + "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(!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(f.embeddedFrameList("TIT2").size() == 1); + CPPUNIT_ASSERT(f.embeddedFrameList("TIT2")[0]->toString() == "TC1"); + } + + void testRenderTableOfContentsFrame() + { + ID3v2::TableOfContentsFrame f("CTOC"); + f.setElementID(ByteVector("\x54\x00", 2)); + f.setIsTopLevel(false); + f.setIsOrdered(true); + f.addChildElement(ByteVector("\x43\x00", 2)); + f.addChildElement(ByteVector("\x44\x00", 2)); + ID3v2::TextIdentificationFrame eF("TIT2"); + eF.setText("TC1"); + f.addEmbeddedFrame(&eF); + CPPUNIT_ASSERT_EQUAL( + ByteVector("CTOC" // Frame ID + "\x00\x00\x00\x16" // Frame size + "\x00\x00" // Frame flags + "\x54\x00" // Element ID + "\x01" // CTOC flags + "\x02" // Entry count + "\x43\x00" // First entry + "\x44\x00" // Second entry + "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 + f.render()); + } + }; CPPUNIT_TEST_SUITE_REGISTRATION(TestID3v2); diff --git a/tests/test_matroska.cpp b/tests/test_matroska.cpp index 339f9a78..b3d88c68 100644 --- a/tests/test_matroska.cpp +++ b/tests/test_matroska.cpp @@ -18,85 +18,90 @@ class TestMatroska : public CppUnit::TestFixture CPPUNIT_TEST(testInsertAndExtract); CPPUNIT_TEST(testAudioProperties); CPPUNIT_TEST_SUITE_END(); - + public: void testPredefined() { ScopedFileCopy copy("matroska", ".mka"); string filename = copy.fileName(); - + EBML::Matroska::File f(filename.c_str()); CPPUNIT_ASSERT(f.isValid()); - + PropertyMap pm = f.properties(); PropertyMap::Iterator i = pm.find("ENCODER"); - CPPUNIT_ASSERT(i != f.properties().end()); - + CPPUNIT_ASSERT(i != pm.end()); + CPPUNIT_ASSERT_EQUAL(String("Lavf54.63.104"), i->second.front()); } - + void testInsertAndExtract() { ScopedFileCopy copy("matroska", ".mka"); string filename = copy.fileName(); - - EBML::Matroska::File f1(filename.c_str()); - CPPUNIT_ASSERT(f1.isValid()); - - Tag* t = f1.tag(); - - CPPUNIT_ASSERT(t != 0); - t->setTitle("Seconds of Silence"); - t->setArtist("Nobody"); - t->setAlbum("TagLib Test Suite"); - t->setComment("Well, there's nothing to say - a few special signs: ©’…ä–€ſ"); - t->setGenre("Air"); - t->setYear(2013); - t->setTrack(15); - - CPPUNIT_ASSERT(f1.save()); - - EBML::Matroska::File f2(filename.c_str()); - CPPUNIT_ASSERT(f2.isValid()); - - t = f2.tag(); - - CPPUNIT_ASSERT(t != 0); - CPPUNIT_ASSERT_EQUAL(String("Seconds of Silence"), t->title()); - CPPUNIT_ASSERT_EQUAL(String("Nobody"), t->artist()); - CPPUNIT_ASSERT_EQUAL(String("TagLib Test Suite"), t->album()); - CPPUNIT_ASSERT_EQUAL(String("Well, there's nothing to say - a few special signs: ©’…ä–€ſ"), t->comment()); - CPPUNIT_ASSERT_EQUAL(String("Air"), t->genre()); - CPPUNIT_ASSERT_EQUAL(2013u, t->year()); - CPPUNIT_ASSERT_EQUAL(15u, t->track()); - - PropertyMap pm = f2.properties(); - pm.erase("COMMENT"); - f2.setProperties(pm); - - CPPUNIT_ASSERT(f2.save()); - - EBML::Matroska::File f3(filename.c_str()); - CPPUNIT_ASSERT(f3.isValid()); - - pm = f3.properties(); - PropertyMap::Iterator i = pm.find("GENRE"); - - CPPUNIT_ASSERT(i != pm.end()); - CPPUNIT_ASSERT_EQUAL(String("Air"), i->second.front()); + + { + EBML::Matroska::File f1(filename.c_str()); + CPPUNIT_ASSERT(f1.isValid()); + + Tag* t = f1.tag(); + + CPPUNIT_ASSERT(t != 0); + t->setTitle("Seconds of Silence"); + t->setArtist("Nobody"); + t->setAlbum("TagLib Test Suite"); + t->setComment("Well, there's nothing to say - a few special signs: ©’…ä–€ſ"); + t->setGenre("Air"); + t->setYear(2013); + t->setTrack(15); + + CPPUNIT_ASSERT(f1.save()); + } + { + EBML::Matroska::File f2(filename.c_str()); + CPPUNIT_ASSERT(f2.isValid()); + + Tag* t = f2.tag(); + + CPPUNIT_ASSERT(t != 0); + CPPUNIT_ASSERT_EQUAL(String("Seconds of Silence"), t->title()); + CPPUNIT_ASSERT_EQUAL(String("Nobody"), t->artist()); + CPPUNIT_ASSERT_EQUAL(String("TagLib Test Suite"), t->album()); + CPPUNIT_ASSERT_EQUAL(String("Well, there's nothing to say - a few special signs: ©’…ä–€ſ"), t->comment()); + CPPUNIT_ASSERT_EQUAL(String("Air"), t->genre()); + CPPUNIT_ASSERT_EQUAL(2013u, t->year()); + CPPUNIT_ASSERT_EQUAL(15u, t->track()); + + PropertyMap pm = f2.properties(); + pm.erase("COMMENT"); + f2.setProperties(pm); + + CPPUNIT_ASSERT(f2.save()); + } + + { + EBML::Matroska::File f3(filename.c_str()); + CPPUNIT_ASSERT(f3.isValid()); + + PropertyMap pm = f3.properties(); + PropertyMap::Iterator i = pm.find("GENRE"); + + CPPUNIT_ASSERT(i != pm.end()); + CPPUNIT_ASSERT_EQUAL(String("Air"), i->second.front()); + } } - + void testAudioProperties() { ScopedFileCopy copy("matroska", ".mka"); string filename = copy.fileName(); - + EBML::Matroska::File f(filename.c_str()); CPPUNIT_ASSERT(f.isValid()); - + AudioProperties* a = f.audioProperties(); CPPUNIT_ASSERT(a != 0); - + // Not a very nice assertion... CPPUNIT_ASSERT_EQUAL(a->length(), 0); // Bitrate is not nice and thus not tested. diff --git a/tests/test_mp4.cpp b/tests/test_mp4.cpp index e38c4a6b..03d57a9e 100644 --- a/tests/test_mp4.cpp +++ b/tests/test_mp4.cpp @@ -158,6 +158,7 @@ public: f->tag()->itemListMap()["pgap"] = true; f->save(); + delete f; f = new MP4::File(filename.c_str()); CPPUNIT_ASSERT_EQUAL(true, f->tag()->itemListMap()["cpil"].toBool()); @@ -167,6 +168,7 @@ public: moov = atoms->atoms[0]; // original size + 'pgap' size + padding CPPUNIT_ASSERT_EQUAL(long(77 + 25 + 974), moov->length); + delete f; } void testGnre() @@ -231,7 +233,7 @@ public: void testProperties() { MP4::File f(TEST_FILE_PATH_C("has-tags.m4a")); - + PropertyMap tags = f.properties(); CPPUNIT_ASSERT_EQUAL(StringList("Test Artist"), tags["ARTIST"]); diff --git a/tests/test_mpeg.cpp b/tests/test_mpeg.cpp index ad6acc49..44925cac 100644 --- a/tests/test_mpeg.cpp +++ b/tests/test_mpeg.cpp @@ -32,18 +32,21 @@ public: string newname = copy.fileName(); String xxx = ByteVector(254, 'X'); - MPEG::File f(newname.c_str()); - CPPUNIT_ASSERT_EQUAL(false, f.hasID3v2Tag()); + { + MPEG::File f(newname.c_str()); + CPPUNIT_ASSERT_EQUAL(false, f.hasID3v2Tag()); - f.tag()->setTitle(xxx); - f.tag()->setArtist("Artist A"); - f.save(MPEG::File::AllTags, true, 4); - CPPUNIT_ASSERT_EQUAL(true, f.hasID3v2Tag()); - - MPEG::File f2(newname.c_str()); - CPPUNIT_ASSERT_EQUAL(TagLib::uint(4), f2.ID3v2Tag()->header()->majorVersion()); - CPPUNIT_ASSERT_EQUAL(String("Artist A"), f2.tag()->artist()); - CPPUNIT_ASSERT_EQUAL(xxx, f2.tag()->title()); + f.tag()->setTitle(xxx); + f.tag()->setArtist("Artist A"); + f.save(MPEG::File::AllTags, true, 4); + CPPUNIT_ASSERT_EQUAL(true, f.hasID3v2Tag()); + } + { + MPEG::File f2(newname.c_str()); + CPPUNIT_ASSERT_EQUAL(TagLib::uint(4), f2.ID3v2Tag()->header()->majorVersion()); + CPPUNIT_ASSERT_EQUAL(String("Artist A"), f2.tag()->artist()); + CPPUNIT_ASSERT_EQUAL(xxx, f2.tag()->title()); + } } void testSaveID3v24WrongParam() @@ -52,15 +55,18 @@ public: string newname = copy.fileName(); String xxx = ByteVector(254, 'X'); - MPEG::File f(newname.c_str()); - f.tag()->setTitle(xxx); - f.tag()->setArtist("Artist A"); - f.save(MPEG::File::AllTags, true, 8); - - MPEG::File f2(newname.c_str()); - CPPUNIT_ASSERT_EQUAL(TagLib::uint(4), f2.ID3v2Tag()->header()->majorVersion()); - CPPUNIT_ASSERT_EQUAL(String("Artist A"), f2.tag()->artist()); - CPPUNIT_ASSERT_EQUAL(xxx, f2.tag()->title()); + { + MPEG::File f(newname.c_str()); + f.tag()->setTitle(xxx); + f.tag()->setArtist("Artist A"); + f.save(MPEG::File::AllTags, true, 8); + } + { + MPEG::File f2(newname.c_str()); + CPPUNIT_ASSERT_EQUAL(TagLib::uint(4), f2.ID3v2Tag()->header()->majorVersion()); + CPPUNIT_ASSERT_EQUAL(String("Artist A"), f2.tag()->artist()); + CPPUNIT_ASSERT_EQUAL(xxx, f2.tag()->title()); + } } void testSaveID3v23() @@ -69,18 +75,21 @@ public: string newname = copy.fileName(); String xxx = ByteVector(254, 'X'); - MPEG::File f(newname.c_str()); - CPPUNIT_ASSERT_EQUAL(false, f.hasID3v2Tag()); + { + MPEG::File f(newname.c_str()); + CPPUNIT_ASSERT_EQUAL(false, f.hasID3v2Tag()); - f.tag()->setTitle(xxx); - f.tag()->setArtist("Artist A"); - f.save(MPEG::File::AllTags, true, 3); - CPPUNIT_ASSERT_EQUAL(true, f.hasID3v2Tag()); - - MPEG::File f2(newname.c_str()); - CPPUNIT_ASSERT_EQUAL(TagLib::uint(3), f2.ID3v2Tag()->header()->majorVersion()); - CPPUNIT_ASSERT_EQUAL(String("Artist A"), f2.tag()->artist()); - CPPUNIT_ASSERT_EQUAL(xxx, f2.tag()->title()); + f.tag()->setTitle(xxx); + f.tag()->setArtist("Artist A"); + f.save(MPEG::File::AllTags, true, 3); + CPPUNIT_ASSERT_EQUAL(true, f.hasID3v2Tag()); + } + { + MPEG::File f2(newname.c_str()); + CPPUNIT_ASSERT_EQUAL(TagLib::uint(3), f2.ID3v2Tag()->header()->majorVersion()); + CPPUNIT_ASSERT_EQUAL(String("Artist A"), f2.tag()->artist()); + CPPUNIT_ASSERT_EQUAL(xxx, f2.tag()->title()); + } } }; diff --git a/tests/test_propertymap.cpp b/tests/test_propertymap.cpp index 9b730a27..941a1a22 100644 --- a/tests/test_propertymap.cpp +++ b/tests/test_propertymap.cpp @@ -11,11 +11,11 @@ public: { TagLib::PropertyMap map1; CPPUNIT_ASSERT(map1.isEmpty()); - map1["\xc4\xd6\xdc"].append("test"); - CPPUNIT_ASSERT_EQUAL(map1.size(), (size_t)1u); + map1[L"\x00c4\x00d6\x00dc"].append("test"); + CPPUNIT_ASSERT_EQUAL(map1.size(), (size_t)1); TagLib::PropertyMap map2; - map2["\xc4\xd6\xdc"].append("test"); + map2[L"\x00c4\x00d6\x00dc"].append("test"); CPPUNIT_ASSERT(map1 == map2); CPPUNIT_ASSERT(map1.contains(map2)); @@ -24,6 +24,7 @@ public: CPPUNIT_ASSERT(map2.contains(map1)); map2["\xc4\xd6\xdc"].append("test 2"); + map2[L"\x00c4\x00d6\x00dc"].append("test 2"); CPPUNIT_ASSERT(!map2.contains(map1)); } diff --git a/tests/test_string.cpp b/tests/test_string.cpp index 0863d3ce..c42082ec 100644 --- a/tests/test_string.cpp +++ b/tests/test_string.cpp @@ -99,7 +99,7 @@ public: String unicode6(L"\u65e5\u672c\u8a9e", String::UTF16LE); CPPUNIT_ASSERT(unicode6[1] == (littleEndian ? L'\u672c' : L'\u2c67')); - wstring stduni = L"\u65e5\u672c\u8a9e"; + std::wstring stduni = L"\u65e5\u672c\u8a9e"; String unicode7(stduni); CPPUNIT_ASSERT(unicode7[1] == L'\u672c'); diff --git a/tests/utils.h b/tests/utils.h index 00cef628..5f2f9814 100644 --- a/tests/utils.h +++ b/tests/utils.h @@ -29,8 +29,8 @@ inline string copyFile(const string &filename, const string &ext) string newname = string(tempnam(NULL, NULL)) + ext; string oldname = testFilePath(filename) + ext; #ifdef _WIN32 - CopyFile(oldname.c_str(), newname.c_str(), FALSE); - SetFileAttributes(newname.c_str(), GetFileAttributes(newname.c_str()) & ~FILE_ATTRIBUTE_READONLY); + CopyFileA(oldname.c_str(), newname.c_str(), FALSE); + SetFileAttributesA(newname.c_str(), GetFileAttributesA(newname.c_str()) & ~FILE_ATTRIBUTE_READONLY); #else char buffer[4096]; int bytes;