mirror of
https://github.com/taglib/taglib.git
synced 2025-06-04 01:28:21 -04:00
Merge branch 'master' into merge-master-to-taglib2
Conflicts: ConfigureChecks.cmake taglib/CMakeLists.txt taglib/riff/aiff/aiffproperties.cpp taglib/toolkit/tbytevector.cpp taglib/toolkit/tbytevector.h taglib/toolkit/tfilestream.cpp taglib/toolkit/tstring.cpp taglib/toolkit/tutils.h tests/test_apetag.cpp tests/test_bytevector.cpp tests/test_flac.cpp tests/test_id3v2.cpp tests/test_propertymap.cpp
This commit is contained in:
commit
f93397fa7b
@ -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)
|
||||
|
||||
|
@ -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.
|
||||
|
2
INSTALL
2
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)
|
||||
|
||||
|
13
cmake/TestFloatFormat.c
Normal file
13
cmake/TestFloatFormat.c
Normal file
@ -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;
|
||||
}
|
60
cmake/modules/TestFloatFormat.cmake
Normal file
60
cmake/modules/TestFloatFormat.cmake
Normal file
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
266
taglib/mpeg/id3v2/frames/chapterframe.cpp
Normal file
266
taglib/mpeg/id3v2/frames/chapterframe.cpp
Normal file
@ -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 <tbytevectorlist.h>
|
||||
#include <tpropertymap.h>
|
||||
#include <tdebug.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#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<ChapterFrame *>(*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));
|
||||
}
|
242
taglib/mpeg/id3v2/frames/chapterframe.h
Normal file
242
taglib/mpeg/id3v2/frames/chapterframe.h
Normal file
@ -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
|
293
taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp
Normal file
293
taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp
Normal file
@ -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 <tbytevectorlist.h>
|
||||
#include <tpropertymap.h>
|
||||
#include <tdebug.h>
|
||||
|
||||
#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<TableOfContentsFrame *>(*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<TableOfContentsFrame *>(*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));
|
||||
}
|
255
taglib/mpeg/id3v2/frames/tableofcontentsframe.h
Normal file
255
taglib/mpeg/id3v2/frames/tableofcontentsframe.h
Normal file
@ -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
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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<char>::iterator and std::vector<char>::reverse_iterator.
|
||||
*/
|
||||
template <class TIterator>
|
||||
@ -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<char>::iterator and std::vector<char>::reverse_iterator.
|
||||
*/
|
||||
template <class TIterator>
|
||||
@ -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 <typename T, ByteOrder ENDIAN>
|
||||
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<const char *>(&value), sizeof(T));
|
||||
}
|
||||
|
||||
class ByteVector::ByteVectorPrivate
|
||||
template <typename TFloat, typename TInt, ByteOrder ENDIAN>
|
||||
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 <typename TFloat, typename TInt, ByteOrder ENDIAN>
|
||||
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<char *>(&tmp), sizeof(TInt));
|
||||
}
|
||||
|
||||
template <ByteOrder ENDIAN>
|
||||
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<ulonglong>(bytes[2]) << 56)
|
||||
| (static_cast<ulonglong>(bytes[3]) << 48)
|
||||
| (static_cast<ulonglong>(bytes[4]) << 40)
|
||||
| (static_cast<ulonglong>(bytes[5]) << 32)
|
||||
| (static_cast<ulonglong>(bytes[6]) << 24)
|
||||
| (static_cast<ulonglong>(bytes[7]) << 16)
|
||||
| (static_cast<ulonglong>(bytes[8]) << 8)
|
||||
| (static_cast<ulonglong>(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<long double>(fraction), exponent - 16383 - 63);
|
||||
}
|
||||
|
||||
if(negative)
|
||||
return -val;
|
||||
else
|
||||
return val;
|
||||
}
|
||||
|
||||
class ByteVector::ByteVectorPrivate
|
||||
{
|
||||
public:
|
||||
ByteVectorPrivate()
|
||||
: data(new std::vector<char>())
|
||||
, offset(0)
|
||||
, length(0)
|
||||
{
|
||||
}
|
||||
ByteVectorPrivate() :
|
||||
data(new std::vector<char>()),
|
||||
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<char>(l, c))
|
||||
, offset(0)
|
||||
, length(l)
|
||||
{
|
||||
}
|
||||
ByteVectorPrivate(size_t l, char c) :
|
||||
data(new std::vector<char>(l, c)),
|
||||
offset(0),
|
||||
length(l) {}
|
||||
|
||||
ByteVectorPrivate(const char *s, size_t l)
|
||||
: data(new std::vector<char>(s, s + l))
|
||||
, offset(0)
|
||||
, length(l)
|
||||
{
|
||||
}
|
||||
|
||||
void detach()
|
||||
{
|
||||
if(!data.unique()) {
|
||||
data.reset(new std::vector<char>(data->begin() + offset, data->begin() + offset + length));
|
||||
offset = 0;
|
||||
}
|
||||
}
|
||||
ByteVectorPrivate(const char *s, size_t l) :
|
||||
data(new std::vector<char>(s, s + l)),
|
||||
offset(0),
|
||||
length(l) {}
|
||||
|
||||
~ByteVectorPrivate()
|
||||
{
|
||||
}
|
||||
~ByteVectorPrivate() {}
|
||||
|
||||
SHARED_PTR<std::vector<char> > data;
|
||||
size_t offset;
|
||||
@ -329,42 +398,62 @@ ByteVector ByteVector::fromUInt64BE(ulonglong value)
|
||||
return fromNumber<ulonglong, BigEndian>(value);
|
||||
}
|
||||
|
||||
ByteVector ByteVector::fromFloat32LE(float value)
|
||||
{
|
||||
return fromFloat<float, uint, LittleEndian>(value);
|
||||
}
|
||||
|
||||
ByteVector ByteVector::fromFloat32BE(float value)
|
||||
{
|
||||
return fromFloat<float, uint, BigEndian>(value);
|
||||
}
|
||||
|
||||
ByteVector ByteVector::fromFloat64LE(double value)
|
||||
{
|
||||
return fromFloat<double, ulonglong, LittleEndian>(value);
|
||||
}
|
||||
|
||||
ByteVector ByteVector::fromFloat64BE(double value)
|
||||
{
|
||||
return fromFloat<double, ulonglong, BigEndian>(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<long long>(toNumber<ulonglong, 8, BigEndian>(*this, offset));
|
||||
}
|
||||
}
|
||||
|
||||
float ByteVector::toFloat32LE(size_t offset) const
|
||||
{
|
||||
return toFloat<float, uint, LittleEndian>(*this, offset);
|
||||
}
|
||||
|
||||
float ByteVector::toFloat32BE(size_t offset) const
|
||||
{
|
||||
typedef std::numeric_limits<float> Limits;
|
||||
return toFloat<float, uint, BigEndian>(*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<const uchar*>(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<uint>(bytes[1] & 0x7f) << 16)
|
||||
| (static_cast<uint>(bytes[2]) << 8)
|
||||
| (static_cast<uint>(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<float>(fraction), exponent - 127 - 23);
|
||||
}
|
||||
|
||||
if(negative)
|
||||
return -val;
|
||||
else
|
||||
return val;
|
||||
}
|
||||
double ByteVector::toFloat64LE(size_t offset) const
|
||||
{
|
||||
return toFloat<double, ulonglong, LittleEndian>(*this, offset);
|
||||
}
|
||||
|
||||
double ByteVector::toFloat64BE(size_t offset) const
|
||||
{
|
||||
typedef std::numeric_limits<double> Limits;
|
||||
return toFloat<double, ulonglong, BigEndian>(*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<const uchar*>(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<ulonglong>(bytes[1] & 0x0F) << 48)
|
||||
| (static_cast<ulonglong>(bytes[2]) << 40)
|
||||
| (static_cast<ulonglong>(bytes[3]) << 32)
|
||||
| (static_cast<ulonglong>(bytes[4]) << 24)
|
||||
| (static_cast<ulonglong>(bytes[5]) << 16)
|
||||
| (static_cast<ulonglong>(bytes[6]) << 8)
|
||||
| (static_cast<ulonglong>(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<double>(fraction), exponent - 1023 - 52);
|
||||
}
|
||||
|
||||
if(negative)
|
||||
return -val;
|
||||
else
|
||||
return val;
|
||||
}
|
||||
long double ByteVector::toFloat80LE(size_t offset) const
|
||||
{
|
||||
return toFloat80<LittleEndian>(*this, offset);
|
||||
}
|
||||
|
||||
long double ByteVector::toFloat80BE(size_t offset) const
|
||||
{
|
||||
typedef std::numeric_limits<long double> 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<const uchar*>(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<ulonglong>(bytes[2]) << 56)
|
||||
| (static_cast<ulonglong>(bytes[3]) << 48)
|
||||
| (static_cast<ulonglong>(bytes[4]) << 40)
|
||||
| (static_cast<ulonglong>(bytes[5]) << 32)
|
||||
| (static_cast<ulonglong>(bytes[6]) << 24)
|
||||
| (static_cast<ulonglong>(bytes[7]) << 16)
|
||||
| (static_cast<ulonglong>(bytes[8]) << 8)
|
||||
| (static_cast<ulonglong>(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<long double>(fraction), exponent - 16383 - 63);
|
||||
}
|
||||
|
||||
if(negative)
|
||||
return -val;
|
||||
else
|
||||
return val;
|
||||
}
|
||||
return toFloat80<BigEndian>(*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<char>::const_iterator begin = d->data->begin() + d->offset;
|
||||
std::vector<char>::const_iterator end = begin + d->length;
|
||||
d->data.reset(new std::vector<char>(begin, end));
|
||||
d->offset = 0;
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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<DWORD>(buffer.size()), &length, NULL))
|
||||
return static_cast<size_t>(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<const char *>(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<long>(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<offset_t>(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<LPDWORD>(&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.");
|
||||
|
@ -34,10 +34,10 @@ using namespace TagLib;
|
||||
# include "tsmartptr.h"
|
||||
# include <windows.h>
|
||||
|
||||
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<FileNameData> 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)
|
||||
|
@ -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<wchar_t>(c);
|
||||
s += 2;
|
||||
}
|
||||
}
|
||||
|
@ -47,6 +47,7 @@
|
||||
#include "tstring.h"
|
||||
#include <cstdio>
|
||||
#include <cstdarg>
|
||||
#include <cstring>
|
||||
|
||||
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
|
||||
}
|
||||
|
@ -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());
|
||||
|
@ -22,6 +22,7 @@
|
||||
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <cstring>
|
||||
#include <tbytevector.h>
|
||||
#include <tbytevectorlist.h>
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
@ -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<unsigned char>(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<const char*>(PI32), 5);
|
||||
CPPUNIT_ASSERT_EQUAL(31415, static_cast<int>(pi32.toFloat32BE(1) * 10000));
|
||||
ByteVector pi32le(reinterpret_cast<const char*>(PI32LE), 5);
|
||||
CPPUNIT_ASSERT_EQUAL(31415, static_cast<int>(pi32le.toFloat32LE(1) * 10000));
|
||||
|
||||
ByteVector pi64(reinterpret_cast<const char*>(PI64), 9);
|
||||
CPPUNIT_ASSERT_EQUAL(31415, static_cast<int>(pi64.toFloat64BE(1) * 10000));
|
||||
ByteVector pi32be(reinterpret_cast<const char*>(PI32BE), 5);
|
||||
CPPUNIT_ASSERT_EQUAL(31415, static_cast<int>(pi32be.toFloat32BE(1) * 10000));
|
||||
|
||||
ByteVector pi80(reinterpret_cast<const char*>(PI80), 11);
|
||||
CPPUNIT_ASSERT_EQUAL(31415, static_cast<int>(pi80.toFloat80BE(1) * 10000));
|
||||
ByteVector pi64le(reinterpret_cast<const char*>(PI64LE), 9);
|
||||
CPPUNIT_ASSERT_EQUAL(31415, static_cast<int>(pi64le.toFloat64LE(1) * 10000));
|
||||
|
||||
ByteVector pi64be(reinterpret_cast<const char*>(PI64BE), 9);
|
||||
CPPUNIT_ASSERT_EQUAL(31415, static_cast<int>(pi64be.toFloat64BE(1) * 10000));
|
||||
|
||||
ByteVector pi80le(reinterpret_cast<const char*>(PI80LE), 11);
|
||||
CPPUNIT_ASSERT_EQUAL(31415, static_cast<int>(pi80le.toFloat80LE(1) * 10000));
|
||||
|
||||
ByteVector pi80be(reinterpret_cast<const char*>(PI80BE), 11);
|
||||
CPPUNIT_ASSERT_EQUAL(31415, static_cast<int>(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()
|
||||
|
@ -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());
|
||||
|
@ -4,12 +4,9 @@
|
||||
|
||||
#include <string>
|
||||
#include <stdio.h>
|
||||
// so evil :(
|
||||
#define protected public
|
||||
#include <id3v2tag.h>
|
||||
#include <mpegfile.h>
|
||||
#include <id3v2frame.h>
|
||||
#undef protected
|
||||
#include <uniquefileidentifierframe.h>
|
||||
#include <textidentificationframe.h>
|
||||
#include <attachedpictureframe.h>
|
||||
@ -22,6 +19,8 @@
|
||||
#include <urllinkframe.h>
|
||||
#include <ownershipframe.h>
|
||||
#include <unknownframe.h>
|
||||
#include <chapterframe.h>
|
||||
#include <tableofcontentsframe.h>
|
||||
#include <tdebug.h>
|
||||
#include <tpropertymap.h>
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
@ -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<ID3v2::PopularimeterFrame *>(bar.ID3v2Tag()->frameList("POPM").front())->email());
|
||||
CPPUNIT_ASSERT_EQUAL(200, dynamic_cast<ID3v2::PopularimeterFrame *>(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<ID3v2::PopularimeterFrame *>(bar.ID3v2Tag()->frameList("POPM").front())->email());
|
||||
CPPUNIT_ASSERT_EQUAL(200, dynamic_cast<ID3v2::PopularimeterFrame *>(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<ID3v2::TextIdentificationFrame *>(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<ID3v2::TextIdentificationFrame *>(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<ID3v2::TextIdentificationFrame *>(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<ID3v2::TextIdentificationFrame *>(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<ID3v2::TextIdentificationFrame *>(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<ID3v2::TextIdentificationFrame *>(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<TagLib::ID3v2::AttachedPictureFrame*>(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<TagLib::ID3v2::UnknownFrame*>(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);
|
||||
|
@ -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.
|
||||
|
@ -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"]);
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
@ -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));
|
||||
|
||||
}
|
||||
|
@ -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');
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user