Merge branch 'master' into taglib2

Conflicts:
	CMakeLists.txt
	ConfigureChecks.cmake
	config.h.cmake
	taglib/CMakeLists.txt
	taglib/ape/apefile.cpp
	taglib/ape/apefile.h
	taglib/ape/apeproperties.cpp
	taglib/ape/apeproperties.h
	taglib/ape/apetag.cpp
	taglib/asf/asfattribute.cpp
	taglib/asf/asffile.cpp
	taglib/asf/asffile.h
	taglib/asf/asfpicture.cpp
	taglib/asf/asfpicture.h
	taglib/asf/asfproperties.cpp
	taglib/asf/asfproperties.h
	taglib/audioproperties.cpp
	taglib/flac/flacfile.cpp
	taglib/flac/flacfile.h
	taglib/flac/flacproperties.cpp
	taglib/flac/flacproperties.h
	taglib/it/itproperties.cpp
	taglib/mod/modproperties.cpp
	taglib/mp4/mp4atom.cpp
	taglib/mp4/mp4file.cpp
	taglib/mp4/mp4file.h
	taglib/mp4/mp4properties.cpp
	taglib/mp4/mp4tag.cpp
	taglib/mp4/mp4tag.h
	taglib/mpc/mpcfile.cpp
	taglib/mpc/mpcfile.h
	taglib/mpc/mpcproperties.cpp
	taglib/mpc/mpcproperties.h
	taglib/mpeg/id3v2/frames/chapterframe.cpp
	taglib/mpeg/id3v2/frames/synchronizedlyricsframe.cpp
	taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp
	taglib/mpeg/mpegfile.cpp
	taglib/mpeg/mpegfile.h
	taglib/mpeg/mpegheader.cpp
	taglib/mpeg/mpegproperties.cpp
	taglib/mpeg/mpegproperties.h
	taglib/mpeg/xingheader.cpp
	taglib/mpeg/xingheader.h
	taglib/ogg/opus/opusfile.cpp
	taglib/ogg/opus/opusfile.h
	taglib/ogg/opus/opusproperties.cpp
	taglib/ogg/opus/opusproperties.h
	taglib/ogg/speex/speexfile.cpp
	taglib/ogg/speex/speexfile.h
	taglib/ogg/speex/speexproperties.cpp
	taglib/ogg/speex/speexproperties.h
	taglib/ogg/vorbis/vorbisfile.cpp
	taglib/ogg/vorbis/vorbisfile.h
	taglib/ogg/vorbis/vorbisproperties.cpp
	taglib/ogg/vorbis/vorbisproperties.h
	taglib/riff/aiff/aifffile.cpp
	taglib/riff/aiff/aifffile.h
	taglib/riff/aiff/aiffproperties.cpp
	taglib/riff/aiff/aiffproperties.h
	taglib/riff/wav/infotag.h
	taglib/riff/wav/wavfile.cpp
	taglib/riff/wav/wavfile.h
	taglib/riff/wav/wavproperties.cpp
	taglib/riff/wav/wavproperties.h
	taglib/s3m/s3mproperties.cpp
	taglib/taglib_config.h.cmake
	taglib/toolkit/tbytevector.cpp
	taglib/toolkit/tfile.cpp
	taglib/toolkit/tfile.h
	taglib/toolkit/tfilestream.cpp
	taglib/toolkit/trefcounter.cpp
	taglib/toolkit/tstring.cpp
	taglib/toolkit/tstring.h
	taglib/toolkit/tutils.h
	taglib/trueaudio/trueaudiofile.cpp
	taglib/trueaudio/trueaudiofile.h
	taglib/trueaudio/trueaudioproperties.cpp
	taglib/trueaudio/trueaudioproperties.h
	taglib/wavpack/wavpackfile.cpp
	taglib/wavpack/wavpackfile.h
	taglib/wavpack/wavpackproperties.cpp
	taglib/wavpack/wavpackproperties.h
	taglib/xm/xmproperties.cpp
	taglib/xm/xmproperties.h
	tests/CMakeLists.txt
	tests/data/alaw.wav
	tests/test_asf.cpp
	tests/test_mp4.cpp
	tests/test_ogg.cpp
	tests/test_opus.cpp
	tests/test_string.cpp
	tests/test_wav.cpp
	tests/test_wavpack.cpp
This commit is contained in:
Tsuda Kageyu
2015-08-10 00:24:20 +09:00
178 changed files with 4703 additions and 2281 deletions

18
.astylerc Normal file
View File

@ -0,0 +1,18 @@
--suffix=none
--style=kr
--indent=spaces=2
--indent-col1-comments
--min-conditional-indent=0
--attach-extern-c
--attach-namespaces
--indent-namespaces
--pad-oper
--unpad-paren
--align-pointer=name
--align-reference=name
--max-instatement-indent=40
--break-closing-brackets
--remove-brackets
--convert-tabs
--max-code-length=100
--break-after-logical

View File

@ -1,7 +1,12 @@
language: cpp
sudo: false
compiler:
- gcc
- clang
install: sudo apt-get install libcppunit-dev zlib1g-dev
addons:
apt:
packages:
- libcppunit-dev
- zlib1g-dev
script: cmake -DBUILD_TESTS=ON -DBUILD_EXAMPLES=ON . && make && make check

View File

@ -6,7 +6,7 @@ if(NOT ${CMAKE_VERSION} VERSION_LESS 2.8.12)
cmake_policy(SET CMP0022 OLD)
endif()
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules")
option(ENABLE_STATIC "Make static version of libtag" OFF)
if(ENABLE_STATIC)
@ -53,8 +53,8 @@ if (MSVC AND ENABLE_STATIC_RUNTIME)
endif()
set(TAGLIB_LIB_MAJOR_VERSION "1")
set(TAGLIB_LIB_MINOR_VERSION "9")
set(TAGLIB_LIB_PATCH_VERSION "1")
set(TAGLIB_LIB_MINOR_VERSION "10")
set(TAGLIB_LIB_PATCH_VERSION "0")
set(TAGLIB_LIB_VERSION_STRING "${TAGLIB_LIB_MAJOR_VERSION}.${TAGLIB_LIB_MINOR_VERSION}.${TAGLIB_LIB_PATCH_VERSION}")
@ -62,9 +62,9 @@ set(TAGLIB_LIB_VERSION_STRING "${TAGLIB_LIB_MAJOR_VERSION}.${TAGLIB_LIB_MINOR_VE
# 2. If any interfaces have been added, removed, or changed since the last update, increment current, and set revision to 0.
# 3. If any interfaces have been added since the last public release, then increment age.
# 4. If any interfaces have been removed since the last public release, then set age to 0.
set(TAGLIB_SOVERSION_CURRENT 15)
set(TAGLIB_SOVERSION_CURRENT 16)
set(TAGLIB_SOVERSION_REVISION 0)
set(TAGLIB_SOVERSION_AGE 14)
set(TAGLIB_SOVERSION_AGE 15)
math(EXPR TAGLIB_SOVERSION_MAJOR "${TAGLIB_SOVERSION_CURRENT} - ${TAGLIB_SOVERSION_AGE}")
math(EXPR TAGLIB_SOVERSION_MINOR "${TAGLIB_SOVERSION_AGE}")
@ -73,18 +73,18 @@ math(EXPR TAGLIB_SOVERSION_PATCH "${TAGLIB_SOVERSION_REVISION}")
include(ConfigureChecks.cmake)
if(NOT WIN32)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/taglib-config.cmake ${CMAKE_CURRENT_BINARY_DIR}/taglib-config )
install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/taglib-config DESTINATION ${BIN_INSTALL_DIR})
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/taglib-config.cmake" "${CMAKE_CURRENT_BINARY_DIR}/taglib-config")
install(PROGRAMS "${CMAKE_CURRENT_BINARY_DIR}/taglib-config" DESTINATION "${BIN_INSTALL_DIR}")
endif()
if(WIN32)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/taglib-config.cmd.cmake ${CMAKE_CURRENT_BINARY_DIR}/taglib-config.cmd )
install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/taglib-config.cmd DESTINATION ${BIN_INSTALL_DIR})
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/taglib-config.cmd.cmake" "${CMAKE_CURRENT_BINARY_DIR}/taglib-config.cmd")
install(PROGRAMS "${CMAKE_CURRENT_BINARY_DIR}/taglib-config.cmd" DESTINATION "${BIN_INSTALL_DIR}")
endif()
if(NOT WIN32 AND NOT BUILD_FRAMEWORK)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/taglib.pc.cmake ${CMAKE_CURRENT_BINARY_DIR}/taglib.pc )
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/taglib.pc DESTINATION ${LIB_INSTALL_DIR}/pkgconfig)
if(NOT BUILD_FRAMEWORK)
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/taglib.pc.cmake" "${CMAKE_CURRENT_BINARY_DIR}/taglib.pc")
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/taglib.pc" DESTINATION "${LIB_INSTALL_DIR}/pkgconfig")
endif()
if(NOT HAVE_ZLIB AND ZLIB_SOURCE)
@ -93,7 +93,7 @@ if(NOT HAVE_ZLIB AND ZLIB_SOURCE)
endif()
include_directories(${CMAKE_CURRENT_BINARY_DIR})
configure_file(config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config.h)
configure_file(config.h.cmake "${CMAKE_CURRENT_BINARY_DIR}/config.h")
option(TRACE_IN_RELEASE "Output debug messages even in release mode" OFF)
if(TRACE_IN_RELEASE)
@ -108,7 +108,7 @@ if(BUILD_TESTS)
endif(BUILD_TESTS)
add_subdirectory(examples)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.cmake ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile)
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.cmake" "${CMAKE_CURRENT_BINARY_DIR}/Doxyfile")
file(COPY doc/taglib.png DESTINATION doc)
add_custom_target(docs doxygen)
@ -118,5 +118,7 @@ configure_file(
"${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake"
IMMEDIATE @ONLY)
add_custom_target(uninstall
COMMAND ${CMAKE_COMMAND} -P "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake")
if (NOT TARGET uninstall)
add_custom_target(uninstall
COMMAND "${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake")
endif()

View File

@ -1,80 +1,39 @@
include(CheckIncludeFile)
include(CheckIncludeFiles)
include(CheckSymbolExists)
include(CheckFunctionExists)
include(CheckLibraryExists)
include(CheckTypeSize)
include(CheckCXXCompilerFlag)
include(CheckCXXSourceCompiles)
include(TestBigEndian)
include(TestFloatFormat)
include(TestLargeFiles)
# Determine whether your compiler supports C++0x/C++11 and enable it if possible.
# This check covers GCC, Clang and ICC.
if(NOT MSVC AND NOT CMAKE_CXX_FLAGS MATCHES "-std=^\\s+")
CHECK_CXX_COMPILER_FLAG("-std=c++11" COMPILER_FLAG_CXX11)
if(COMPILER_FLAG_CXX11)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
else()
CHECK_CXX_COMPILER_FLAG("-std=c++0x" COMPILER_FLAG_CXX0X)
if(COMPILER_FLAG_CXX0X)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x")
endif()
endif()
endif()
# Check if the size of numeric types are suitable.
check_type_size("short" SIZEOF_SHORT)
if(NOT ${SIZEOF_SHORT} EQUAL 2)
MESSAGE(FATAL_ERROR "TagLib requires that short is 16-bit wide.")
message(FATAL_ERROR "TagLib requires that short is 16-bit wide.")
endif()
check_type_size("int" SIZEOF_INT)
if(NOT ${SIZEOF_INT} EQUAL 4)
MESSAGE(FATAL_ERROR "TagLib requires that int is 32-bit wide.")
message(FATAL_ERROR "TagLib requires that int is 32-bit wide.")
endif()
check_type_size("long long" SIZEOF_LONGLONG)
if(NOT ${SIZEOF_LONGLONG} EQUAL 8)
MESSAGE(FATAL_ERROR "TagLib requires that long long is 64-bit wide.")
message(FATAL_ERROR "TagLib requires that long long is 64-bit wide.")
endif()
check_type_size("wchar_t" SIZEOF_WCHAR_T)
if(${SIZEOF_WCHAR_T} LESS 2)
MESSAGE(FATAL_ERROR "TagLib requires that wchar_t is sufficient to store a UTF-16 char.")
message(FATAL_ERROR "TagLib requires that wchar_t is sufficient to store a UTF-16 char.")
endif()
check_type_size("float" SIZEOF_FLOAT)
if(NOT ${SIZEOF_FLOAT} EQUAL 4)
MESSAGE(FATAL_ERROR "TagLib requires that float is 32-bit wide.")
message(FATAL_ERROR "TagLib requires that float is 32-bit wide.")
endif()
check_type_size("double" SIZEOF_DOUBLE)
if(NOT ${SIZEOF_DOUBLE} EQUAL 8)
MESSAGE(FATAL_ERROR "TagLib requires that double is 64-bit wide.")
endif()
# Determine the CPU byte order.
test_big_endian(IS_BIG_ENDIAN)
if(NOT IS_BIG_ENDIAN)
set(SYSTEM_BYTEORDER 1)
else()
set(SYSTEM_BYTEORDER 2)
endif()
# Check if the format of floating point types are suitable.
test_float_format(FP_IEEE754)
if(${FP_IEEE754} EQUAL 1)
set(FLOAT_BYTEORDER 1)
elseif(${FP_IEEE754} EQUAL 2)
set(FLOAT_BYTEORDER 2)
else()
MESSAGE(FATAL_ERROR "TagLib requires that floating point types are IEEE754 compliant.")
message(FATAL_ERROR "TagLib requires that double is 64-bit wide.")
endif()
# Determine whether your compiler supports large files.
@ -182,30 +141,16 @@ endif()
# Determine which kind of byte swap functions your compiler supports.
# GCC's __builtin_bswap* should be checked individually
# because some of them can be missing depends on the GCC version.
check_cxx_source_compiles("
int main() {
__builtin_bswap16(0);
return 0;
}
" HAVE_GCC_BYTESWAP_16)
check_cxx_source_compiles("
int main() {
__builtin_bswap32(0);
return 0;
}
" HAVE_GCC_BYTESWAP_32)
check_cxx_source_compiles("
int main() {
__builtin_bswap64(0);
return 0;
}
" HAVE_GCC_BYTESWAP_64)
" HAVE_GCC_BYTESWAP)
if(NOT HAVE_GCC_BYTESWAP_16 OR NOT HAVE_GCC_BYTESWAP_32 OR NOT HAVE_GCC_BYTESWAP_64)
if(NOT HAVE_GCC_BYTESWAP)
check_cxx_source_compiles("
#include <byteswap.h>
int main() {
@ -253,18 +198,30 @@ if(NOT HAVE_GCC_BYTESWAP_16 OR NOT HAVE_GCC_BYTESWAP_32 OR NOT HAVE_GCC_BYTESWAP
endif()
endif()
# Determine whether your compiler supports snprintf or sprintf_s.
# Determine whether your compiler supports some safer version of vsprintf.
check_cxx_source_compiles("
#include <cstdio>
int main() { char buf[20]; snprintf(buf, 20, \"%d\", 1); return 0; }
" HAVE_SNPRINTF)
#include <cstdarg>
int main() {
char buf[20];
va_list args;
vsnprintf(buf, 20, \"%d\", args);
return 0;
}
" HAVE_VSNPRINTF)
if(NOT HAVE_SNPRINTF)
if(NOT HAVE_VSNPRINTF)
check_cxx_source_compiles("
#include <cstdio>
int main() { char buf[20]; sprintf_s(buf, \"%d\", 1); return 0; }
" HAVE_SPRINTF_S)
#include <cstdarg>
int main() {
char buf[20];
va_list args;
vsprintf_s(buf, \"%d\", args);
return 0;
}
" HAVE_VSPRINTF_S)
endif()
# Check which your compiler supports ISO _strdup.
@ -290,8 +247,10 @@ endif()
# Determine whether CppUnit is installed.
find_package(CppUnit)
if(NOT CppUnit_FOUND AND BUILD_TESTS)
message(STATUS "CppUnit not found, disabling tests.")
set(BUILD_TESTS OFF)
if(BUILD_TESTS)
find_package(CppUnit)
if(NOT CppUnit_FOUND)
message(STATUS "CppUnit not found, disabling tests.")
set(BUILD_TESTS OFF)
endif()
endif()

48
NEWS
View File

@ -1,3 +1,49 @@
TagLib 1.10 (??? ??, 2015)
==========================
1.10 BETA:
* Fixed binary incompatible change in TagLib::String.
* Fixed crash when parsing certain FLAC files.
* Fixed crash when encoding empty strings.
* Fixed saving of certain XM files on OS X.
* Allowed Xiph and APE generic getters to return space-concatenated values.
* Added support for ID3v2 ETCO and SYLT frames.
* Added support for album artist in PropertyMap API of MP4 files.
* Added support for embedded frames in ID3v2 CHAP and CTOC frames.
* Added ZLIB_SOURCE build option.
* Fixed possible file corruptions when removing tags from WAV files.
* Added support for MP4 files with 64-bit atoms in certain 64-bit environments.
* Better handling of duplicate ID3v2 tags in MPEG files.
* Prevented ID3v2 padding from being too large.
* Fixed crash when parsing corrupted APE files.
* Added support for AIFF-C files.
* Fixed crash when parsing corrupted WAV files.
* Fixed crash when parsing corrupted Ogg FLAC files.
* Fixed crash when parsing corrupted MPEG files.
* Fixed saving empty tags in WAV files.
* Fixed crash when parsing corrupted Musepack files.
* Fixed possible memory leaks when parsing AIFF and WAV files.
* Fixed crash when parsing corrupted MP4 files.
* Stopped writing empty ID3v2 frames.
* Fixed possible file corruptions when saving WMA files.
* Added TagLib::MP4::Tag::isEmpty().
* Added accessors to manipulate MP4 tags .
* Fixed crash when parsing corrupted WavPack files.
* Fixed seeking MPEG frames.
* Allowed generating taglib.pc on Windows.
* Fixed reading FLAC files with zero-sized padding blocks.
* New API for the audio length in milliseconds.
* Added support for reading the encoder information of WMA files.
* Added support for reading the codec of WAV files.
* Added support for multi channel WavPack files.
* Added support for reading the nominal bitrate of Ogg Speex files.
* Added support for VBR headers in MPEG files.
* Marked FLAC::File::streamInfoData() deprecated. It returns an empty ByteVector.
* Marked FLAC::File::streamLength() deprecated. It returns zero.
* Fixed possible file corruptions when adding an ID3v1 tag to FLAC files.
* Many smaller bug fixes and performance improvements.
TagLib 1.9.1 (Oct 8, 2013)
==========================
@ -5,7 +51,7 @@ TagLib 1.9.1 (Oct 8, 2013)
* Fixed constructing String from ByteVector.
* Fixed compilation on MSVC with the /Zc:wchar_t- option.
* Fixed detecting of RIFF files with invalid chunk sizes.
* Added TagLib::MP4::PropertyMap::codec().
* Added TagLib::MP4::Properties::codec().
TagLib 1.9 (Oct 6, 2013)
========================

View File

@ -1,17 +0,0 @@
int main(int argc, char **argv)
{
int ret = 0;
double bin1[] = {
// "*TAGLIB*" encoded as a little-endian floating-point number
(double)3.9865557444897601e-105, (double)0.0
};
float bin2[] = {
// "*TL*" encoded as a little-endian floating-point number
(float)1.81480400e-013, (float)0.0
};
ret += ((int*)bin1)[argc];
ret += ((int*)bin2)[argc];
return ret;
}

View File

@ -1,61 +0,0 @@
# Returns 1 if IEEE754 little-endian, 2 if IEEE754 big-endian, otherwise 0.
MACRO(TEST_FLOAT_FORMAT FP_IEEE754)
IF(NOT FP_IEEE754)
TRY_COMPILE(HAVE_FLOAT_FORMAT_BIN
"${CMAKE_BINARY_DIR}" "${CMAKE_SOURCE_DIR}/cmake/TestFloatFormat.c"
COPY_FILE "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/TestFloatFormat.bin")
SET(FP_IEEE754 0)
IF(HAVE_FLOAT_FORMAT_BIN)
# dont match first/last letter because of string rounding errors :-)
FILE(STRINGS "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/TestFloatFormat.bin"
DOUBLE_IEEE754_LE LIMIT_COUNT 1 REGEX "TAGLIB")
FILE(STRINGS "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/TestFloatFormat.bin"
DOUBLE_IEEE754_BE LIMIT_COUNT 1 REGEX "BILGAT")
FILE(STRINGS "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/TestFloatFormat.bin"
FLOAT_IEEE754_LE LIMIT_COUNT 1 REGEX "TL")
FILE(STRINGS "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/TestFloatFormat.bin"
FLOAT_IEEE754_BE LIMIT_COUNT 1 REGEX "LT")
IF(DOUBLE_IEEE754_LE AND FLOAT_IEEE754_LE)
SET(FP_IEEE754_LE 1)
ENDIF()
IF(DOUBLE_IEEE754_BE AND FLOAT_IEEE754_BE)
SET(FP_IEEE754_BE 1)
ENDIF()
# OS X Universal binaries will contain both strings, set it to the host
IF(FP_IEEE754_LE AND FP_IEEE754_BE)
IF(CMAKE_SYSTEM_PROCESSOR MATCHES powerpc)
SET(FP_IEEE754_LE FALSE)
SET(FP_IEEE754_BE TRUE)
ELSE()
SET(FP_IEEE754_LE TRUE)
SET(FP_IEEE754_BE FALSE)
ENDIF()
ENDIF()
IF(FP_IEEE754_LE)
SET(FP_IEEE754 1)
ELSEIF(FP_IEEE754_BE)
SET(FP_IEEE754 2)
ENDIF()
ENDIF()
# just some informational output for the user
IF(FP_IEEE754_LE)
MESSAGE(STATUS "Checking the floating point format - IEEE754 (LittleEndian)")
ELSEIF(FP_IEEE754_BE)
MESSAGE(STATUS "Checking the floating point format - IEEE754 (BigEndian)")
ELSE()
MESSAGE(STATUS "Checking the floating point format - Not IEEE754 or failed to detect.")
ENDIF()
SET(FP_IEEE754 "${${FP_IEEE754}}" CACHE INTERNAL "Result of TEST_FLOAT_FORMAT" FORCE)
ENDIF()
ENDMACRO(TEST_FLOAT_FORMAT FP_IEEE754)

View File

@ -1,22 +1,12 @@
/* config.h. Generated by cmake from config.h.cmake */
/* Integer byte order of your target system */
/* 1 if little-endian, 2 if big-endian. */
#cmakedefine SYSTEM_BYTEORDER ${SYSTEM_BYTEORDER}
/* IEEE754 byte order of your target system. */
/* 1 if little-endian, 2 if big-endian. */
#cmakedefine FLOAT_BYTEORDER ${FLOAT_BYTEORDER}
/* Defined if required for large files support */
#cmakedefine _LARGE_FILES ${_LARGE_FILES}
#cmakedefine _LARGEFILE_SOURCE ${_LARGEFILE_SOURCE}
#cmakedefine _FILE_OFFSET_BITS ${_FILE_OFFSET_BITS}
/* Defined if your compiler supports some byte swap functions */
#cmakedefine HAVE_GCC_BYTESWAP_16 1
#cmakedefine HAVE_GCC_BYTESWAP_32 1
#cmakedefine HAVE_GCC_BYTESWAP_64 1
#cmakedefine HAVE_GCC_BYTESWAP 1
#cmakedefine HAVE_GLIBC_BYTESWAP 1
#cmakedefine HAVE_MSC_BYTESWAP 1
#cmakedefine HAVE_MAC_BYTESWAP 1
@ -34,9 +24,9 @@
#cmakedefine HAVE_STD_SMART_PTR 1
#cmakedefine HAVE_BOOST_SMART_PTR 1
/* Defined if your compiler supports snprintf or sprintf_s. */
#cmakedefine HAVE_SNPRINTF 1
#cmakedefine HAVE_SPRINTF_S 1
/* Defined if your compiler supports some safer version of vsprintf */
#cmakedefine HAVE_VSNPRINTF 1
#cmakedefine HAVE_VSPRINTF_S 1
/* Defined if your compiler supports ISO _strdup. */
#cmakedefine HAVE_ISO_STRDUP 1

View File

@ -36,6 +36,7 @@
#include <tdebug.h>
#include <tagunion.h>
#include <id3v1tag.h>
#include <id3v2header.h>
#include <tpropertymap.h>
#include "apefile.h"
@ -57,12 +58,17 @@ public:
APELocation(-1),
APESize(0),
ID3v1Location(-1),
ID3v2Header(0),
ID3v2Location(-1),
ID3v2Size(0),
properties(0),
hasAPE(false),
hasID3v1(false) {}
hasID3v1(false),
hasID3v2(false) {}
~FilePrivate()
{
delete ID3v2Header;
delete properties;
}
@ -71,6 +77,10 @@ public:
offset_t ID3v1Location;
ID3v2::Header *ID3v2Header;
offset_t ID3v2Location;
uint ID3v2Size;
DoubleTagUnion tag;
AudioProperties *properties;
@ -80,26 +90,27 @@ public:
bool hasAPE;
bool hasID3v1;
bool hasID3v2;
};
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////
APE::File::File(FileName file, bool readProperties,
AudioProperties::ReadStyle propertiesStyle) : TagLib::File(file)
APE::File::File(FileName file, bool readProperties, AudioProperties::ReadStyle) :
TagLib::File(file),
d(new FilePrivate())
{
d = new FilePrivate;
if(isOpen())
read(readProperties, propertiesStyle);
read(readProperties);
}
APE::File::File(IOStream *stream, bool readProperties,
AudioProperties::ReadStyle propertiesStyle) : TagLib::File(stream)
APE::File::File(IOStream *stream, bool readProperties, AudioProperties::ReadStyle) :
TagLib::File(stream),
d(new FilePrivate())
{
d = new FilePrivate;
if(isOpen())
read(readProperties, propertiesStyle);
read(readProperties);
}
APE::File::~File()
@ -232,8 +243,19 @@ bool APE::File::hasID3v1Tag() const
// private members
////////////////////////////////////////////////////////////////////////////////
void APE::File::read(bool readProperties, AudioProperties::ReadStyle /* propertiesStyle */)
void APE::File::read(bool readProperties)
{
// Look for an ID3v2 tag
d->ID3v2Location = findID3v2();
if(d->ID3v2Location >= 0) {
seek(d->ID3v2Location);
d->ID3v2Header = new ID3v2::Header(readBlock(ID3v2::Header::size()));
d->ID3v2Size = d->ID3v2Header->completeTagSize();
d->hasID3v2 = true;
}
// Look for an ID3v1 tag
d->ID3v1Location = findID3v1();
@ -260,7 +282,25 @@ void APE::File::read(bool readProperties, AudioProperties::ReadStyle /* properti
// Look for APE audio properties
if(readProperties) {
d->properties = new AudioProperties(this);
offset_t streamLength;
if(d->hasAPE)
streamLength = d->APELocation;
else if(d->hasID3v1)
streamLength = d->ID3v1Location;
else
streamLength = length();
if(d->hasID3v2) {
seek(d->ID3v2Location + d->ID3v2Size);
streamLength -= (d->ID3v2Location + d->ID3v2Size);
}
else {
seek(0);
}
d->properties = new AudioProperties(this, streamLength);
}
}
@ -295,3 +335,16 @@ offset_t APE::File::findID3v1()
return -1;
}
offset_t APE::File::findID3v2()
{
if(!isValid())
return -1;
seek(0);
if(readBlock(3) == ID3v2::Header::fileIdentifier())
return 0;
return -1;
}

View File

@ -117,7 +117,7 @@ namespace TagLib {
/*!
* Implements the unified property interface -- import function.
* Creates an APEv2 tag if necessary. A pontentially existing ID3v1
* Creates an APEv2 tag if necessary. A potentially existing ID3v1
* tag will be updated as well.
*/
PropertyMap setProperties(const PropertyMap &);
@ -133,6 +133,9 @@ namespace TagLib {
*
* \note According to the official Monkey's Audio SDK, an APE file
* can only have either ID3V1 or APE tags, so a parameter is used here.
*
* \warning In the current implementation, it's dangerous to call save()
* repeatedly. At worst it will corrupt the file.
*/
virtual bool save();
@ -143,8 +146,8 @@ namespace TagLib {
* if there is no valid ID3v1 tag. If \a create is true it will create
* an ID3v1 tag if one does not exist and returns a valid pointer.
*
* \note This may return a valid pointer regardless of whether or not the
* file on disk has an ID3v1 tag. Use hasID3v1Tag() to check if the file
* \note This may return a valid pointer regardless of whether or not the
* file on disk has an ID3v1 tag. Use hasID3v1Tag() to check if the file
* on disk actually has an ID3v1 tag.
*
* \note The Tag <b>is still</b> owned by the MPEG::File and should not be
@ -162,8 +165,8 @@ namespace TagLib {
* if there is no valid APE tag. If \a create is true it will create
* an APE tag if one does not exist and returns a valid pointer.
*
* \note This may return a valid pointer regardless of whether or not the
* file on disk has an APE tag. Use hasAPETag() to check if the file
* \note This may return a valid pointer regardless of whether or not the
* file on disk has an APE tag. Use hasAPETag() to check if the file
* on disk actually has an APE tag.
*
* \note The Tag <b>is still</b> owned by the MPEG::File and should not be
@ -202,10 +205,10 @@ namespace TagLib {
File(const File &);
File &operator=(const File &);
void read(bool readProperties, AudioProperties::ReadStyle propertiesStyle);
void scan();
offset_t findID3v1();
void read(bool readProperties);
offset_t findAPE();
offset_t findID3v1();
offset_t findID3v2();
class FilePrivate;
FilePrivate *d;

View File

@ -37,7 +37,7 @@ namespace TagLib {
/*!
* This class implements APE footers (and headers). It attempts to follow, both
* semantically and programatically, the structure specified in
* semantically and programmatically, the structure specified in
* the APE v2.0 standard. The API is based on the properties of APE footer and
* headers specified there.
*/

View File

@ -147,13 +147,13 @@ namespace TagLib {
/*!
* Returns the value as a single string. In case of multiple strings,
* the first is returned. If the data type is not \a Text, always returns
* the first is returned. If the data type is not \a Text, always returns
* an empty String.
*/
String toString() const;
/*!
* Returns the list of text values. If the data type is not \a Text, always
* Returns the list of text values. If the data type is not \a Text, always
* returns an empty StringList.
*/
StringList values() const;

View File

@ -33,6 +33,8 @@
#include "id3v2tag.h"
#include "apeproperties.h"
#include "apefile.h"
#include "apetag.h"
#include "apefooter.h"
using namespace TagLib;
@ -61,10 +63,18 @@ public:
// public members
////////////////////////////////////////////////////////////////////////////////
APE::AudioProperties::AudioProperties(File *file, ReadStyle style) :
APE::AudioProperties::AudioProperties(File *, ReadStyle) :
TagLib::AudioProperties(),
d(new PropertiesPrivate())
{
read(file);
debug("APE::Properties::Properties() -- This constructor is no longer used.");
}
APE::AudioProperties::AudioProperties(File *file, offset_t streamLength, ReadStyle) :
TagLib::AudioProperties(),
d(new PropertiesPrivate())
{
read(file, streamLength);
}
APE::AudioProperties::~AudioProperties()
@ -73,6 +83,16 @@ APE::AudioProperties::~AudioProperties()
}
int APE::AudioProperties::length() const
{
return lengthInSeconds();
}
int APE::AudioProperties::lengthInSeconds() const
{
return d->length / 1000;
}
int APE::AudioProperties::lengthInMilliseconds() const
{
return d->length;
}
@ -111,99 +131,93 @@ TagLib::uint APE::AudioProperties::sampleFrames() const
// private members
////////////////////////////////////////////////////////////////////////////////
void APE::AudioProperties::read(File *file)
namespace
{
// First we are searching the descriptor
offset_t offset = findDescriptor(file);
if(offset < 0)
return;
inline int headerVersion(const ByteVector &header)
{
if(header.size() < 6 || !header.startsWith("MAC "))
return -1;
// Then we read the header common for all versions of APE
file->seek(offset);
ByteVector commonHeader = file->readBlock(6);
if(!commonHeader.startsWith("MAC "))
return;
d->version = commonHeader.toUInt16LE(4);
return header.toUInt16LE(4);
}
}
if(d->version >= 3980) {
void APE::AudioProperties::read(File *file, offset_t streamLength)
{
// First, we assume that the file pointer is set at the first descriptor.
offset_t offset = file->tell();
int version = headerVersion(file->readBlock(6));
// Next, we look for the descriptor.
if(version < 0) {
offset = file->find("MAC ", offset);
file->seek(offset);
version = headerVersion(file->readBlock(6));
}
if(version < 0) {
debug("APE::Properties::read() -- APE descriptor not found");
return;
}
d->version = version;
if(d->version >= 3980)
analyzeCurrent(file);
}
else {
analyzeOld(file);
}
}
offset_t APE::AudioProperties::findDescriptor(File *file)
{
offset_t ID3v2Location = findID3v2(file);
long ID3v2OriginalSize = 0;
bool hasID3v2 = false;
if(ID3v2Location >= 0) {
ID3v2::Tag tag(file, ID3v2Location);
ID3v2OriginalSize = tag.header()->completeTagSize();
if(tag.header()->tagSize() > 0)
hasID3v2 = true;
}
offset_t offset = 0;
if(hasID3v2)
offset = file->find("MAC ", ID3v2Location + ID3v2OriginalSize);
else
offset = file->find("MAC ");
analyzeOld(file);
if(offset < 0) {
debug("APE::Properties::findDescriptor() -- APE descriptor not found");
return -1;
if(d->sampleFrames > 0 && d->sampleRate > 0) {
const double length = d->sampleFrames * 1000.0 / d->sampleRate;
d->length = static_cast<int>(length + 0.5);
d->bitrate = static_cast<int>(streamLength * 8.0 / length + 0.5);
}
return offset;
}
offset_t APE::AudioProperties::findID3v2(File *file)
{
if(!file->isValid())
return -1;
file->seek(0);
if(file->readBlock(3) == ID3v2::Header::fileIdentifier())
return 0;
return -1;
}
void APE::AudioProperties::analyzeCurrent(File *file)
{
// Read the descriptor
file->seek(2, File::Current);
ByteVector descriptor = file->readBlock(44);
const ByteVector descriptor = file->readBlock(44);
if(descriptor.size() < 44) {
debug("APE::Properties::analyzeCurrent() -- descriptor is too short.");
return;
}
const uint descriptorBytes = descriptor.toUInt32LE(0);
if ((descriptorBytes - 52) > 0)
if((descriptorBytes - 52) > 0)
file->seek(descriptorBytes - 52, File::Current);
// Read the header
ByteVector header = file->readBlock(24);
const ByteVector header = file->readBlock(24);
if(header.size() < 24) {
debug("APE::Properties::analyzeCurrent() -- MAC header is too short.");
return;
}
// Get the APE info
d->channels = header.toInt16LE(18);
d->sampleRate = header.toUInt32LE(20);
d->bitsPerSample = header.toInt16LE(16);
//d->compressionLevel =
d->channels = header.toUInt16LE(18);
d->sampleRate = header.toUInt32LE(20);
d->bitsPerSample = header.toUInt16LE(16);
const uint totalFrames = header.toUInt32LE(12);
const uint blocksPerFrame = header.toUInt32LE(4);
const uint finalFrameBlocks = header.toUInt32LE(8);
if(totalFrames == 0)
return;
d->sampleFrames = totalFrames > 0 ? (totalFrames - 1) * blocksPerFrame + finalFrameBlocks : 0;
d->length = d->sampleRate > 0 ? d->sampleFrames / d->sampleRate : 0;
d->bitrate = d->length > 0 ? static_cast<int>(file->length() * 8L / d->length / 1000) : 0;
const uint blocksPerFrame = header.toUInt32LE(4);
const uint finalFrameBlocks = header.toUInt32LE(8);
d->sampleFrames = (totalFrames - 1) * blocksPerFrame + finalFrameBlocks;
}
void APE::AudioProperties::analyzeOld(File *file)
{
ByteVector header = file->readBlock(26);
const ByteVector header = file->readBlock(26);
if(header.size() < 26) {
debug("APE::Properties::analyzeOld() -- MAC header is too short.");
return;
}
const uint totalFrames = header.toUInt32LE(18);
// Fail on 0 length APE files (catches non-finalized APE files)
@ -219,19 +233,20 @@ void APE::AudioProperties::analyzeOld(File *file)
else
blocksPerFrame = 9216;
// Get the APE info
d->channels = header.toUInt16LE(4);
d->sampleRate = header.toUInt32LE(6);
const uint finalFrameBlocks = header.toUInt32LE(22);
d->sampleFrames = (totalFrames - 1) * blocksPerFrame + finalFrameBlocks;
uint totalBlocks = 0;
if(totalFrames > 0)
totalBlocks = (totalFrames - 1) * blocksPerFrame + finalFrameBlocks;
// Get the bit depth from the RIFF-fmt chunk.
file->seek(16, File::Current);
const ByteVector fmt = file->readBlock(28);
if(fmt.size() < 28 || !fmt.startsWith("WAVEfmt ")) {
debug("APE::Properties::analyzeOld() -- fmt header is too short.");
return;
}
if(d->sampleRate > 0)
d->length = totalBlocks / d->sampleRate;
if(d->length > 0)
d->bitrate = ((file->length() * 8L) / d->length) / 1000;
d->bitsPerSample = fmt.toUInt16LE(26);
}

View File

@ -50,27 +50,74 @@ namespace TagLib {
{
public:
/*!
* Creates an instance of APE::AudioProperties with the data read from
* the ByteVector \a data.
* Create an instance of APE::Properties with the data read from the
* APE::File \a file.
*
* \deprecated
*/
AudioProperties(File *file, ReadStyle style = Average);
/*!
* Create an instance of APE::Properties with the data read from the
* APE::File \a file.
*/
AudioProperties(File *file, offset_t streamLength, ReadStyle style = Average);
/*!
* Destroys this APE::AudioProperties instance.
*/
virtual ~AudioProperties();
// Reimplementations.
/*!
* Returns the length of the file in seconds. The length is rounded down to
* the nearest whole second.
*
* \note This method is just an alias of lengthInSeconds().
*
* \deprecated
*/
virtual int length() const;
/*!
* Returns the length of the file in seconds. The length is rounded down to
* the nearest whole second.
*
* \see lengthInMilliseconds()
*/
// BIC: make virtual
int lengthInSeconds() const;
/*!
* Returns the length of the file in milliseconds.
*
* \see lengthInSeconds()
*/
// BIC: make virtual
int lengthInMilliseconds() const;
/*!
* Returns the average bit rate of the file in kb/s.
*/
virtual int bitrate() const;
/*!
* Returns the sample rate in Hz.
*/
virtual int sampleRate() const;
/*!
* Returns the number of audio channels.
*/
virtual int channels() const;
/*!
* Returns number of bits per sample.
* Returns the number of bits per audio sample.
*/
int bitsPerSample() const;
/*!
* Returns the total number of audio samples in file.
*/
uint sampleFrames() const;
/*!
@ -79,11 +126,7 @@ namespace TagLib {
int version() const;
private:
void read(File *file);
offset_t findDescriptor(File *file);
offset_t findID3v2(File *file);
void read(File *file, offset_t streamLength);
void analyzeCurrent(File *file);
void analyzeOld(File *file);

View File

@ -46,14 +46,7 @@ using namespace APE;
class APE::Tag::TagPrivate
{
public:
TagPrivate() : file(0), footerLocation(-1), tagLength(0) {}
TagLib::File *file;
offset_t footerLocation;
long tagLength;
Footer footer;
ItemListMap itemListMap;
};
@ -61,18 +54,17 @@ public:
// public methods
////////////////////////////////////////////////////////////////////////////////
APE::Tag::Tag() : TagLib::Tag()
APE::Tag::Tag() :
TagLib::Tag(),
d(new TagPrivate())
{
d = new TagPrivate;
}
APE::Tag::Tag(TagLib::File *file, offset_t footerLocation) : TagLib::Tag()
APE::Tag::Tag(TagLib::File *file, offset_t footerLocation) :
TagLib::Tag(),
d(new TagPrivate())
{
d = new TagPrivate;
d->file = file;
d->footerLocation = footerLocation;
read();
read(file, footerLocation);
}
APE::Tag::~Tag()
@ -287,9 +279,7 @@ const APE::ItemListMap& APE::Tag::itemListMap() const
void APE::Tag::removeItem(const String &key)
{
Map<const String, Item>::Iterator it = d->itemListMap.find(key.upper());
if(it != d->itemListMap.end())
d->itemListMap.erase(it);
d->itemListMap.erase(key.upper());
}
void APE::Tag::addValue(const String &key, const String &value, bool replace)
@ -332,19 +322,19 @@ bool APE::Tag::isEmpty() const
// protected methods
////////////////////////////////////////////////////////////////////////////////
void APE::Tag::read()
void APE::Tag::read(TagLib::File *file, offset_t footerLocation)
{
if(d->file && d->file->isValid()) {
if(file && file->isValid()) {
d->file->seek(d->footerLocation);
d->footer.setData(d->file->readBlock(Footer::size()));
file->seek(footerLocation);
d->footer.setData(file->readBlock(Footer::size()));
if(d->footer.tagSize() <= Footer::size() ||
d->footer.tagSize() > uint(d->file->length()))
d->footer.tagSize() > uint(file->length()))
return;
d->file->seek(d->footerLocation + Footer::size() - d->footer.tagSize());
parse(d->file->readBlock(d->footer.tagSize() - Footer::size()));
file->seek(footerLocation + Footer::size() - d->footer.tagSize());
parse(file->readBlock(d->footer.tagSize() - Footer::size()));
}
}

View File

@ -143,7 +143,7 @@ namespace TagLib {
* Returns a reference to the item list map. This is an ItemListMap of
* all of the items in the tag.
*
* This is the most powerfull structure for accessing the items of the tag.
* This is the most powerful structure for accessing the items of the tag.
*
* APE tags are case-insensitive, all keys in this map have been converted
* to upper case.
@ -188,7 +188,7 @@ namespace TagLib {
/*!
* Reads from the file specified in the constructor.
*/
void read();
void read(TagLib::File *file, offset_t footerLocation);
/*!
* Parses the body of the tag in \a data.

View File

@ -23,11 +23,13 @@
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#include "taglib.h"
#include "tdebug.h"
#include "tsmartptr.h"
#include <taglib.h>
#include <tdebug.h>
#include <tsmartptr.h>
#include "asfattribute.h"
#include "asffile.h"
#include "asfutils.h"
using namespace TagLib;
@ -53,8 +55,8 @@ namespace
class ASF::Attribute::AttributePrivate
{
public:
AttributePrivate()
: data(new AttributeData())
AttributePrivate() :
data(new AttributeData())
{
data->pictureValue = ASF::Picture::fromInvalid();
data->stream = 0;
@ -153,7 +155,7 @@ ByteVector ASF::Attribute::toByteVector() const
{
if(d->data->pictureValue.isValid())
return d->data->pictureValue.render();
return d->data->byteVectorValue;
}
@ -189,23 +191,23 @@ String ASF::Attribute::parse(ASF::File &f, int kind)
d->data->pictureValue = Picture::fromInvalid();
// extended content descriptor
if(kind == 0) {
nameLength = f.readWORD();
name = f.readString(nameLength);
d->data->type = ASF::Attribute::AttributeTypes(f.readWORD());
size = f.readWORD();
nameLength = readWORD(&f);
name = readString(&f, nameLength);
d->data->type = ASF::Attribute::AttributeTypes(readWORD(&f));
size = readWORD(&f);
}
// metadata & metadata library
else {
int temp = f.readWORD();
int temp = readWORD(&f);
// metadata library
if(kind == 2) {
d->data->language = temp;
}
d->data->stream = f.readWORD();
nameLength = f.readWORD();
d->data->type = ASF::Attribute::AttributeTypes(f.readWORD());
size = f.readDWORD();
name = f.readString(nameLength);
d->data->stream = readWORD(&f);
nameLength = readWORD(&f);
d->data->type = ASF::Attribute::AttributeTypes(readWORD(&f));
size = readDWORD(&f);
name = readString(&f, nameLength);
}
if(kind != 2 && size > 65535) {
@ -214,28 +216,28 @@ String ASF::Attribute::parse(ASF::File &f, int kind)
switch(d->data->type) {
case WordType:
d->data->shortValue = f.readWORD();
d->data->shortValue = readWORD(&f);
break;
case BoolType:
if(kind == 0) {
d->data->boolValue = f.readDWORD() == 1;
d->data->boolValue = (readDWORD(&f) == 1);
}
else {
d->data->boolValue = f.readWORD() == 1;
d->data->boolValue = (readWORD(&f) == 1);
}
break;
case DWordType:
d->data->intValue = f.readDWORD();
d->data->intValue = readDWORD(&f);
break;
case QWordType:
d->data->longLongValue = f.readQWORD();
d->data->longLongValue = readQWORD(&f);
break;
case UnicodeType:
d->data->stringValue = f.readString(size);
d->data->stringValue = readString(&f, size);
break;
case BytesType:
@ -303,7 +305,7 @@ ByteVector ASF::Attribute::render(const String &name, int kind) const
break;
case UnicodeType:
data.append(File::renderString(d->data->stringValue));
data.append(renderString(d->data->stringValue));
break;
case BytesType:
@ -317,13 +319,13 @@ ByteVector ASF::Attribute::render(const String &name, int kind) const
}
if(kind == 0) {
data = File::renderString(name, true) +
data = renderString(name, true) +
ByteVector::fromUInt16LE((int)d->data->type) +
ByteVector::fromUInt16LE(data.size()) +
data;
}
else {
ByteVector nameData = File::renderString(name);
ByteVector nameData = renderString(name);
data = ByteVector::fromUInt16LE(kind == 2 ? d->data->language : 0) +
ByteVector::fromUInt16LE(d->data->stream) +
ByteVector::fromUInt16LE(nameData.size()) +

View File

@ -23,19 +23,35 @@
* http://www.mozilla.org/MPL/ *
***************************************************************************/
// The implementation of this class is based on the document found at:
// http://download.microsoft.com/download/8/0/5/80506BEB-C95A-47AE-99CF-0D6D6D028ABA/ASF_Specification.pdf
#include <tdebug.h>
#include <tbytevectorlist.h>
#include <tpropertymap.h>
#include <tstring.h>
#include "asffile.h"
#include "asftag.h"
#include "asfproperties.h"
#include "asfutils.h"
using namespace TagLib;
class ASF::File::FilePrivate
{
public:
class BaseObject;
class UnknownObject;
class FilePropertiesObject;
class StreamPropertiesObject;
class ContentDescriptionObject;
class ExtendedContentDescriptionObject;
class HeaderExtensionObject;
class CodecListObject;
class MetadataObject;
class MetadataLibraryObject;
FilePrivate():
size(0),
tag(0),
@ -44,16 +60,29 @@ public:
extendedContentDescriptionObject(0),
headerExtensionObject(0),
metadataObject(0),
metadataLibraryObject(0) {}
metadataLibraryObject(0)
{
objects.setAutoDelete(true);
}
~FilePrivate()
{
delete tag;
delete properties;
}
unsigned long long size;
ASF::Tag *tag;
ASF::AudioProperties *properties;
List<ASF::File::BaseObject *> objects;
ASF::File::ContentDescriptionObject *contentDescriptionObject;
ASF::File::ExtendedContentDescriptionObject *extendedContentDescriptionObject;
ASF::File::HeaderExtensionObject *headerExtensionObject;
ASF::File::MetadataObject *metadataObject;
ASF::File::MetadataLibraryObject *metadataLibraryObject;
List<BaseObject *> objects;
ContentDescriptionObject *contentDescriptionObject;
ExtendedContentDescriptionObject *extendedContentDescriptionObject;
HeaderExtensionObject *headerExtensionObject;
MetadataObject *metadataObject;
MetadataLibraryObject *metadataLibraryObject;
};
namespace
@ -66,169 +95,192 @@ namespace
const ByteVector headerExtensionGuid("\xb5\x03\xbf_.\xa9\xcf\x11\x8e\xe3\x00\xc0\x0c Se", 16);
const ByteVector metadataGuid("\xEA\xCB\xF8\xC5\xAF[wH\204g\xAA\214D\xFAL\xCA", 16);
const ByteVector metadataLibraryGuid("\224\034#D\230\224\321I\241A\x1d\x13NEpT", 16);
const ByteVector codecListGuid("\x40\x52\xd1\x86\x1d\x31\xd0\x11\xa3\xa4\x00\xa0\xc9\x03\x48\xf6", 16);
const ByteVector contentEncryptionGuid("\xFB\xB3\x11\x22\x23\xBD\xD2\x11\xB4\xB7\x00\xA0\xC9\x55\xFC\x6E", 16);
const ByteVector extendedContentEncryptionGuid("\x14\xE6\x8A\x29\x22\x26 \x17\x4C\xB9\x35\xDA\xE0\x7E\xE9\x28\x9C", 16);
const ByteVector advancedContentEncryptionGuid("\xB6\x9B\x07\x7A\xA4\xDA\x12\x4E\xA5\xCA\x91\xD3\x8D\xC1\x1A\x8D", 16);
}
class ASF::File::BaseObject
class ASF::File::FilePrivate::BaseObject
{
public:
ByteVector data;
virtual ~BaseObject() {}
virtual ByteVector guid() = 0;
virtual ByteVector guid() const = 0;
virtual void parse(ASF::File *file, unsigned int size);
virtual ByteVector render(ASF::File *file);
};
class ASF::File::UnknownObject : public ASF::File::BaseObject
class ASF::File::FilePrivate::UnknownObject : public ASF::File::FilePrivate::BaseObject
{
ByteVector myGuid;
public:
UnknownObject(const ByteVector &guid);
ByteVector guid();
ByteVector guid() const;
};
class ASF::File::FilePropertiesObject : public ASF::File::BaseObject
class ASF::File::FilePrivate::FilePropertiesObject : public ASF::File::FilePrivate::BaseObject
{
public:
ByteVector guid();
ByteVector guid() const;
void parse(ASF::File *file, uint size);
};
class ASF::File::StreamPropertiesObject : public ASF::File::BaseObject
class ASF::File::FilePrivate::StreamPropertiesObject : public ASF::File::FilePrivate::BaseObject
{
public:
ByteVector guid();
ByteVector guid() const;
void parse(ASF::File *file, uint size);
};
class ASF::File::ContentDescriptionObject : public ASF::File::BaseObject
class ASF::File::FilePrivate::ContentDescriptionObject : public ASF::File::FilePrivate::BaseObject
{
public:
ByteVector guid();
ByteVector guid() const;
void parse(ASF::File *file, uint size);
ByteVector render(ASF::File *file);
};
class ASF::File::ExtendedContentDescriptionObject : public ASF::File::BaseObject
class ASF::File::FilePrivate::ExtendedContentDescriptionObject : public ASF::File::FilePrivate::BaseObject
{
public:
ByteVectorList attributeData;
ByteVector guid();
ByteVector guid() const;
void parse(ASF::File *file, uint size);
ByteVector render(ASF::File *file);
};
class ASF::File::MetadataObject : public ASF::File::BaseObject
class ASF::File::FilePrivate::MetadataObject : public ASF::File::FilePrivate::BaseObject
{
public:
ByteVectorList attributeData;
ByteVector guid();
ByteVector guid() const;
void parse(ASF::File *file, uint size);
ByteVector render(ASF::File *file);
};
class ASF::File::MetadataLibraryObject : public ASF::File::BaseObject
class ASF::File::FilePrivate::MetadataLibraryObject : public ASF::File::FilePrivate::BaseObject
{
public:
ByteVectorList attributeData;
ByteVector guid();
ByteVector guid() const;
void parse(ASF::File *file, uint size);
ByteVector render(ASF::File *file);
};
class ASF::File::HeaderExtensionObject : public ASF::File::BaseObject
class ASF::File::FilePrivate::HeaderExtensionObject : public ASF::File::FilePrivate::BaseObject
{
public:
List<ASF::File::BaseObject *> objects;
~HeaderExtensionObject();
ByteVector guid();
List<ASF::File::FilePrivate::BaseObject *> objects;
HeaderExtensionObject();
ByteVector guid() const;
void parse(ASF::File *file, uint size);
ByteVector render(ASF::File *file);
};
ASF::File::HeaderExtensionObject::~HeaderExtensionObject()
class ASF::File::FilePrivate::CodecListObject : public ASF::File::FilePrivate::BaseObject
{
for(unsigned int i = 0; i < objects.size(); i++) {
delete objects[i];
}
}
public:
ByteVector guid() const;
void parse(ASF::File *file, uint size);
void ASF::File::BaseObject::parse(ASF::File *file, unsigned int size)
private:
enum CodecType
{
Video = 0x0001,
Audio = 0x0002,
Unknown = 0xFFFF
};
};
void ASF::File::FilePrivate::BaseObject::parse(ASF::File *file, unsigned int size)
{
data.clear();
if (size > 24 && static_cast<offset_t>(size) <= file->length())
if(size > 24 && static_cast<offset_t>(size) <= file->length())
data = file->readBlock(size - 24);
else
data = ByteVector::null;
}
ByteVector ASF::File::BaseObject::render(ASF::File * /*file*/)
ByteVector ASF::File::FilePrivate::BaseObject::render(ASF::File * /*file*/)
{
return guid() + ByteVector::fromUInt64LE(data.size() + 24) + data;
}
ASF::File::UnknownObject::UnknownObject(const ByteVector &guid) : myGuid(guid)
ASF::File::FilePrivate::UnknownObject::UnknownObject(const ByteVector &guid) : myGuid(guid)
{
}
ByteVector ASF::File::UnknownObject::guid()
ByteVector ASF::File::FilePrivate::UnknownObject::guid() const
{
return myGuid;
}
ByteVector ASF::File::FilePropertiesObject::guid()
ByteVector ASF::File::FilePrivate::FilePropertiesObject::guid() const
{
return filePropertiesGuid;
}
void ASF::File::FilePropertiesObject::parse(ASF::File *file, uint size)
void ASF::File::FilePrivate::FilePropertiesObject::parse(ASF::File *file, uint size)
{
BaseObject::parse(file, size);
file->d->properties->setLength((int)(data.toInt64LE(40) / 10000000L - data.toInt64LE(56) / 1000L));
if(data.size() < 64) {
debug("ASF::File::FilePrivate::FilePropertiesObject::parse() -- data is too short.");
return;
}
const long long duration = data.toInt64LE(40);
const long long preroll = data.toInt64LE(56);
file->d->properties->setLengthInMilliseconds(static_cast<int>(duration / 10000.0 - preroll + 0.5));
}
ByteVector ASF::File::StreamPropertiesObject::guid()
ByteVector ASF::File::FilePrivate::StreamPropertiesObject::guid() const
{
return streamPropertiesGuid;
}
void ASF::File::StreamPropertiesObject::parse(ASF::File *file, uint size)
void ASF::File::FilePrivate::StreamPropertiesObject::parse(ASF::File *file, uint size)
{
BaseObject::parse(file, size);
file->d->properties->setChannels(data.toInt16LE(56));
if(data.size() < 70) {
debug("ASF::File::FilePrivate::StreamPropertiesObject::parse() -- data is too short.");
return;
}
file->d->properties->setCodec(data.toUInt16LE(54));
file->d->properties->setChannels(data.toUInt16LE(56));
file->d->properties->setSampleRate(data.toUInt32LE(58));
file->d->properties->setBitrate(data.toInt16LE(62) * 8 / 1000);
file->d->properties->setBitrate(static_cast<int>(data.toUInt32LE(62) * 8.0 / 1000.0 + 0.5));
file->d->properties->setBitsPerSample(data.toUInt16LE(68));
}
ByteVector ASF::File::ContentDescriptionObject::guid()
ByteVector ASF::File::FilePrivate::ContentDescriptionObject::guid() const
{
return contentDescriptionGuid;
}
void ASF::File::ContentDescriptionObject::parse(ASF::File *file, uint /*size*/)
void ASF::File::FilePrivate::ContentDescriptionObject::parse(ASF::File *file, uint /*size*/)
{
file->d->contentDescriptionObject = this;
int titleLength = file->readWORD();
int artistLength = file->readWORD();
int copyrightLength = file->readWORD();
int commentLength = file->readWORD();
int ratingLength = file->readWORD();
file->d->tag->setTitle(file->readString(titleLength));
file->d->tag->setArtist(file->readString(artistLength));
file->d->tag->setCopyright(file->readString(copyrightLength));
file->d->tag->setComment(file->readString(commentLength));
file->d->tag->setRating(file->readString(ratingLength));
const int titleLength = readWORD(file);
const int artistLength = readWORD(file);
const int copyrightLength = readWORD(file);
const int commentLength = readWORD(file);
const int ratingLength = readWORD(file);
file->d->tag->setTitle(readString(file,titleLength));
file->d->tag->setArtist(readString(file,artistLength));
file->d->tag->setCopyright(readString(file,copyrightLength));
file->d->tag->setComment(readString(file,commentLength));
file->d->tag->setRating(readString(file,ratingLength));
}
ByteVector ASF::File::ContentDescriptionObject::render(ASF::File *file)
ByteVector ASF::File::FilePrivate::ContentDescriptionObject::render(ASF::File *file)
{
ByteVector v1 = file->renderString(file->d->tag->title());
ByteVector v2 = file->renderString(file->d->tag->artist());
ByteVector v3 = file->renderString(file->d->tag->copyright());
ByteVector v4 = file->renderString(file->d->tag->comment());
ByteVector v5 = file->renderString(file->d->tag->rating());
const ByteVector v1 = renderString(file->d->tag->title());
const ByteVector v2 = renderString(file->d->tag->artist());
const ByteVector v3 = renderString(file->d->tag->copyright());
const ByteVector v4 = renderString(file->d->tag->comment());
const ByteVector v5 = renderString(file->d->tag->rating());
data.clear();
data.append(ByteVector::fromUInt16LE(v1.size()));
data.append(ByteVector::fromUInt16LE(v2.size()));
@ -243,15 +295,15 @@ ByteVector ASF::File::ContentDescriptionObject::render(ASF::File *file)
return BaseObject::render(file);
}
ByteVector ASF::File::ExtendedContentDescriptionObject::guid()
ByteVector ASF::File::FilePrivate::ExtendedContentDescriptionObject::guid() const
{
return extendedContentDescriptionGuid;
}
void ASF::File::ExtendedContentDescriptionObject::parse(ASF::File *file, uint /*size*/)
void ASF::File::FilePrivate::ExtendedContentDescriptionObject::parse(ASF::File *file, uint /*size*/)
{
file->d->extendedContentDescriptionObject = this;
int count = file->readWORD();
int count = readWORD(file);
while(count--) {
ASF::Attribute attribute;
String name = attribute.parse(*file);
@ -259,7 +311,7 @@ void ASF::File::ExtendedContentDescriptionObject::parse(ASF::File *file, uint /*
}
}
ByteVector ASF::File::ExtendedContentDescriptionObject::render(ASF::File *file)
ByteVector ASF::File::FilePrivate::ExtendedContentDescriptionObject::render(ASF::File *file)
{
data.clear();
data.append(ByteVector::fromUInt16LE(attributeData.size()));
@ -267,15 +319,15 @@ ByteVector ASF::File::ExtendedContentDescriptionObject::render(ASF::File *file)
return BaseObject::render(file);
}
ByteVector ASF::File::MetadataObject::guid()
ByteVector ASF::File::FilePrivate::MetadataObject::guid() const
{
return metadataGuid;
}
void ASF::File::MetadataObject::parse(ASF::File *file, uint /*size*/)
void ASF::File::FilePrivate::MetadataObject::parse(ASF::File *file, uint /*size*/)
{
file->d->metadataObject = this;
int count = file->readWORD();
int count = readWORD(file);
while(count--) {
ASF::Attribute attribute;
String name = attribute.parse(*file, 1);
@ -283,7 +335,7 @@ void ASF::File::MetadataObject::parse(ASF::File *file, uint /*size*/)
}
}
ByteVector ASF::File::MetadataObject::render(ASF::File *file)
ByteVector ASF::File::FilePrivate::MetadataObject::render(ASF::File *file)
{
data.clear();
data.append(ByteVector::fromUInt16LE(attributeData.size()));
@ -291,15 +343,15 @@ ByteVector ASF::File::MetadataObject::render(ASF::File *file)
return BaseObject::render(file);
}
ByteVector ASF::File::MetadataLibraryObject::guid()
ByteVector ASF::File::FilePrivate::MetadataLibraryObject::guid() const
{
return metadataLibraryGuid;
}
void ASF::File::MetadataLibraryObject::parse(ASF::File *file, uint /*size*/)
void ASF::File::FilePrivate::MetadataLibraryObject::parse(ASF::File *file, uint /*size*/)
{
file->d->metadataLibraryObject = this;
int count = file->readWORD();
int count = readWORD(file);
while(count--) {
ASF::Attribute attribute;
String name = attribute.parse(*file, 2);
@ -307,7 +359,7 @@ void ASF::File::MetadataLibraryObject::parse(ASF::File *file, uint /*size*/)
}
}
ByteVector ASF::File::MetadataLibraryObject::render(ASF::File *file)
ByteVector ASF::File::FilePrivate::MetadataLibraryObject::render(ASF::File *file)
{
data.clear();
data.append(ByteVector::fromUInt16LE(attributeData.size()));
@ -315,16 +367,21 @@ ByteVector ASF::File::MetadataLibraryObject::render(ASF::File *file)
return BaseObject::render(file);
}
ByteVector ASF::File::HeaderExtensionObject::guid()
ASF::File::FilePrivate::HeaderExtensionObject::HeaderExtensionObject()
{
objects.setAutoDelete(true);
}
ByteVector ASF::File::FilePrivate::HeaderExtensionObject::guid() const
{
return headerExtensionGuid;
}
void ASF::File::HeaderExtensionObject::parse(ASF::File *file, uint /*size*/)
void ASF::File::FilePrivate::HeaderExtensionObject::parse(ASF::File *file, uint /*size*/)
{
file->d->headerExtensionObject = this;
file->seek(18, File::Current);
long long dataSize = file->readDWORD();
long long dataSize = readDWORD(file);
long long dataPos = 0;
while(dataPos < dataSize) {
ByteVector guid = file->readBlock(16);
@ -333,7 +390,7 @@ void ASF::File::HeaderExtensionObject::parse(ASF::File *file, uint /*size*/)
break;
}
bool ok;
long long size = file->readQWORD(&ok);
long long size = readQWORD(file, &ok);
if(!ok) {
file->setValid(false);
break;
@ -354,47 +411,93 @@ void ASF::File::HeaderExtensionObject::parse(ASF::File *file, uint /*size*/)
}
}
ByteVector ASF::File::HeaderExtensionObject::render(ASF::File *file)
ByteVector ASF::File::FilePrivate::HeaderExtensionObject::render(ASF::File *file)
{
data.clear();
for(unsigned int i = 0; i < objects.size(); i++) {
data.append(objects[i]->render(file));
for(List<BaseObject *>::ConstIterator it = objects.begin(); it != objects.end(); ++it) {
data.append((*it)->render(file));
}
data = ByteVector("\x11\xD2\xD3\xAB\xBA\xA9\xcf\x11\x8E\xE6\x00\xC0\x0C\x20\x53\x65\x06\x00", 18) + ByteVector::fromUInt32LE(data.size()) + data;
return BaseObject::render(file);
}
ByteVector ASF::File::FilePrivate::CodecListObject::guid() const
{
return codecListGuid;
}
void ASF::File::FilePrivate::CodecListObject::parse(ASF::File *file, uint size)
{
BaseObject::parse(file, size);
if(data.size() <= 20) {
debug("ASF::File::FilePrivate::CodecListObject::parse() -- data is too short.");
return;
}
uint pos = 16;
const int count = data.toUInt32LE(pos);
pos += 4;
for(int i = 0; i < count; ++i) {
if(pos >= data.size())
break;
const CodecType type = static_cast<CodecType>(data.toUInt16LE(pos));
pos += 2;
int nameLength = data.toUInt16LE(pos);
pos += 2;
const uint namePos = pos;
pos += nameLength * 2;
const int descLength = data.toUInt16LE(pos);
pos += 2;
const uint descPos = pos;
pos += descLength * 2;
const int infoLength = data.toUInt16LE(pos);
pos += 2 + infoLength * 2;
if(type == CodecListObject::Audio) {
// First audio codec found.
const String name(data.mid(namePos, nameLength * 2), String::UTF16LE);
file->d->properties->setCodecName(name.stripWhiteSpace());
const String desc(data.mid(descPos, descLength * 2), String::UTF16LE);
file->d->properties->setCodecDescription(desc.stripWhiteSpace());
break;
}
}
}
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////
ASF::File::File(FileName file, bool readProperties, AudioProperties::ReadStyle propertiesStyle)
: TagLib::File(file)
ASF::File::File(FileName file, bool, AudioProperties::ReadStyle) :
TagLib::File(file),
d(new FilePrivate())
{
d = new FilePrivate;
if(isOpen())
read(readProperties, propertiesStyle);
read();
}
ASF::File::File(IOStream *stream, bool readProperties, AudioProperties::ReadStyle propertiesStyle)
: TagLib::File(stream)
ASF::File::File(IOStream *stream, bool, AudioProperties::ReadStyle) :
TagLib::File(stream),
d(new FilePrivate())
{
d = new FilePrivate;
if(isOpen())
read(readProperties, propertiesStyle);
read();
}
ASF::File::~File()
{
for(unsigned int i = 0; i < d->objects.size(); i++) {
delete d->objects[i];
}
if(d->tag) {
delete d->tag;
}
if(d->properties) {
delete d->properties;
}
delete d;
}
@ -423,7 +526,84 @@ ASF::AudioProperties *ASF::File::audioProperties() const
return d->properties;
}
void ASF::File::read(bool /*readProperties*/, AudioProperties::ReadStyle /*propertiesStyle*/)
bool ASF::File::save()
{
if(readOnly()) {
debug("ASF::File::save() -- File is read only.");
return false;
}
if(!isValid()) {
debug("ASF::File::save() -- Trying to save invalid file.");
return false;
}
if(!d->contentDescriptionObject) {
d->contentDescriptionObject = new FilePrivate::ContentDescriptionObject();
d->objects.append(d->contentDescriptionObject);
}
if(!d->extendedContentDescriptionObject) {
d->extendedContentDescriptionObject = new FilePrivate::ExtendedContentDescriptionObject();
d->objects.append(d->extendedContentDescriptionObject);
}
if(!d->headerExtensionObject) {
d->headerExtensionObject = new FilePrivate::HeaderExtensionObject();
d->objects.append(d->headerExtensionObject);
}
if(!d->metadataObject) {
d->metadataObject = new FilePrivate::MetadataObject();
d->headerExtensionObject->objects.append(d->metadataObject);
}
if(!d->metadataLibraryObject) {
d->metadataLibraryObject = new FilePrivate::MetadataLibraryObject();
d->headerExtensionObject->objects.append(d->metadataLibraryObject);
}
const AttributeListMap allAttributes = d->tag->attributeListMap();
for(AttributeListMap::ConstIterator it = allAttributes.begin(); it != allAttributes.end(); ++it) {
const String &name = it->first;
const AttributeList &attributes = it->second;
bool inExtendedContentDescriptionObject = false;
bool inMetadataObject = false;
for(AttributeList::ConstIterator jt = attributes.begin(); jt != attributes.end(); ++jt) {
const Attribute &attribute = *jt;
const bool largeValue = (attribute.dataSize() > 65535);
const bool guid = (attribute.type() == Attribute::GuidType);
if(!inExtendedContentDescriptionObject && !guid && !largeValue && attribute.language() == 0 && attribute.stream() == 0) {
d->extendedContentDescriptionObject->attributeData.append(attribute.render(name));
inExtendedContentDescriptionObject = true;
}
else if(!inMetadataObject && !guid && !largeValue && attribute.language() == 0 && attribute.stream() != 0) {
d->metadataObject->attributeData.append(attribute.render(name, 1));
inMetadataObject = true;
}
else {
d->metadataLibraryObject->attributeData.append(attribute.render(name, 2));
}
}
}
ByteVector data;
for(List<FilePrivate::BaseObject *>::ConstIterator it = d->objects.begin(); it != d->objects.end(); ++it) {
data.append((*it)->render(this));
}
data = headerGuid + ByteVector::fromUInt64LE(data.size() + 30) + ByteVector::fromUInt32LE(d->objects.size()) + ByteVector("\x01\x02", 2) + data;
insert(data, 0, static_cast<size_t>(d->size));
return true;
}
////////////////////////////////////////////////////////////////////////////////
// private members
////////////////////////////////////////////////////////////////////////////////
void ASF::File::read()
{
if(!isValid())
return;
@ -439,12 +619,12 @@ void ASF::File::read(bool /*readProperties*/, AudioProperties::ReadStyle /*prope
d->properties = new ASF::AudioProperties();
bool ok;
d->size = readQWORD(&ok);
d->size = readQWORD(this, &ok);
if(!ok) {
setValid(false);
return;
}
int numObjects = readDWORD(&ok);
int numObjects = readDWORD(this, &ok);
if(!ok) {
setValid(false);
return;
@ -452,31 +632,34 @@ void ASF::File::read(bool /*readProperties*/, AudioProperties::ReadStyle /*prope
seek(2, Current);
for(int i = 0; i < numObjects; i++) {
ByteVector guid = readBlock(16);
guid = readBlock(16);
if(guid.size() != 16) {
setValid(false);
break;
}
long size = (long)readQWORD(&ok);
long size = (long)readQWORD(this, &ok);
if(!ok) {
setValid(false);
break;
}
BaseObject *obj;
FilePrivate::BaseObject *obj;
if(guid == filePropertiesGuid) {
obj = new FilePropertiesObject();
obj = new FilePrivate::FilePropertiesObject();
}
else if(guid == streamPropertiesGuid) {
obj = new StreamPropertiesObject();
obj = new FilePrivate::StreamPropertiesObject();
}
else if(guid == contentDescriptionGuid) {
obj = new ContentDescriptionObject();
obj = new FilePrivate::ContentDescriptionObject();
}
else if(guid == extendedContentDescriptionGuid) {
obj = new ExtendedContentDescriptionObject();
obj = new FilePrivate::ExtendedContentDescriptionObject();
}
else if(guid == headerExtensionGuid) {
obj = new HeaderExtensionObject();
obj = new FilePrivate::HeaderExtensionObject();
}
else if(guid == codecListGuid) {
obj = new FilePrivate::CodecListObject();
}
else {
if(guid == contentEncryptionGuid ||
@ -484,149 +667,9 @@ void ASF::File::read(bool /*readProperties*/, AudioProperties::ReadStyle /*prope
guid == advancedContentEncryptionGuid) {
d->properties->setEncrypted(true);
}
obj = new UnknownObject(guid);
obj = new FilePrivate::UnknownObject(guid);
}
obj->parse(this, size);
d->objects.append(obj);
}
}
bool ASF::File::save()
{
if(readOnly()) {
debug("ASF::File::save() -- File is read only.");
return false;
}
if(!isValid()) {
debug("ASF::File::save() -- Trying to save invalid file.");
return false;
}
if(!d->contentDescriptionObject) {
d->contentDescriptionObject = new ContentDescriptionObject();
d->objects.append(d->contentDescriptionObject);
}
if(!d->extendedContentDescriptionObject) {
d->extendedContentDescriptionObject = new ExtendedContentDescriptionObject();
d->objects.append(d->extendedContentDescriptionObject);
}
if(!d->headerExtensionObject) {
d->headerExtensionObject = new HeaderExtensionObject();
d->objects.append(d->headerExtensionObject);
}
if(!d->metadataObject) {
d->metadataObject = new MetadataObject();
d->headerExtensionObject->objects.append(d->metadataObject);
}
if(!d->metadataLibraryObject) {
d->metadataLibraryObject = new MetadataLibraryObject();
d->headerExtensionObject->objects.append(d->metadataLibraryObject);
}
ASF::AttributeListMap::ConstIterator it = d->tag->attributeListMap().begin();
for(; it != d->tag->attributeListMap().end(); it++) {
const String &name = it->first;
const AttributeList &attributes = it->second;
bool inExtendedContentDescriptionObject = false;
bool inMetadataObject = false;
for(unsigned int j = 0; j < attributes.size(); j++) {
const Attribute &attribute = attributes[j];
bool largeValue = attribute.dataSize() > 65535;
if(!inExtendedContentDescriptionObject && !largeValue && attribute.language() == 0 && attribute.stream() == 0) {
d->extendedContentDescriptionObject->attributeData.append(attribute.render(name));
inExtendedContentDescriptionObject = true;
}
else if(!inMetadataObject && !largeValue && attribute.language() == 0 && attribute.stream() != 0) {
d->metadataObject->attributeData.append(attribute.render(name, 1));
inMetadataObject = true;
}
else {
d->metadataLibraryObject->attributeData.append(attribute.render(name, 2));
}
}
}
ByteVector data;
for(unsigned int i = 0; i < d->objects.size(); i++) {
data.append(d->objects[i]->render(this));
}
data = headerGuid + ByteVector::fromUInt64LE(data.size() + 30) + ByteVector::fromUInt32LE(d->objects.size()) + ByteVector("\x01\x02", 2) + data;
insert(data, 0, (uint)d->size);
return true;
}
////////////////////////////////////////////////////////////////////////////////
// protected members
////////////////////////////////////////////////////////////////////////////////
int ASF::File::readBYTE(bool *ok)
{
ByteVector v = readBlock(1);
if(v.size() != 1) {
if(ok) *ok = false;
return 0;
}
if(ok) *ok = true;
return v[0];
}
int ASF::File::readWORD(bool *ok)
{
ByteVector v = readBlock(2);
if(v.size() != 2) {
if(ok) *ok = false;
return 0;
}
if(ok) *ok = true;
return v.toUInt16LE(0);
}
unsigned int ASF::File::readDWORD(bool *ok)
{
ByteVector v = readBlock(4);
if(v.size() != 4) {
if(ok) *ok = false;
return 0;
}
if(ok) *ok = true;
return v.toUInt32LE(0);
}
long long ASF::File::readQWORD(bool *ok)
{
ByteVector v = readBlock(8);
if(v.size() != 8) {
if(ok) *ok = false;
return 0;
}
if(ok) *ok = true;
return v.toInt64LE(0);
}
String ASF::File::readString(int length)
{
ByteVector data = readBlock(length);
size_t size = data.size();
while (size >= 2) {
if(data[size - 1] != '\0' || data[size - 2] != '\0') {
break;
}
size -= 2;
}
if(size != data.size()) {
data.resize(size);
}
return String(data, String::UTF16LE);
}
ByteVector ASF::File::renderString(const String &str, bool includeLength)
{
ByteVector data = str.data(String::UTF16LE) + ByteVector::fromUInt16LE(0);
if(includeLength) {
data = ByteVector::fromUInt16LE(data.size()) + data;
}
return data;
}

View File

@ -56,7 +56,7 @@ namespace TagLib {
* \a propertiesStyle are ignored. The audio properties are always
* read.
*/
File(FileName file, bool readProperties = true,
File(FileName file, bool readProperties = true,
AudioProperties::ReadStyle propertiesStyle = AudioProperties::Average);
/*!
@ -71,7 +71,7 @@ namespace TagLib {
* \note TagLib will *not* take ownership of the stream, the caller is
* responsible for deleting it after the File object.
*/
File(IOStream *stream, bool readProperties = true,
File(IOStream *stream, bool readProperties = true,
AudioProperties::ReadStyle propertiesStyle = AudioProperties::Average);
/*!
@ -116,30 +116,14 @@ namespace TagLib {
* Save the file.
*
* This returns true if the save was successful.
*
* \warning In the current implementation, it's dangerous to call save()
* repeatedly. At worst it will corrupt the file.
*/
virtual bool save();
private:
int readBYTE(bool *ok = 0);
int readWORD(bool *ok = 0);
unsigned int readDWORD(bool *ok = 0);
long long readQWORD(bool *ok = 0);
static ByteVector renderString(const String &str, bool includeLength = false);
String readString(int len);
void read(bool readProperties, AudioProperties::ReadStyle propertiesStyle);
friend class Attribute;
friend class Picture;
class BaseObject;
class UnknownObject;
class FilePropertiesObject;
class StreamPropertiesObject;
class ContentDescriptionObject;
class ExtendedContentDescriptionObject;
class HeaderExtensionObject;
class MetadataObject;
class MetadataLibraryObject;
void read();
class FilePrivate;
FilePrivate *d;

View File

@ -23,12 +23,14 @@
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#include "taglib.h"
#include "tdebug.h"
#include "tsmartptr.h"
#include <taglib.h>
#include <tdebug.h>
#include <tsmartptr.h>
#include "asfattribute.h"
#include "asffile.h"
#include "asfpicture.h"
#include "asfutils.h"
using namespace TagLib;
@ -44,13 +46,11 @@ namespace
};
}
class ASF::Picture::PicturePrivate
class ASF::Picture::PicturePrivate
{
public:
PicturePrivate()
: data(new PictureData())
{
}
PicturePrivate() :
data(new PictureData()) {}
SHARED_PTR<PictureData> data;
};
@ -141,8 +141,8 @@ ByteVector ASF::Picture::render() const
return
ByteVector((char)d->data->type) +
ByteVector::fromUInt32LE(d->data->picture.size()) +
ASF::File::renderString(d->data->mimeType) +
ASF::File::renderString(d->data->description) +
renderString(d->data->mimeType) +
renderString(d->data->description) +
d->data->picture;
}

View File

@ -47,7 +47,7 @@ namespace TagLib
* \see Attribute::toPicture()
* \see Attribute::Attribute(const Picture& picture)
*/
class TAGLIB_EXPORT Picture
class TAGLIB_EXPORT Picture
{
public:

View File

@ -32,17 +32,23 @@ using namespace TagLib;
class ASF::AudioProperties::PropertiesPrivate
{
public:
PropertiesPrivate() :
length(0),
bitrate(0),
sampleRate(0),
channels(0),
PropertiesPrivate() :
length(0),
bitrate(0),
sampleRate(0),
channels(0),
bitsPerSample(0),
codec(ASF::AudioProperties::Unknown),
encrypted(false) {}
int length;
int bitrate;
int sampleRate;
int channels;
int bitsPerSample;
ASF::AudioProperties::Codec codec;
String codecName;
String codecDescription;
bool encrypted;
};
@ -51,6 +57,7 @@ public:
////////////////////////////////////////////////////////////////////////////////
ASF::AudioProperties::AudioProperties() :
TagLib::AudioProperties(),
d(new PropertiesPrivate())
{
}
@ -61,6 +68,16 @@ ASF::AudioProperties::~AudioProperties()
}
int ASF::AudioProperties::length() const
{
return lengthInSeconds();
}
int ASF::AudioProperties::lengthInSeconds() const
{
return d->length / 1000;
}
int ASF::AudioProperties::lengthInMilliseconds() const
{
return d->length;
}
@ -80,6 +97,26 @@ int ASF::AudioProperties::channels() const
return d->channels;
}
int ASF::AudioProperties::bitsPerSample() const
{
return d->bitsPerSample;
}
ASF::AudioProperties::Codec ASF::AudioProperties::codec() const
{
return d->codec;
}
String ASF::AudioProperties::codecName() const
{
return d->codecName;
}
String ASF::AudioProperties::codecDescription() const
{
return d->codecDescription;
}
bool ASF::AudioProperties::isEncrypted() const
{
return d->encrypted;
@ -89,28 +126,65 @@ bool ASF::AudioProperties::isEncrypted() const
// private members
////////////////////////////////////////////////////////////////////////////////
void ASF::AudioProperties::setLength(int length)
void ASF::AudioProperties::setLengthInMilliseconds(int value)
{
d->length = length;
d->length = value;
}
void ASF::AudioProperties::setBitrate(int length)
void ASF::AudioProperties::setBitrate(int value)
{
d->bitrate = length;
d->bitrate = value;
}
void ASF::AudioProperties::setSampleRate(int length)
void ASF::AudioProperties::setSampleRate(int value)
{
d->sampleRate = length;
d->sampleRate = value;
}
void ASF::AudioProperties::setChannels(int length)
void ASF::AudioProperties::setChannels(int value)
{
d->channels = length;
d->channels = value;
}
void ASF::AudioProperties::setEncrypted(bool encrypted)
void ASF::AudioProperties::setBitsPerSample(int value)
{
d->encrypted = encrypted;
d->bitsPerSample = value;
}
void ASF::AudioProperties::setCodec(int value)
{
switch(value)
{
case 0x0160:
d->codec = WMA1;
break;
case 0x0161:
d->codec = WMA2;
break;
case 0x0162:
d->codec = WMA9Pro;
break;
case 0x0163:
d->codec = WMA9Lossless;
break;
default:
d->codec = Unknown;
break;
}
}
void ASF::AudioProperties::setCodecName(const String &value)
{
d->codecName = value;
}
void ASF::AudioProperties::setCodecDescription(const String &value)
{
d->codecDescription = value;
}
void ASF::AudioProperties::setEncrypted(bool value)
{
d->encrypted = value;
}

View File

@ -42,6 +42,37 @@ namespace TagLib {
friend class File;
public:
/*!
* Audio codec types can be used in ASF file.
*/
enum Codec
{
/*!
* Couldn't detect the codec.
*/
Unknown = 0,
/*!
* Windows Media Audio 1
*/
WMA1,
/*!
* Windows Media Audio 2 or above
*/
WMA2,
/*!
* Windows Media Audio 9 Professional
*/
WMA9Pro,
/*!
* Windows Media Audio 9 Lossless
*/
WMA9Lossless,
};
/*!
* Creates an instance of ASF::AudioProperties.
*/
@ -52,19 +83,94 @@ namespace TagLib {
*/
virtual ~AudioProperties();
// Reimplementations.
/*!
* Returns the length of the file in seconds. The length is rounded down to
* the nearest whole second.
*
* \note This method is just an alias of lengthInSeconds().
*
* \deprecated
*/
virtual int length() const;
/*!
* Returns the length of the file in seconds. The length is rounded down to
* the nearest whole second.
*
* \see lengthInMilliseconds()
*/
// BIC: make virtual
int lengthInSeconds() const;
/*!
* Returns the length of the file in milliseconds.
*
* \see lengthInSeconds()
*/
// BIC: make virtual
int lengthInMilliseconds() const;
/*!
* Returns the average bit rate of the file in kb/s.
*/
virtual int bitrate() const;
/*!
* Returns the sample rate in Hz.
*/
virtual int sampleRate() const;
/*!
* Returns the number of audio channels.
*/
virtual int channels() const;
/*!
* Returns the number of bits per audio sample.
*/
int bitsPerSample() const;
/*!
* Returns the codec used in the file.
*
* \see codecName()
* \see codecDescription()
*/
Codec codec() const;
/*!
* Returns the concrete codec name, for example "Windows Media Audio 9.1"
* used in the file if available, otherwise an empty string.
*
* \see codec()
* \see codecDescription()
*/
String codecName() const;
/*!
* Returns the codec description, typically contains the encoder settings,
* for example "VBR Quality 50, 44kHz, stereo 1-pass VBR" if available,
* otherwise an empty string.
*
* \see codec()
* \see codecName()
*/
String codecDescription() const;
/*!
* Returns whether or not the file is encrypted.
*/
bool isEncrypted() const;
private:
void setLength(int value);
void setLengthInMilliseconds(int value);
void setBitrate(int value);
void setSampleRate(int value);
void setChannels(int value);
void setBitsPerSample(int value);
void setCodec(int value);
void setCodecName(const String &value);
void setCodecDescription(const String &value);
void setEncrypted(bool value);
class PropertiesPrivate;

View File

@ -47,8 +47,7 @@ ASF::Tag::Tag()
ASF::Tag::~Tag()
{
if(d)
delete d;
delete d;
}
String ASF::Tag::title() const
@ -161,11 +160,24 @@ ASF::AttributeListMap& ASF::Tag::attributeListMap()
return d->attributeListMap;
}
const ASF::AttributeListMap &ASF::Tag::attributeListMap() const
{
return d->attributeListMap;
}
bool ASF::Tag::contains(const String &key) const
{
return d->attributeListMap.contains(key);
}
void ASF::Tag::removeItem(const String &key)
{
AttributeListMap::Iterator it = d->attributeListMap.find(key);
if(it != d->attributeListMap.end())
d->attributeListMap.erase(it);
d->attributeListMap.erase(key);
}
ASF::AttributeList ASF::Tag::attribute(const String &name) const
{
return d->attributeListMap[name];
}
void ASF::Tag::setAttribute(const String &name, const Attribute &attribute)
@ -175,6 +187,11 @@ void ASF::Tag::setAttribute(const String &name, const Attribute &attribute)
d->attributeListMap.insert(name, value);
}
void ASF::Tag::setAttribute(const String &name, const AttributeList &values)
{
d->attributeListMap.insert(name, values);
}
void ASF::Tag::addAttribute(const String &name, const Attribute &attribute)
{
if(d->attributeListMap.contains(name)) {

View File

@ -152,24 +152,43 @@ namespace TagLib {
virtual bool isEmpty() const;
/*!
* Returns a reference to the item list map. This is an AttributeListMap of
* all of the items in the tag.
*
* This is the most powerfull structure for accessing the items of the tag.
* \deprecated
*/
AttributeListMap &attributeListMap();
/*!
* Returns a reference to the item list map. This is an AttributeListMap of
* all of the items in the tag.
*/
const AttributeListMap &attributeListMap() const;
/*!
* \return True if a value for \a attribute is currently set.
*/
bool contains(const String &name) const;
/*!
* Removes the \a key attribute from the tag
*/
void removeItem(const String &name);
/*!
* \return The list of values for the key \a name, or an empty list if no
* values have been set.
*/
AttributeList attribute(const String &name) const;
/*!
* Sets the \a key attribute to the value of \a attribute. If an attribute
* with the \a key is already present, it will be replaced.
*/
void setAttribute(const String &name, const Attribute &attribute);
/*!
* Sets multiple \a values to the key \a name.
*/
void setAttribute(const String &name, const AttributeList &values);
/*!
* Sets the \a key attribute to the value of \a attribute. If an attribute
* with the \a key is already present, it will be added to the list.

101
taglib/asf/asfutils.h Normal file
View File

@ -0,0 +1,101 @@
/***************************************************************************
copyright : (C) 2015 by Tsuda Kageyu
email : tsuda.kageyu@gmail.com
***************************************************************************/
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#ifndef TAGLIB_ASFUTILS_H
#define TAGLIB_ASFUTILS_H
// THIS FILE IS NOT A PART OF THE TAGLIB API
#ifndef DO_NOT_DOCUMENT // tell Doxygen not to document this header
namespace TagLib
{
namespace ASF
{
inline ushort readWORD(File *file, bool *ok = 0)
{
const ByteVector v = file->readBlock(2);
if(v.size() != 2) {
if(ok) *ok = false;
return 0;
}
if(ok) *ok = true;
return v.toUInt16LE(0);
}
inline uint readDWORD(File *file, bool *ok = 0)
{
const ByteVector v = file->readBlock(4);
if(v.size() != 4) {
if(ok) *ok = false;
return 0;
}
if(ok) *ok = true;
return v.toUInt32LE(0);
}
inline long long readQWORD(File *file, bool *ok = 0)
{
const ByteVector v = file->readBlock(8);
if(v.size() != 8) {
if(ok) *ok = false;
return 0;
}
if(ok) *ok = true;
return v.toInt64LE(0);
}
inline String readString(File *file, int length)
{
ByteVector data = file->readBlock(length);
unsigned int size = data.size();
while (size >= 2) {
if(data[size - 1] != '\0' || data[size - 2] != '\0') {
break;
}
size -= 2;
}
if(size != data.size()) {
data.resize(size);
}
return String(data, String::UTF16LE);
}
inline ByteVector renderString(const String &str, bool includeLength = false)
{
ByteVector data = str.data(String::UTF16LE) + ByteVector::fromUInt16LE(0);
if(includeLength) {
data = ByteVector::fromUInt16LE(data.size()) + data;
}
return data;
}
}
}
#endif
#endif

View File

@ -23,6 +23,22 @@
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#include <tbytevector.h>
#include "aiffproperties.h"
#include "apeproperties.h"
#include "asfproperties.h"
#include "flacproperties.h"
#include "mp4properties.h"
#include "mpcproperties.h"
#include "mpegproperties.h"
#include "opusproperties.h"
#include "speexproperties.h"
#include "trueaudioproperties.h"
#include "vorbisproperties.h"
#include "wavproperties.h"
#include "wavpackproperties.h"
#include "audioproperties.h"
#include "tstringlist.h"
@ -45,10 +61,107 @@ String AudioProperties::toString() const
return desc.toString(", ");
}
int TagLib::AudioProperties::lengthInSeconds() const
{
// This is an ugly workaround but we can't add a virtual function.
// Should be virtual in taglib2.
if(dynamic_cast<const APE::AudioProperties*>(this))
return dynamic_cast<const APE::AudioProperties*>(this)->lengthInSeconds();
else if(dynamic_cast<const ASF::AudioProperties*>(this))
return dynamic_cast<const ASF::AudioProperties*>(this)->lengthInSeconds();
else if(dynamic_cast<const FLAC::AudioProperties*>(this))
return dynamic_cast<const FLAC::AudioProperties*>(this)->lengthInSeconds();
else if(dynamic_cast<const MP4::AudioProperties*>(this))
return dynamic_cast<const MP4::AudioProperties*>(this)->lengthInSeconds();
else if(dynamic_cast<const MPC::AudioProperties*>(this))
return dynamic_cast<const MPC::AudioProperties*>(this)->lengthInSeconds();
else if(dynamic_cast<const MPEG::AudioProperties*>(this))
return dynamic_cast<const MPEG::AudioProperties*>(this)->lengthInSeconds();
else if(dynamic_cast<const Ogg::Opus::AudioProperties*>(this))
return dynamic_cast<const Ogg::Opus::AudioProperties*>(this)->lengthInSeconds();
else if(dynamic_cast<const Ogg::Speex::AudioProperties*>(this))
return dynamic_cast<const Ogg::Speex::AudioProperties*>(this)->lengthInSeconds();
else if(dynamic_cast<const TrueAudio::AudioProperties*>(this))
return dynamic_cast<const TrueAudio::AudioProperties*>(this)->lengthInSeconds();
else if (dynamic_cast<const RIFF::AIFF::AudioProperties*>(this))
return dynamic_cast<const RIFF::AIFF::AudioProperties*>(this)->lengthInSeconds();
else if(dynamic_cast<const RIFF::WAV::AudioProperties*>(this))
return dynamic_cast<const RIFF::WAV::AudioProperties*>(this)->lengthInSeconds();
else if(dynamic_cast<const Ogg::Vorbis::AudioProperties*>(this))
return dynamic_cast<const Ogg::Vorbis::AudioProperties*>(this)->lengthInSeconds();
else if(dynamic_cast<const WavPack::AudioProperties*>(this))
return dynamic_cast<const WavPack::AudioProperties*>(this)->lengthInSeconds();
else
return 0;
}
int TagLib::AudioProperties::lengthInMilliseconds() const
{
// This is an ugly workaround but we can't add a virtual function.
// Should be virtual in taglib2.
if(dynamic_cast<const APE::AudioProperties*>(this))
return dynamic_cast<const APE::AudioProperties*>(this)->lengthInMilliseconds();
else if(dynamic_cast<const ASF::AudioProperties*>(this))
return dynamic_cast<const ASF::AudioProperties*>(this)->lengthInMilliseconds();
else if(dynamic_cast<const FLAC::AudioProperties*>(this))
return dynamic_cast<const FLAC::AudioProperties*>(this)->lengthInMilliseconds();
else if(dynamic_cast<const MP4::AudioProperties*>(this))
return dynamic_cast<const MP4::AudioProperties*>(this)->lengthInMilliseconds();
else if(dynamic_cast<const MPC::AudioProperties*>(this))
return dynamic_cast<const MPC::AudioProperties*>(this)->lengthInMilliseconds();
else if(dynamic_cast<const MPEG::AudioProperties*>(this))
return dynamic_cast<const MPEG::AudioProperties*>(this)->lengthInMilliseconds();
else if(dynamic_cast<const Ogg::Opus::AudioProperties*>(this))
return dynamic_cast<const Ogg::Opus::AudioProperties*>(this)->lengthInMilliseconds();
else if(dynamic_cast<const Ogg::Speex::AudioProperties*>(this))
return dynamic_cast<const Ogg::Speex::AudioProperties*>(this)->lengthInMilliseconds();
else if(dynamic_cast<const TrueAudio::AudioProperties*>(this))
return dynamic_cast<const TrueAudio::AudioProperties*>(this)->lengthInMilliseconds();
else if(dynamic_cast<const RIFF::AIFF::AudioProperties*>(this))
return dynamic_cast<const RIFF::AIFF::AudioProperties*>(this)->lengthInMilliseconds();
else if(dynamic_cast<const RIFF::WAV::AudioProperties*>(this))
return dynamic_cast<const RIFF::WAV::AudioProperties*>(this)->lengthInMilliseconds();
else if(dynamic_cast<const Ogg::Vorbis::AudioProperties*>(this))
return dynamic_cast<const Ogg::Vorbis::AudioProperties*>(this)->lengthInMilliseconds();
else if(dynamic_cast<const WavPack::AudioProperties*>(this))
return dynamic_cast<const WavPack::AudioProperties*>(this)->lengthInMilliseconds();
else
return 0;
}
////////////////////////////////////////////////////////////////////////////////
// protected methods
////////////////////////////////////////////////////////////////////////////////
AudioProperties::AudioProperties()
AudioProperties::AudioProperties() :
d(0)
{
}

View File

@ -35,7 +35,7 @@ namespace TagLib {
/*!
* The values here are common to most audio formats. For more specific, codec
* dependant values, please see see the subclasses APIs. This is meant to
* dependent values, please see see the subclasses APIs. This is meant to
* compliment the TagLib::File and TagLib::Tag APIs in providing a simple
* interface that is sufficient for most applications.
*/
@ -70,6 +70,23 @@ namespace TagLib {
*/
virtual int length() const = 0;
/*!
* Returns the length of the file in seconds. The length is rounded down to
* the nearest whole second.
*
* \see lengthInMilliseconds()
*/
// BIC: make virtual
int lengthInSeconds() const;
/*!
* Returns the length of the file in milliseconds.
*
* \see lengthInSeconds()
*/
// BIC: make virtual
int lengthInMilliseconds() const;
/*!
* Returns the most appropriate bit rate for the file in kb/s. For constant
* bitrate formats this is simply the bitrate of the file. For variable
@ -105,6 +122,9 @@ namespace TagLib {
// Noncopyable. Derived classes as well.
AudioProperties(const AudioProperties &);
AudioProperties &operator=(const AudioProperties &);
class PropertiesPrivate;
PropertiesPrivate *d;
};
}

View File

@ -72,7 +72,7 @@ namespace TagLib {
*
* class MyFileTypeResolver : FileTypeResolver
* {
* TagLib::File *createFile(TagLib::FileName *fileName, bool, AudioProperties::ReadStyle)
* TagLib::File *createFile(TagLib::FileName *fileName, bool, AudioProperties::ReadStyle) const
* {
* if(someCheckForAnMP3File(fileName))
* return new TagLib::MPEG::File(fileName);
@ -129,7 +129,7 @@ namespace TagLib {
audioPropertiesStyle = AudioProperties::Average);
/*!
* Contruct a FileRef using \a file. The FileRef now takes ownership of the
* Construct a FileRef using \a file. The FileRef now takes ownership of the
* pointer and will delete the File when it passes out of scope.
*/
explicit FileRef(File *file);
@ -227,7 +227,7 @@ namespace TagLib {
* is tried.
*
* Returns a pointer to the added resolver (the same one that's passed in --
* this is mostly so that static inialializers have something to use for
* this is mostly so that static initializers have something to use for
* assignment).
*
* \see FileTypeResolver
@ -245,7 +245,7 @@ namespace TagLib {
* by TagLib for resolution is case-insensitive.
*
* \note This does not account for any additional file type resolvers that
* are plugged in. Also note that this is not intended to replace a propper
* are plugged in. Also note that this is not intended to replace a proper
* mime-type resolution system, but is just here for reference.
*
* \see FileTypeResolver

View File

@ -60,7 +60,6 @@ public:
properties(0),
flacStart(0),
streamStart(0),
streamLength(0),
scanned(false),
hasXiphComment(false),
hasID3v2(false),
@ -86,13 +85,11 @@ public:
TripleTagUnion tag;
AudioProperties *properties;
ByteVector streamInfoData;
ByteVector xiphCommentData;
List<MetadataBlock *> blocks;
offset_t flacStart;
offset_t streamStart;
offset_t streamLength;
bool scanned;
bool hasXiphComment;
@ -104,28 +101,26 @@ public:
// public members
////////////////////////////////////////////////////////////////////////////////
FLAC::File::File(FileName file,
bool readProperties, AudioProperties::ReadStyle propertiesStyle,
FLAC::File::File(FileName file, bool readProperties, AudioProperties::ReadStyle,
ID3v2::FrameFactory *frameFactory) :
TagLib::File(file)
TagLib::File(file),
d(new FilePrivate())
{
d = new FilePrivate;
if(frameFactory)
d->ID3v2FrameFactory = frameFactory;
if(isOpen())
read(readProperties, propertiesStyle);
read(readProperties);
}
FLAC::File::File(IOStream *stream,
bool readProperties, AudioProperties::ReadStyle propertiesStyle,
FLAC::File::File(IOStream *stream, bool readProperties, AudioProperties::ReadStyle,
ID3v2::FrameFactory *frameFactory) :
TagLib::File(stream)
TagLib::File(stream),
d(new FilePrivate())
{
d = new FilePrivate;
if(frameFactory)
d->ID3v2FrameFactory = frameFactory;
if(isOpen())
read(readProperties, propertiesStyle);
read(readProperties);
}
FLAC::File::~File()
@ -148,7 +143,6 @@ FLAC::AudioProperties *FLAC::File::audioProperties() const
return d->properties;
}
bool FLAC::File::save()
{
if(readOnly()) {
@ -205,9 +199,9 @@ bool FLAC::File::save()
// Adjust the padding block(s)
uint originalLength = static_cast<uint>(d->streamStart - d->flacStart);
int paddingLength = static_cast<int>(originalLength - data.size() - 4);
if (paddingLength < 0) {
long originalLength = static_cast<long>(d->streamStart - d->flacStart);
int paddingLength = originalLength - data.size() - 4;
if(paddingLength <= 0) {
paddingLength = MinPaddingLength;
}
ByteVector padding = ByteVector::fromUInt32BE(paddingLength);
@ -235,8 +229,16 @@ bool FLAC::File::save()
}
if(ID3v1Tag()) {
seek(-128, End);
if(d->hasID3v1) {
seek(d->ID3v1Location);
}
else {
seek(0, End);
d->ID3v1Location = tell();
}
writeBlock(ID3v1Tag()->render());
d->hasID3v1 = true;
}
return true;
@ -262,211 +264,6 @@ void FLAC::File::setID3v2FrameFactory(const ID3v2::FrameFactory *factory)
d->ID3v2FrameFactory = factory;
}
////////////////////////////////////////////////////////////////////////////////
// private members
////////////////////////////////////////////////////////////////////////////////
void FLAC::File::read(bool readProperties, AudioProperties::ReadStyle propertiesStyle)
{
// Look for an ID3v2 tag
d->ID3v2Location = findID3v2();
if(d->ID3v2Location >= 0) {
d->tag.set(FlacID3v2Index, new ID3v2::Tag(this, d->ID3v2Location, d->ID3v2FrameFactory));
d->ID3v2OriginalSize = ID3v2Tag()->header()->completeTagSize();
if(ID3v2Tag()->header()->tagSize() <= 0)
d->tag.set(FlacID3v2Index, 0);
else
d->hasID3v2 = true;
}
// Look for an ID3v1 tag
d->ID3v1Location = findID3v1();
if(d->ID3v1Location >= 0) {
d->tag.set(FlacID3v1Index, new ID3v1::Tag(this, d->ID3v1Location));
d->hasID3v1 = true;
}
// Look for FLAC metadata, including vorbis comments
scan();
if(!isValid())
return;
if(d->hasXiphComment)
d->tag.set(FlacXiphIndex, new Ogg::XiphComment(xiphCommentData()));
else
d->tag.set(FlacXiphIndex, new Ogg::XiphComment);
if(readProperties)
d->properties = new AudioProperties(d->streamInfoData, d->streamLength, propertiesStyle);
}
ByteVector FLAC::File::xiphCommentData() const
{
return (isValid() && d->hasXiphComment) ? d->xiphCommentData : ByteVector();
}
void FLAC::File::scan()
{
// Scan the metadata pages
if(d->scanned)
return;
if(!isValid())
return;
offset_t nextBlockOffset;
if(d->hasID3v2)
nextBlockOffset = find("fLaC", d->ID3v2Location + d->ID3v2OriginalSize);
else
nextBlockOffset = find("fLaC");
if(nextBlockOffset < 0) {
debug("FLAC::File::scan() -- FLAC stream not found");
setValid(false);
return;
}
nextBlockOffset += 4;
d->flacStart = nextBlockOffset;
seek(nextBlockOffset);
ByteVector header = readBlock(4);
// Header format (from spec):
// <1> Last-metadata-block flag
// <7> BLOCK_TYPE
// 0 : STREAMINFO
// 1 : PADDING
// ..
// 4 : VORBIS_COMMENT
// ..
// <24> Length of metadata to follow
char blockType = header[0] & 0x7f;
bool isLastBlock = (header[0] & 0x80) != 0;
uint length = header.toUInt24BE(1);
// First block should be the stream_info metadata
if(blockType != MetadataBlock::StreamInfo) {
debug("FLAC::File::scan() -- invalid FLAC stream");
setValid(false);
return;
}
d->streamInfoData = readBlock(length);
d->blocks.append(new UnknownMetadataBlock(blockType, d->streamInfoData));
nextBlockOffset += length + 4;
// Search through the remaining metadata
while(!isLastBlock) {
header = readBlock(4);
blockType = header[0] & 0x7f;
isLastBlock = (header[0] & 0x80) != 0;
length = header.toUInt24BE(1);
ByteVector data = readBlock(length);
if(data.size() != length || length == 0) {
debug("FLAC::File::scan() -- FLAC stream corrupted");
setValid(false);
return;
}
MetadataBlock *block = 0;
// Found the vorbis-comment
if(blockType == MetadataBlock::VorbisComment) {
if(!d->hasXiphComment) {
d->xiphCommentData = data;
d->hasXiphComment = true;
}
else {
debug("FLAC::File::scan() -- multiple Vorbis Comment blocks found, using the first one");
}
}
else if(blockType == MetadataBlock::Picture) {
FLAC::Picture *picture = new FLAC::Picture();
if(picture->parse(data)) {
block = picture;
}
else {
debug("FLAC::File::scan() -- invalid picture found, discarting");
delete picture;
}
}
if(!block) {
block = new UnknownMetadataBlock(blockType, data);
}
if(block->code() != MetadataBlock::Padding) {
d->blocks.append(block);
}
else {
delete block;
}
nextBlockOffset += length + 4;
if(nextBlockOffset >= File::length()) {
debug("FLAC::File::scan() -- FLAC stream corrupted");
setValid(false);
return;
}
seek(nextBlockOffset);
}
// End of metadata, now comes the datastream
d->streamStart = nextBlockOffset;
d->streamLength = File::length() - d->streamStart;
if(d->hasID3v1)
d->streamLength -= 128;
d->scanned = true;
}
offset_t FLAC::File::findID3v1()
{
if(!isValid())
return -1;
seek(-128, End);
offset_t p = tell();
if(readBlock(3) == ID3v1::Tag::fileIdentifier())
return p;
return -1;
}
offset_t FLAC::File::findID3v2()
{
if(!isValid())
return -1;
seek(0);
if(readBlock(3) == ID3v2::Header::fileIdentifier())
return 0;
return -1;
}
List<FLAC::Picture *> FLAC::File::pictureList()
{
List<Picture *> pictures;
@ -524,3 +321,216 @@ bool FLAC::File::hasID3v2Tag() const
{
return d->hasID3v2;
}
////////////////////////////////////////////////////////////////////////////////
// private members
////////////////////////////////////////////////////////////////////////////////
void FLAC::File::read(bool readProperties)
{
// Look for an ID3v2 tag
d->ID3v2Location = findID3v2();
if(d->ID3v2Location >= 0) {
d->tag.set(FlacID3v2Index, new ID3v2::Tag(this, d->ID3v2Location, d->ID3v2FrameFactory));
d->ID3v2OriginalSize = ID3v2Tag()->header()->completeTagSize();
if(ID3v2Tag()->header()->tagSize() <= 0)
d->tag.set(FlacID3v2Index, 0);
else
d->hasID3v2 = true;
}
// Look for an ID3v1 tag
d->ID3v1Location = findID3v1();
if(d->ID3v1Location >= 0) {
d->tag.set(FlacID3v1Index, new ID3v1::Tag(this, d->ID3v1Location));
d->hasID3v1 = true;
}
// Look for FLAC metadata, including vorbis comments
scan();
if(!isValid())
return;
if(d->hasXiphComment)
d->tag.set(FlacXiphIndex, new Ogg::XiphComment(d->xiphCommentData));
else
d->tag.set(FlacXiphIndex, new Ogg::XiphComment);
if(readProperties) {
// First block should be the stream_info metadata
const ByteVector infoData = d->blocks.front()->render();
offset_t streamLength;
if(d->hasID3v1)
streamLength = d->ID3v1Location - d->streamStart;
else
streamLength = File::length() - d->streamStart;
d->properties = new AudioProperties(infoData, streamLength);
}
}
void FLAC::File::scan()
{
// Scan the metadata pages
if(d->scanned)
return;
if(!isValid())
return;
offset_t nextBlockOffset;
if(d->hasID3v2)
nextBlockOffset = find("fLaC", d->ID3v2Location + d->ID3v2OriginalSize);
else
nextBlockOffset = find("fLaC");
if(nextBlockOffset < 0) {
debug("FLAC::File::scan() -- FLAC stream not found");
setValid(false);
return;
}
nextBlockOffset += 4;
d->flacStart = nextBlockOffset;
seek(nextBlockOffset);
ByteVector header = readBlock(4);
// Header format (from spec):
// <1> Last-metadata-block flag
// <7> BLOCK_TYPE
// 0 : STREAMINFO
// 1 : PADDING
// ..
// 4 : VORBIS_COMMENT
// ..
// <24> Length of metadata to follow
char blockType = header[0] & 0x7f;
bool isLastBlock = (header[0] & 0x80) != 0;
uint length = header.toUInt24BE(1);
// First block should be the stream_info metadata
if(blockType != MetadataBlock::StreamInfo) {
debug("FLAC::File::scan() -- invalid FLAC stream");
setValid(false);
return;
}
d->blocks.append(new UnknownMetadataBlock(blockType, readBlock(length)));
nextBlockOffset += length + 4;
// Search through the remaining metadata
while(!isLastBlock) {
header = readBlock(4);
blockType = header[0] & 0x7f;
isLastBlock = (header[0] & 0x80) != 0;
length = header.toUInt24BE(1);
if(length == 0 && blockType != MetadataBlock::Padding) {
debug("FLAC::File::scan() -- Zero-sized metadata block found");
setValid(false);
return;
}
const ByteVector data = readBlock(length);
if(data.size() != length) {
debug("FLAC::File::scan() -- Failed to read a metadata block");
setValid(false);
return;
}
MetadataBlock *block = 0;
// Found the vorbis-comment
if(blockType == MetadataBlock::VorbisComment) {
if(!d->hasXiphComment) {
d->xiphCommentData = data;
d->hasXiphComment = true;
}
else {
debug("FLAC::File::scan() -- multiple Vorbis Comment blocks found, using the first one");
}
}
else if(blockType == MetadataBlock::Picture) {
FLAC::Picture *picture = new FLAC::Picture();
if(picture->parse(data)) {
block = picture;
}
else {
debug("FLAC::File::scan() -- invalid picture found, discarding");
delete picture;
}
}
if(!block) {
block = new UnknownMetadataBlock(blockType, data);
}
if(block->code() != MetadataBlock::Padding) {
d->blocks.append(block);
}
else {
delete block;
}
nextBlockOffset += length + 4;
if(nextBlockOffset >= File::length()) {
debug("FLAC::File::scan() -- FLAC stream corrupted");
setValid(false);
return;
}
seek(nextBlockOffset);
}
// End of metadata, now comes the datastream
d->streamStart = nextBlockOffset;
d->scanned = true;
}
offset_t FLAC::File::findID3v1()
{
if(!isValid())
return -1;
seek(-128, End);
offset_t p = tell();
if(readBlock(3) == ID3v1::Tag::fileIdentifier())
return p;
return -1;
}
offset_t FLAC::File::findID3v2()
{
if(!isValid())
return -1;
seek(0);
if(readBlock(3) == ID3v2::Header::fileIdentifier())
return 0;
return -1;
}

View File

@ -46,7 +46,7 @@ namespace TagLib {
/*!
* This is implementation of FLAC metadata for non-Ogg FLAC files. At some
* point when Ogg / FLAC is more common there will be a similar implementation
* under the Ogg hiearchy.
* under the Ogg hierarchy.
*
* This supports ID3v1, ID3v2 and Xiph style comments as well as reading stream
* properties from the file.
@ -67,7 +67,7 @@ namespace TagLib {
{
public:
/*!
* Constructs a FLAC file from \a file. If \a readProperties is true the
* Constructs an FLAC file from \a file. If \a readProperties is true the
* file's audio properties will also be read.
*
* If this file contains and ID3v2 tag the frames will be created using
@ -133,6 +133,9 @@ namespace TagLib {
* has no XiphComment, one will be constructed from the ID3-tags.
*
* This returns true if the save was successful.
*
* \warning In the current implementation, it's dangerous to call save()
* repeatedly. At worst it will corrupt the file.
*/
virtual bool save();
@ -143,8 +146,8 @@ namespace TagLib {
* if there is no valid ID3v2 tag. If \a create is true it will create
* an ID3v2 tag if one does not exist and returns a valid pointer.
*
* \note This may return a valid pointer regardless of whether or not the
* file on disk has an ID3v2 tag. Use hasID3v2Tag() to check if the file
* \note This may return a valid pointer regardless of whether or not the
* file on disk has an ID3v2 tag. Use hasID3v2Tag() to check if the file
* on disk actually has an ID3v2 tag.
*
* \note The Tag <b>is still</b> owned by the MPEG::File and should not be
@ -162,8 +165,8 @@ namespace TagLib {
* if there is no valid APE tag. If \a create is true it will create
* an APE tag if one does not exist and returns a valid pointer.
*
* \note This may return a valid pointer regardless of whether or not the
* file on disk has an ID3v1 tag. Use hasID3v1Tag() to check if the file
* \note This may return a valid pointer regardless of whether or not the
* file on disk has an ID3v1 tag. Use hasID3v1Tag() to check if the file
* on disk actually has an ID3v1 tag.
*
* \note The Tag <b>is still</b> owned by the MPEG::File and should not be
@ -181,10 +184,10 @@ namespace TagLib {
* if there is no valid XiphComment. If \a create is true it will create
* a XiphComment if one does not exist and returns a valid pointer.
*
* \note This may return a valid pointer regardless of whether or not the
* file on disk has a XiphComment. Use hasXiphComment() to check if the
* \note This may return a valid pointer regardless of whether or not the
* file on disk has a XiphComment. Use hasXiphComment() to check if the
* file on disk actually has a XiphComment.
*
*
* \note The Tag <b>is still</b> owned by the FLAC::File and should not be
* deleted by the user. It will be deleted when the file (object) is
* destroyed.
@ -199,6 +202,7 @@ namespace TagLib {
* when
*
* \see ID3v2FrameFactory
* \deprecated This value should be passed in via the constructor
*/
void setID3v2FrameFactory(const ID3v2::FrameFactory *factory);
@ -251,12 +255,10 @@ namespace TagLib {
File(const File &);
File &operator=(const File &);
void read(bool readProperties, AudioProperties::ReadStyle propertiesStyle);
void read(bool readProperties);
void scan();
offset_t findID3v2();
offset_t findID3v1();
ByteVector xiphCommentData() const;
offset_t findPaddingBreak(long nextPageOffset, long targetOffset, bool *isLast);
class FilePrivate;
FilePrivate *d;

View File

@ -38,14 +38,14 @@ public:
length(0),
bitrate(0),
sampleRate(0),
sampleWidth(0),
bitsPerSample(0),
channels(0),
sampleFrames(0) {}
int length;
int bitrate;
int sampleRate;
int sampleWidth;
int bitsPerSample;
int channels;
unsigned long long sampleFrames;
ByteVector signature;
@ -55,8 +55,8 @@ public:
// public members
////////////////////////////////////////////////////////////////////////////////
FLAC::AudioProperties::AudioProperties(const ByteVector &data, offset_t streamLength,
ReadStyle style) :
FLAC::AudioProperties::AudioProperties(const ByteVector &data, offset_t streamLength, ReadStyle) :
TagLib::AudioProperties(),
d(new PropertiesPrivate())
{
read(data, streamLength);
@ -68,6 +68,16 @@ FLAC::AudioProperties::~AudioProperties()
}
int FLAC::AudioProperties::length() const
{
return lengthInSeconds();
}
int FLAC::AudioProperties::lengthInSeconds() const
{
return d->length / 1000;
}
int FLAC::AudioProperties::lengthInMilliseconds() const
{
return d->length;
}
@ -82,9 +92,14 @@ int FLAC::AudioProperties::sampleRate() const
return d->sampleRate;
}
int FLAC::AudioProperties::bitsPerSample() const
{
return d->bitsPerSample;
}
int FLAC::AudioProperties::sampleWidth() const
{
return d->sampleWidth;
return bitsPerSample();
}
int FLAC::AudioProperties::channels() const
@ -130,9 +145,9 @@ void FLAC::AudioProperties::read(const ByteVector &data, offset_t streamLength)
const uint flags = data.toUInt32BE(pos);
pos += 4;
d->sampleRate = flags >> 12;
d->channels = ((flags >> 9) & 7) + 1;
d->sampleWidth = ((flags >> 4) & 31) + 1;
d->sampleRate = flags >> 12;
d->channels = ((flags >> 9) & 7) + 1;
d->bitsPerSample = ((flags >> 4) & 31) + 1;
// The last 4 bits are the most significant 4 bits for the 36 bit
// stream length in samples. (Audio files measured in days)
@ -143,16 +158,12 @@ void FLAC::AudioProperties::read(const ByteVector &data, offset_t streamLength)
d->sampleFrames = (hi << 32) | lo;
if(d->sampleRate > 0)
d->length = static_cast<int>(d->sampleFrames / d->sampleRate);
if(d->sampleFrames > 0 && d->sampleRate > 0) {
const double length = d->sampleFrames * 1000.0 / d->sampleRate;
d->length = static_cast<int>(length + 0.5);
d->bitrate = static_cast<int>(streamLength * 8.0 / length + 0.5);
}
// Uncompressed bitrate:
//d->bitrate = ((d->sampleRate * d->channels) / 1000) * d->sampleWidth;
// Real bitrate:
d->bitrate = d->length > 0 ? static_cast<int>(streamLength * 8L / d->length / 1000) : 0;
d->signature = data.mid(pos, 32);
if(data.size() >= pos + 16)
d->signature = data.mid(pos, 16);
}

View File

@ -46,7 +46,7 @@ namespace TagLib {
{
public:
/*!
* Creates an instance of FLAC::AudioProperties with the data read from
* Creates an instance of FLAC::AudioProperties with the data read from
* the ByteVector \a data.
*/
AudioProperties(const ByteVector &data, offset_t streamLength, ReadStyle style = Average);
@ -56,27 +56,72 @@ namespace TagLib {
*/
virtual ~AudioProperties();
// Reimplementations.
/*!
* Returns the length of the file in seconds. The length is rounded down to
* the nearest whole second.
*
* \note This method is just an alias of lengthInSeconds().
*
* \deprecated
*/
virtual int length() const;
/*!
* Returns the length of the file in seconds. The length is rounded down to
* the nearest whole second.
*
* \see lengthInMilliseconds()
*/
// BIC: make virtual
int lengthInSeconds() const;
/*!
* Returns the length of the file in milliseconds.
*
* \see lengthInSeconds()
*/
// BIC: make virtual
int lengthInMilliseconds() const;
/*!
* Returns the average bit rate of the file in kb/s.
*/
virtual int bitrate() const;
/*!
* Returns the sample rate in Hz.
*/
virtual int sampleRate() const;
/*!
* Returns the number of audio channels.
*/
virtual int channels() const;
/*!
* Returns the number of bits per audio sample as read from the FLAC
* identification header.
*/
int bitsPerSample() const;
/*!
* Returns the sample width as read from the FLAC identification
* header.
*
* \note This method is just an alias of bitsPerSample().
*
* \deprecated
*/
int sampleWidth() const;
/*!
* Return the number of sample frames
* Return the number of sample frames.
*/
unsigned long long sampleFrames() const;
/*!
* Returns the MD5 signature of the uncompressed audio stream as read
* from the stream info header header.
* from the stream info header.
*/
ByteVector signature() const;

View File

@ -65,7 +65,8 @@ public:
// public members
////////////////////////////////////////////////////////////////////////////////
IT::AudioProperties::AudioProperties(AudioProperties::ReadStyle propertiesStyle) :
IT::AudioProperties::AudioProperties(AudioProperties::ReadStyle) :
TagLib::AudioProperties(),
d(new PropertiesPrivate())
{
}
@ -80,6 +81,16 @@ int IT::AudioProperties::length() const
return 0;
}
int IT::AudioProperties::lengthInSeconds() const
{
return 0;
}
int IT::AudioProperties::lengthInMilliseconds() const
{
return 0;
}
int IT::AudioProperties::bitrate() const
{
return 0;

View File

@ -57,10 +57,12 @@ namespace TagLib {
AudioProperties(AudioProperties::ReadStyle propertiesStyle);
virtual ~AudioProperties();
int length() const;
int bitrate() const;
int sampleRate() const;
int channels() const;
int length() const;
int lengthInSeconds() const;
int lengthInMilliseconds() const;
int bitrate() const;
int sampleRate() const;
int channels() const;
ushort lengthInPatterns() const;
bool stereo() const;

View File

@ -41,7 +41,8 @@ public:
// public members
////////////////////////////////////////////////////////////////////////////////
Mod::AudioProperties::AudioProperties(AudioProperties::ReadStyle propertiesStyle) :
Mod::AudioProperties::AudioProperties(AudioProperties::ReadStyle) :
TagLib::AudioProperties(),
d(new PropertiesPrivate)
{
}
@ -56,6 +57,16 @@ int Mod::AudioProperties::length() const
return 0;
}
int Mod::AudioProperties::lengthInSeconds() const
{
return 0;
}
int Mod::AudioProperties::lengthInMilliseconds() const
{
return 0;
}
int Mod::AudioProperties::bitrate() const
{
return 0;

View File

@ -39,12 +39,14 @@ namespace TagLib {
AudioProperties(AudioProperties::ReadStyle propertiesStyle);
virtual ~AudioProperties();
int length() const;
int bitrate() const;
int sampleRate() const;
int channels() const;
int length() const;
int lengthInSeconds() const;
int lengthInMilliseconds() const;
int bitrate() const;
int sampleRate() const;
int channels() const;
uint instrumentCount() const;
uint instrumentCount() const;
uchar lengthInPatterns() const;
private:

View File

@ -97,7 +97,7 @@ namespace TagLib {
* Sets the title to \a title. If \a title is String::null then this
* value will be cleared.
*
* The length limits per file type are (1 characetr = 1 byte):
* The length limits per file type are (1 character = 1 byte):
* Mod 20 characters, S3M 27 characters, IT 25 characters and XM 20
* characters.
*/
@ -126,7 +126,7 @@ namespace TagLib {
* an thus the line length in comments are limited. Too big comments
* will be truncated.
*
* The line length limits per file type are (1 characetr = 1 byte):
* The line length limits per file type are (1 character = 1 byte):
* Mod 22 characters, S3M 27 characters, IT 25 characters and XM 22
* characters.
*/
@ -169,7 +169,7 @@ namespace TagLib {
* Implements the unified property interface -- import function.
* Because of the limitations of the module file tag, any tags besides
* COMMENT, TITLE and, if it is an XM file, TRACKERNAME, will be
* returened. Additionally, if the map contains tags with multiple values,
* returned. Additionally, if the map contains tags with multiple values,
* all but the first will be contained in the returned map of unsupported
* properties.
*/

View File

@ -59,7 +59,7 @@ namespace TagLib {
TypeDateTime = 17, // in UTC, counting seconds since midnight, January 1, 1904; 32 or 64-bits
TypeGenred = 18, // a list of enumerated values
TypeInteger = 21, // a signed big-endian integer with length one of { 1,2,3,4,8 } bytes
TypeRIAAPA = 24, // RIAA parental advisory; { -1=no, 1=yes, 0=unspecified }, 8-bit ingteger
TypeRIAAPA = 24, // RIAA parental advisory; { -1=no, 1=yes, 0=unspecified }, 8-bit integer
TypeUPC = 25, // Universal Product Code, in text UTF-8 format (valid as an ID)
TypeBMP = 27, // Windows bitmap image
TypeUndefined = 255 // undefined

View File

@ -32,48 +32,57 @@
using namespace TagLib;
namespace
{
bool checkValid(const MP4::AtomList &list)
{
for(MP4::AtomList::ConstIterator it = list.begin(); it != list.end(); ++it) {
if((*it)->length == 0)
return false;
if(!checkValid((*it)->children))
return false;
}
return true;
}
}
class MP4::File::FilePrivate
{
public:
FilePrivate() : tag(0), atoms(0), properties(0)
{
}
FilePrivate() :
tag(0),
atoms(0),
properties(0) {}
~FilePrivate()
{
if(atoms) {
delete atoms;
atoms = 0;
}
if(tag) {
delete tag;
tag = 0;
}
if(properties) {
delete properties;
properties = 0;
}
delete atoms;
delete tag;
delete properties;
}
MP4::Tag *tag;
MP4::Atoms *atoms;
MP4::Tag *tag;
MP4::Atoms *atoms;
MP4::AudioProperties *properties;
};
MP4::File::File(FileName file, bool readProperties, AudioProperties::ReadStyle audioPropertiesStyle)
: TagLib::File(file)
MP4::File::File(FileName file, bool readProperties, AudioProperties::ReadStyle) :
TagLib::File(file),
d(new FilePrivate())
{
d = new FilePrivate;
if(isOpen())
read(readProperties, audioPropertiesStyle);
read(readProperties);
}
MP4::File::File(IOStream *stream, bool readProperties, AudioProperties::ReadStyle audioPropertiesStyle)
: TagLib::File(stream)
MP4::File::File(IOStream *stream, bool readProperties, AudioProperties::ReadStyle) :
TagLib::File(stream),
d(new FilePrivate())
{
d = new FilePrivate;
if(isOpen())
read(readProperties, audioPropertiesStyle);
read(readProperties);
}
MP4::File::~File()
@ -93,26 +102,14 @@ MP4::File::audioProperties() const
return d->properties;
}
bool
MP4::File::checkValid(const MP4::AtomList &list)
{
for(uint i = 0; i < list.size(); i++) {
if(list[i]->length == 0)
return false;
if(!checkValid(list[i]->children))
return false;
}
return true;
}
void
MP4::File::read(bool readProperties, AudioProperties::ReadStyle audioPropertiesStyle)
MP4::File::read(bool readProperties)
{
if(!isValid())
return;
d->atoms = new Atoms(this);
if (!checkValid(d->atoms->atoms)) {
if(!checkValid(d->atoms->atoms)) {
setValid(false);
return;
}
@ -126,7 +123,7 @@ MP4::File::read(bool readProperties, AudioProperties::ReadStyle audioPropertiesS
d->tag = new Tag(this, d->atoms);
if(readProperties) {
d->properties = new AudioProperties(this, d->atoms, audioPropertiesStyle);
d->properties = new AudioProperties(this, d->atoms);
}
}

View File

@ -54,7 +54,7 @@ namespace TagLib {
*
* \note In the current implementation, \a propertiesStyle is ignored.
*/
File(FileName file, bool readProperties = true,
File(FileName file, bool readProperties = true,
AudioProperties::ReadStyle audioPropertiesStyle = AudioProperties::Average);
/*!
@ -66,7 +66,7 @@ namespace TagLib {
* \note TagLib will *not* take ownership of the stream, the caller is
* responsible for deleting it after the File object.
*/
File(IOStream *stream, bool readProperties = true,
File(IOStream *stream, bool readProperties = true,
AudioProperties::ReadStyle audioPropertiesStyle = AudioProperties::Average);
/*!
@ -95,13 +95,14 @@ namespace TagLib {
* Save the file.
*
* This returns true if the save was successful.
*
* \warning In the current implementation, it's dangerous to call save()
* repeatedly. At worst it will corrupt the file.
*/
bool save();
private:
void read(bool readProperties, AudioProperties::ReadStyle audioPropertiesStyle);
bool checkValid(const MP4::AtomList &list);
void read(bool readProperties);
class FilePrivate;
FilePrivate *d;

View File

@ -34,14 +34,14 @@ using namespace TagLib;
class MP4::AudioProperties::PropertiesPrivate
{
public:
PropertiesPrivate() :
length(0),
bitrate(0),
sampleRate(0),
channels(0),
bitsPerSample(0),
encrypted(false),
codec(Unknown) {}
PropertiesPrivate() :
length(0),
bitrate(0),
sampleRate(0),
channels(0),
bitsPerSample(0),
encrypted(false),
codec(MP4::AudioProperties::Unknown) {}
int length;
int bitrate;
@ -56,7 +56,8 @@ public:
// public members
////////////////////////////////////////////////////////////////////////////////
MP4::AudioProperties::AudioProperties(File *file, MP4::Atoms *atoms, ReadStyle style) :
MP4::AudioProperties::AudioProperties(File *file, MP4::Atoms *atoms, ReadStyle) :
TagLib::AudioProperties(),
d(new PropertiesPrivate())
{
read(file, atoms);
@ -81,6 +82,18 @@ MP4::AudioProperties::sampleRate() const
int
MP4::AudioProperties::length() const
{
return lengthInSeconds();
}
int
MP4::AudioProperties::lengthInSeconds() const
{
return d->length / 1000;
}
int
MP4::AudioProperties::lengthInMilliseconds() const
{
return d->length;
}
@ -103,7 +116,7 @@ MP4::AudioProperties::isEncrypted() const
return d->encrypted;
}
MP4::AudioProperties::Codec
MP4::AudioProperties::Codec
MP4::AudioProperties::codec() const
{
return d->codec;
@ -133,7 +146,7 @@ MP4::AudioProperties::toString() const
// private members
////////////////////////////////////////////////////////////////////////////////
void
void
MP4::AudioProperties::read(File *file, Atoms *atoms)
{
MP4::Atom *moov = atoms->find("moov");
@ -145,22 +158,22 @@ MP4::AudioProperties::read(File *file, Atoms *atoms)
MP4::Atom *trak = 0;
ByteVector data;
MP4::AtomList trakList = moov->findall("trak");
for (unsigned int i = 0; i < trakList.size(); i++) {
trak = trakList[i];
const MP4::AtomList trakList = moov->findall("trak");
for(MP4::AtomList::ConstIterator it = trakList.begin(); it != trakList.end(); ++it) {
trak = *it;
MP4::Atom *hdlr = trak->find("mdia", "hdlr");
if(!hdlr) {
debug("MP4: Atom 'trak.mdia.hdlr' not found");
return;
}
file->seek(hdlr->offset);
data = file->readBlock(hdlr->length);
if(data.mid(16, 4) == "soun") {
data = file->readBlock(static_cast<size_t>(hdlr->length));
if(data.containsAt("soun", 16)) {
break;
}
trak = 0;
}
if (!trak) {
if(!trak) {
debug("MP4: No audio tracks");
return;
}
@ -172,26 +185,29 @@ MP4::AudioProperties::read(File *file, Atoms *atoms)
}
file->seek(mdhd->offset);
data = file->readBlock(mdhd->length);
uint version = data[8];
data = file->readBlock(static_cast<size_t>(mdhd->length));
const uint version = data[8];
long long unit;
long long length;
if(version == 1) {
if (data.size() < 36 + 8) {
if(data.size() < 36 + 8) {
debug("MP4: Atom 'trak.mdia.mdhd' is smaller than expected");
return;
}
const long long unit = data.toInt64BE(28);
const long long length = data.toInt64BE(36);
d->length = unit ? int(length / unit) : 0;
unit = data.toInt64BE(28);
length = data.toInt64BE(36);
}
else {
if (data.size() < 24 + 4) {
if(data.size() < 24 + 4) {
debug("MP4: Atom 'trak.mdia.mdhd' is smaller than expected");
return;
}
const unsigned int unit = data.toUInt32BE(20);
const unsigned int length = data.toUInt32BE(24);
d->length = unit ? length / unit : 0;
unit = data.toUInt32BE(20);
length = data.toUInt32BE(24);
}
if(unit > 0 && length > 0)
d->length = static_cast<int>(length * 1000.0 / unit);
MP4::Atom *atom = trak->find("mdia", "minf", "stbl", "stsd");
if(!atom) {
@ -199,34 +215,34 @@ MP4::AudioProperties::read(File *file, Atoms *atoms)
}
file->seek(atom->offset);
data = file->readBlock(atom->length);
if(data.mid(20, 4) == "mp4a") {
d->codec = AAC;
d->channels = data.toInt16BE(40);
d->bitsPerSample = data.toInt16BE(42);
data = file->readBlock(static_cast<size_t>(atom->length));
if(data.containsAt("mp4a", 20)) {
d->codec = AAC;
d->channels = data.toUInt16BE(40);
d->bitsPerSample = data.toUInt16BE(42);
d->sampleRate = data.toUInt32BE(46);
if(data.mid(56, 4) == "esds" && data[64] == 0x03) {
if(data.containsAt("esds", 56) && data[64] == 0x03) {
uint pos = 65;
if(data.mid(pos, 3) == "\x80\x80\x80") {
if(data.containsAt("\x80\x80\x80", pos)) {
pos += 3;
}
pos += 4;
if(data[pos] == 0x04) {
pos += 1;
if(data.mid(pos, 3) == "\x80\x80\x80") {
if(data.containsAt("\x80\x80\x80", pos)) {
pos += 3;
}
pos += 10;
d->bitrate = (data.toUInt32BE(pos) + 500) / 1000;
d->bitrate = static_cast<int>((data.toUInt32BE(pos) + 500) / 1000.0 + 0.5);
}
}
}
else if (data.mid(20, 4) == "alac") {
d->codec = ALAC;
if (atom->length == 88 && data.mid(56, 4) == "alac") {
else if(data.containsAt("alac", 20)) {
if(atom->length == 88 && data.containsAt("alac", 56)) {
d->codec = ALAC;
d->bitsPerSample = data.at(69);
d->channels = data.at(73);
d->bitrate = data.toUInt32BE(80) / 1000;
d->bitrate = static_cast<int>(data.toUInt32BE(80) / 1000.0 + 0.5);
d->sampleRate = data.toUInt32BE(84);
}
}

View File

@ -49,14 +49,61 @@ namespace TagLib {
AudioProperties(File *file, Atoms *atoms, ReadStyle style = Average);
virtual ~AudioProperties();
/*!
* Returns the length of the file in seconds. The length is rounded down to
* the nearest whole second.
*
* \note This method is just an alias of lengthInSeconds().
*
* \deprecated
*/
virtual int length() const;
/*!
* Returns the length of the file in seconds. The length is rounded down to
* the nearest whole second.
*
* \see lengthInMilliseconds()
*/
// BIC: make virtual
int lengthInSeconds() const;
/*!
* Returns the length of the file in milliseconds.
*
* \see lengthInSeconds()
*/
// BIC: make virtual
int lengthInMilliseconds() const;
/*!
* Returns the average bit rate of the file in kb/s.
*/
virtual int bitrate() const;
/*!
* Returns the sample rate in Hz.
*/
virtual int sampleRate() const;
/*!
* Returns the number of audio channels.
*/
virtual int channels() const;
/*!
* Returns the number of bits per audio sample.
*/
virtual int bitsPerSample() const;
/*!
* Returns whether or not the file is encrypted.
*/
bool isEncrypted() const;
//! Audio codec used in the MP4 file
/*!
* Returns the codec used in the file.
*/
Codec codec() const;
String toString() const;

View File

@ -39,7 +39,7 @@ public:
~TagPrivate() {}
TagLib::File *file;
Atoms *atoms;
ItemListMap items;
ItemMap items;
};
MP4::Tag::Tag()
@ -63,36 +63,36 @@ MP4::Tag::Tag(TagLib::File *file, MP4::Atoms *atoms)
MP4::Atom *atom = ilst->children[i];
file->seek(atom->offset + 8);
if(atom->name == "----") {
parseFreeForm(atom, file);
parseFreeForm(atom);
}
else if(atom->name == "trkn" || atom->name == "disk") {
parseIntPair(atom, file);
parseIntPair(atom);
}
else if(atom->name == "cpil" || atom->name == "pgap" || atom->name == "pcst" ||
atom->name == "hdvd") {
parseBool(atom, file);
parseBool(atom);
}
else if(atom->name == "tmpo") {
parseInt(atom, file);
parseInt(atom);
}
else if(atom->name == "tvsn" || atom->name == "tves" || atom->name == "cnID" ||
atom->name == "sfID" || atom->name == "atID" || atom->name == "geID") {
parseUInt(atom, file);
parseUInt(atom);
}
else if(atom->name == "plID") {
parseLongLong(atom, file);
parseLongLong(atom);
}
else if(atom->name == "stik" || atom->name == "rtng" || atom->name == "akID") {
parseByte(atom, file);
parseByte(atom);
}
else if(atom->name == "gnre") {
parseGnre(atom, file);
parseGnre(atom);
}
else if(atom->name == "covr") {
parseCovr(atom, file);
parseCovr(atom);
}
else {
parseText(atom, file);
parseText(atom);
}
}
}
@ -103,16 +103,21 @@ MP4::Tag::~Tag()
}
MP4::AtomDataList
MP4::Tag::parseData2(MP4::Atom *atom, TagLib::File *file, int expectedFlags, bool freeForm)
MP4::Tag::parseData2(const MP4::Atom *atom, int expectedFlags, bool freeForm)
{
AtomDataList result;
ByteVector data = file->readBlock(atom->length - 8);
ByteVector data = d->file->readBlock(static_cast<size_t>(atom->length - 8));
int i = 0;
size_t pos = 0;
while(pos < data.size()) {
const int length = data.toUInt32BE(pos);
const int length = static_cast<int>(data.toUInt32BE(pos));
if(length < 12) {
debug("MP4: Too short atom");
return result;
}
const ByteVector name = data.mid(pos + 4, 4);
const int flags = data.toUInt32BE(pos + 8);
const int flags = static_cast<int>(data.toUInt32BE(pos + 8));
if(freeForm && i < 2) {
if(i == 0 && name != "mean") {
debug("MP4: Unexpected atom \"" + name + "\", expecting \"mean\"");
@ -140,9 +145,9 @@ MP4::Tag::parseData2(MP4::Atom *atom, TagLib::File *file, int expectedFlags, boo
}
ByteVectorList
MP4::Tag::parseData(MP4::Atom *atom, TagLib::File *file, int expectedFlags, bool freeForm)
MP4::Tag::parseData(const MP4::Atom *atom, int expectedFlags, bool freeForm)
{
AtomDataList data = parseData2(atom, file, expectedFlags, freeForm);
AtomDataList data = parseData2(atom, expectedFlags, freeForm);
ByteVectorList result;
for(uint i = 0; i < data.size(); i++) {
result.append(data[i].data);
@ -151,45 +156,45 @@ MP4::Tag::parseData(MP4::Atom *atom, TagLib::File *file, int expectedFlags, bool
}
void
MP4::Tag::parseInt(MP4::Atom *atom, TagLib::File *file)
MP4::Tag::parseInt(const MP4::Atom *atom)
{
ByteVectorList data = parseData(atom, file);
ByteVectorList data = parseData(atom);
if(data.size()) {
d->items.insert(atom->name, (int)data[0].toInt16BE(0));
}
}
void
MP4::Tag::parseUInt(MP4::Atom *atom, TagLib::File *file)
MP4::Tag::parseUInt(const MP4::Atom *atom)
{
ByteVectorList data = parseData(atom, file);
ByteVectorList data = parseData(atom);
if(data.size()) {
d->items.insert(atom->name, data[0].toUInt32BE(0));
}
}
void
MP4::Tag::parseLongLong(MP4::Atom *atom, TagLib::File *file)
MP4::Tag::parseLongLong(const MP4::Atom *atom)
{
ByteVectorList data = parseData(atom, file);
ByteVectorList data = parseData(atom);
if(data.size()) {
d->items.insert(atom->name, data[0].toInt64BE(0));
}
}
void
MP4::Tag::parseByte(MP4::Atom *atom, TagLib::File *file)
MP4::Tag::parseByte(const MP4::Atom *atom)
{
ByteVectorList data = parseData(atom, file);
ByteVectorList data = parseData(atom);
if(data.size()) {
addItem(atom->name, (uchar)data[0].at(0));
}
}
void
MP4::Tag::parseGnre(MP4::Atom *atom, TagLib::File *file)
MP4::Tag::parseGnre(const MP4::Atom *atom)
{
ByteVectorList data = parseData(atom, file);
ByteVectorList data = parseData(atom);
if(data.size()) {
const int idx = (int)data[0].toInt16BE(0);
if(!d->items.contains("\251gen") && idx > 0) {
@ -199,9 +204,9 @@ MP4::Tag::parseGnre(MP4::Atom *atom, TagLib::File *file)
}
void
MP4::Tag::parseIntPair(MP4::Atom *atom, TagLib::File *file)
MP4::Tag::parseIntPair(const MP4::Atom *atom)
{
ByteVectorList data = parseData(atom, file);
ByteVectorList data = parseData(atom);
if(data.size()) {
const int a = data[0].toInt16BE(2);
const int b = data[0].toInt16BE(4);
@ -210,9 +215,9 @@ MP4::Tag::parseIntPair(MP4::Atom *atom, TagLib::File *file)
}
void
MP4::Tag::parseBool(MP4::Atom *atom, TagLib::File *file)
MP4::Tag::parseBool(const MP4::Atom *atom)
{
ByteVectorList data = parseData(atom, file);
ByteVectorList data = parseData(atom);
if(data.size()) {
bool value = data[0].size() ? data[0][0] != '\0' : false;
addItem(atom->name, value);
@ -220,9 +225,9 @@ MP4::Tag::parseBool(MP4::Atom *atom, TagLib::File *file)
}
void
MP4::Tag::parseText(MP4::Atom *atom, TagLib::File *file, int expectedFlags)
MP4::Tag::parseText(const MP4::Atom *atom, int expectedFlags)
{
ByteVectorList data = parseData(atom, file, expectedFlags);
ByteVectorList data = parseData(atom, expectedFlags);
if(data.size()) {
StringList value;
for(unsigned int i = 0; i < data.size(); i++) {
@ -233,9 +238,9 @@ MP4::Tag::parseText(MP4::Atom *atom, TagLib::File *file, int expectedFlags)
}
void
MP4::Tag::parseFreeForm(MP4::Atom *atom, TagLib::File *file)
MP4::Tag::parseFreeForm(const MP4::Atom *atom)
{
AtomDataList data = parseData2(atom, file, -1, true);
AtomDataList data = parseData2(atom, -1, true);
if(data.size() > 2) {
String name = "----:" + String(data[0].data, String::UTF8) + ':' + String(data[1].data, String::UTF8);
AtomDataType type = data[2].type;
@ -267,20 +272,26 @@ MP4::Tag::parseFreeForm(MP4::Atom *atom, TagLib::File *file)
}
void
MP4::Tag::parseCovr(MP4::Atom *atom, TagLib::File *file)
MP4::Tag::parseCovr(const MP4::Atom *atom)
{
MP4::CoverArtList value;
ByteVector data = file->readBlock(atom->length - 8);
size_t pos = 0;
ByteVector data = d->file->readBlock(static_cast<size_t>(atom->length - 8));
unsigned int pos = 0;
while(pos < data.size()) {
const int length = data.toUInt32BE(pos);
const int length = static_cast<int>(data.toUInt32BE(pos));
if(length < 12) {
debug("MP4: Too short atom");
break;;
}
const ByteVector name = data.mid(pos + 4, 4);
const int flags = data.toUInt32BE(pos + 8);
const int flags = static_cast<int>(data.toUInt32BE(pos + 8));
if(name != "data") {
debug("MP4: Unexpected atom \"" + name + "\", expecting \"data\"");
break;
}
if(flags == TypeJPEG || flags == TypePNG || flags == TypeBMP || flags == TypeGIF || flags == TypeImplicit) {
if(flags == TypeJPEG || flags == TypePNG || flags == TypeBMP ||
flags == TypeGIF || flags == TypeImplicit) {
value.append(MP4::CoverArt(MP4::CoverArt::Format(flags),
data.mid(pos + 16, length - 16)));
}
@ -294,22 +305,22 @@ MP4::Tag::parseCovr(MP4::Atom *atom, TagLib::File *file)
}
ByteVector
MP4::Tag::padIlst(const ByteVector &data, int length)
MP4::Tag::padIlst(const ByteVector &data, int length) const
{
if (length == -1) {
if(length == -1) {
length = static_cast<int>(((data.size() + 1023) & ~1023) - data.size());
}
return renderAtom("free", ByteVector(length, '\1'));
}
ByteVector
MP4::Tag::renderAtom(const ByteVector &name, const ByteVector &data)
MP4::Tag::renderAtom(const ByteVector &name, const ByteVector &data) const
{
return ByteVector::fromUInt32BE(data.size() + 8) + name + data;
}
ByteVector
MP4::Tag::renderData(const ByteVector &name, int flags, const ByteVectorList &data)
MP4::Tag::renderData(const ByteVector &name, int flags, const ByteVectorList &data) const
{
ByteVector result;
for(unsigned int i = 0; i < data.size(); i++) {
@ -319,7 +330,7 @@ MP4::Tag::renderData(const ByteVector &name, int flags, const ByteVectorList &da
}
ByteVector
MP4::Tag::renderBool(const ByteVector &name, MP4::Item &item)
MP4::Tag::renderBool(const ByteVector &name, const MP4::Item &item) const
{
ByteVectorList data;
data.append(ByteVector(1, item.toBool() ? '\1' : '\0'));
@ -327,7 +338,7 @@ MP4::Tag::renderBool(const ByteVector &name, MP4::Item &item)
}
ByteVector
MP4::Tag::renderInt(const ByteVector &name, MP4::Item &item)
MP4::Tag::renderInt(const ByteVector &name, const MP4::Item &item) const
{
ByteVectorList data;
data.append(ByteVector::fromUInt16BE(item.toInt()));
@ -335,7 +346,7 @@ MP4::Tag::renderInt(const ByteVector &name, MP4::Item &item)
}
ByteVector
MP4::Tag::renderUInt(const ByteVector &name, MP4::Item &item)
MP4::Tag::renderUInt(const ByteVector &name, const MP4::Item &item) const
{
ByteVectorList data;
data.append(ByteVector::fromUInt32BE(item.toUInt()));
@ -343,7 +354,7 @@ MP4::Tag::renderUInt(const ByteVector &name, MP4::Item &item)
}
ByteVector
MP4::Tag::renderLongLong(const ByteVector &name, MP4::Item &item)
MP4::Tag::renderLongLong(const ByteVector &name, const MP4::Item &item) const
{
ByteVectorList data;
data.append(ByteVector::fromUInt64BE(item.toLongLong()));
@ -351,7 +362,7 @@ MP4::Tag::renderLongLong(const ByteVector &name, MP4::Item &item)
}
ByteVector
MP4::Tag::renderByte(const ByteVector &name, MP4::Item &item)
MP4::Tag::renderByte(const ByteVector &name, const MP4::Item &item) const
{
ByteVectorList data;
data.append(ByteVector(1, item.toByte()));
@ -359,7 +370,7 @@ MP4::Tag::renderByte(const ByteVector &name, MP4::Item &item)
}
ByteVector
MP4::Tag::renderIntPair(const ByteVector &name, MP4::Item &item)
MP4::Tag::renderIntPair(const ByteVector &name, const MP4::Item &item) const
{
ByteVectorList data;
data.append(ByteVector(2, '\0') +
@ -370,7 +381,7 @@ MP4::Tag::renderIntPair(const ByteVector &name, MP4::Item &item)
}
ByteVector
MP4::Tag::renderIntPairNoTrailing(const ByteVector &name, MP4::Item &item)
MP4::Tag::renderIntPairNoTrailing(const ByteVector &name, const MP4::Item &item) const
{
ByteVectorList data;
data.append(ByteVector(2, '\0') +
@ -380,7 +391,7 @@ MP4::Tag::renderIntPairNoTrailing(const ByteVector &name, MP4::Item &item)
}
ByteVector
MP4::Tag::renderText(const ByteVector &name, MP4::Item &item, int flags)
MP4::Tag::renderText(const ByteVector &name, const MP4::Item &item, int flags) const
{
ByteVectorList data;
StringList value = item.toStringList();
@ -391,7 +402,7 @@ MP4::Tag::renderText(const ByteVector &name, MP4::Item &item, int flags)
}
ByteVector
MP4::Tag::renderCovr(const ByteVector &name, MP4::Item &item)
MP4::Tag::renderCovr(const ByteVector &name, const MP4::Item &item) const
{
ByteVector data;
MP4::CoverArtList value = item.toCoverArtList();
@ -403,7 +414,7 @@ MP4::Tag::renderCovr(const ByteVector &name, MP4::Item &item)
}
ByteVector
MP4::Tag::renderFreeForm(const String &name, MP4::Item &item)
MP4::Tag::renderFreeForm(const String &name, const MP4::Item &item) const
{
StringList header = StringList::split(name, ":");
if (header.size() != 3) {
@ -441,38 +452,38 @@ bool
MP4::Tag::save()
{
ByteVector data;
for(MP4::ItemListMap::Iterator i = d->items.begin(); i != d->items.end(); i++) {
const String name = i->first;
for(MP4::ItemMap::ConstIterator it = d->items.begin(); it != d->items.end(); ++it) {
const String name = it->first;
if(name.startsWith("----")) {
data.append(renderFreeForm(name, i->second));
data.append(renderFreeForm(name, it->second));
}
else if(name == "trkn") {
data.append(renderIntPair(name.data(String::Latin1), i->second));
data.append(renderIntPair(name.data(String::Latin1), it->second));
}
else if(name == "disk") {
data.append(renderIntPairNoTrailing(name.data(String::Latin1), i->second));
data.append(renderIntPairNoTrailing(name.data(String::Latin1), it->second));
}
else if(name == "cpil" || name == "pgap" || name == "pcst" || name == "hdvd") {
data.append(renderBool(name.data(String::Latin1), i->second));
data.append(renderBool(name.data(String::Latin1), it->second));
}
else if(name == "tmpo") {
data.append(renderInt(name.data(String::Latin1), i->second));
data.append(renderInt(name.data(String::Latin1), it->second));
}
else if(name == "tvsn" || name == "tves" || name == "cnID" ||
name == "sfID" || name == "atID" || name == "geID") {
data.append(renderUInt(name.data(String::Latin1), i->second));
data.append(renderUInt(name.data(String::Latin1), it->second));
}
else if(name == "plID") {
data.append(renderLongLong(name.data(String::Latin1), i->second));
data.append(renderLongLong(name.data(String::Latin1), it->second));
}
else if(name == "stik" || name == "rtng" || name == "akID") {
data.append(renderByte(name.data(String::Latin1), i->second));
data.append(renderByte(name.data(String::Latin1), it->second));
}
else if(name == "covr") {
data.append(renderCovr(name.data(String::Latin1), i->second));
data.append(renderCovr(name.data(String::Latin1), it->second));
}
else if(name.size() == 4){
data.append(renderText(name.data(String::Latin1), i->second));
data.append(renderText(name.data(String::Latin1), it->second));
}
else {
debug("MP4: Unknown item name \"" + name + "\"");
@ -492,7 +503,7 @@ MP4::Tag::save()
}
void
MP4::Tag::updateParents(AtomList &path, long delta, int ignore)
MP4::Tag::updateParents(const AtomList &path, long delta, int ignore)
{
for(size_t i = 0; i < path.size() - ignore; i++) {
d->file->seek(path[i]->offset);
@ -525,7 +536,7 @@ MP4::Tag::updateOffsets(long delta, offset_t offset)
atom->offset += delta;
}
d->file->seek(atom->offset + 12);
ByteVector data = d->file->readBlock(atom->length - 12);
ByteVector data = d->file->readBlock(static_cast<size_t>(atom->length - 12));
unsigned int count = data.toUInt32BE(0);
d->file->seek(atom->offset + 16);
size_t pos = 4;
@ -546,7 +557,7 @@ MP4::Tag::updateOffsets(long delta, offset_t offset)
atom->offset += delta;
}
d->file->seek(atom->offset + 12);
ByteVector data = d->file->readBlock(atom->length - 12);
ByteVector data = d->file->readBlock(static_cast<size_t>(atom->length - 12));
unsigned int count = data.toUInt32BE(0);
d->file->seek(atom->offset + 16);
size_t pos = 4;
@ -570,7 +581,7 @@ MP4::Tag::updateOffsets(long delta, offset_t offset)
atom->offset += delta;
}
d->file->seek(atom->offset + 9);
ByteVector data = d->file->readBlock(atom->length - 9);
ByteVector data = d->file->readBlock(static_cast<size_t>(atom->length - 9));
const uint flags = data.toUInt24BE(0);
if(flags & 1) {
long long o = data.toInt64BE(7);
@ -585,10 +596,11 @@ MP4::Tag::updateOffsets(long delta, offset_t offset)
}
void
MP4::Tag::saveNew(ByteVector &data)
MP4::Tag::saveNew(ByteVector data)
{
data = renderAtom("meta", TagLib::ByteVector(4, '\0') +
renderAtom("hdlr", TagLib::ByteVector(8, '\0') + TagLib::ByteVector("mdirappl") + TagLib::ByteVector(9, '\0')) +
data = renderAtom("meta", ByteVector(4, '\0') +
renderAtom("hdlr", ByteVector(8, '\0') + ByteVector("mdirappl") +
ByteVector(9, '\0')) +
data + padIlst(data));
AtomList path = d->atoms->path("moov", "udta");
@ -605,11 +617,11 @@ MP4::Tag::saveNew(ByteVector &data)
}
void
MP4::Tag::saveExisting(ByteVector &data, AtomList &path)
MP4::Tag::saveExisting(ByteVector data, const AtomList &path)
{
MP4::Atom *ilst = path[path.size() - 1];
offset_t offset = ilst->offset;
long length = ilst->length;
offset_t length = ilst->length;
MP4::Atom *meta = path[path.size() - 2];
AtomList::ConstIterator index = meta->children.find(ilst);
@ -634,17 +646,17 @@ MP4::Tag::saveExisting(ByteVector &data, AtomList &path)
}
}
long delta = static_cast<long>(data.size()) - length;
long delta = static_cast<long>(data.size() - length);
if(delta > 0 || (delta < 0 && delta > -8)) {
data.append(padIlst(data));
delta = static_cast<long>(data.size()) - length;
delta = static_cast<long>(data.size() - length);
}
else if(delta < 0) {
data.append(padIlst(data, -delta - 8));
data.append(padIlst(data, 0 - delta - 8));
delta = 0;
}
d->file->insert(data, offset, length);
d->file->insert(data, offset, static_cast<size_t>(length));
if(delta) {
updateParents(path, delta, 1);
@ -750,12 +762,41 @@ MP4::Tag::setTrack(uint value)
d->items["trkn"] = MP4::Item(value, 0);
}
MP4::ItemListMap &
MP4::Tag::itemListMap()
bool MP4::Tag::isEmpty() const
{
return d->items.isEmpty();
}
MP4::ItemMap &MP4::Tag::itemListMap()
{
return d->items;
}
const MP4::ItemMap &MP4::Tag::itemMap() const
{
return d->items;
}
MP4::Item MP4::Tag::item(const String &key) const
{
return d->items[key];
}
void MP4::Tag::setItem(const String &key, const Item &value)
{
d->items[key] = value;
}
void MP4::Tag::removeItem(const String &key)
{
d->items.erase(key);
}
bool MP4::Tag::contains(const String &key) const
{
return d->items.contains(key);
}
String
MP4::Tag::toString() const
{
@ -766,58 +807,56 @@ MP4::Tag::toString() const
return desc.toString("\n");
}
namespace
{
const char *keyTranslation[][2] = {
{ "\251nam", "TITLE" },
{ "\251ART", "ARTIST" },
{ "\251alb", "ALBUM" },
{ "\251cmt", "COMMENT" },
{ "\251gen", "GENRE" },
{ "\251day", "DATE" },
{ "\251wrt", "COMPOSER" },
{ "\251grp", "GROUPING" },
{ "aART", "ALBUMARTIST" },
{ "trkn", "TRACKNUMBER" },
{ "disk", "DISCNUMBER" },
{ "cpil", "COMPILATION" },
{ "tmpo", "BPM" },
{ "cprt", "COPYRIGHT" },
{ "\251lyr", "LYRICS" },
{ "\251too", "ENCODEDBY" },
{ "soal", "ALBUMSORT" },
{ "soaa", "ALBUMARTISTSORT" },
{ "soar", "ARTISTSORT" },
{ "sonm", "TITLESORT" },
{ "soco", "COMPOSERSORT" },
{ "sosn", "SHOWSORT" },
{ "----:com.apple.iTunes:MusicBrainz Track Id", "MUSICBRAINZ_TRACKID" },
{ "----:com.apple.iTunes:MusicBrainz Artist Id", "MUSICBRAINZ_ARTISTID" },
{ "----:com.apple.iTunes:MusicBrainz Album Id", "MUSICBRAINZ_ALBUMID" },
{ "----:com.apple.iTunes:MusicBrainz Album Artist Id", "MUSICBRAINZ_ALBUMARTISTID" },
{ "----:com.apple.iTunes:MusicBrainz Release Group Id", "MUSICBRAINZ_RELEASEGROUPID" },
{ "----:com.apple.iTunes:MusicBrainz Work Id", "MUSICBRAINZ_WORKID" },
{ "----:com.apple.iTunes:ASIN", "ASIN" },
{ "----:com.apple.iTunes:LABEL", "LABEL" },
{ "----:com.apple.iTunes:LYRICIST", "LYRICIST" },
{ "----:com.apple.iTunes:CONDUCTOR", "CONDUCTOR" },
{ "----:com.apple.iTunes:REMIXER", "REMIXER" },
{ "----:com.apple.iTunes:ENGINEER", "ENGINEER" },
{ "----:com.apple.iTunes:PRODUCER", "PRODUCER" },
{ "----:com.apple.iTunes:DJMIXER", "DJMIXER" },
{ "----:com.apple.iTunes:MIXER", "MIXER" },
{ "----:com.apple.iTunes:SUBTITLE", "SUBTITLE" },
{ "----:com.apple.iTunes:DISCSUBTITLE", "DISCSUBTITLE" },
{ "----:com.apple.iTunes:MOOD", "MOOD" },
{ "----:com.apple.iTunes:ISRC", "ISRC" },
{ "----:com.apple.iTunes:CATALOGNUMBER", "CATALOGNUMBER" },
{ "----:com.apple.iTunes:BARCODE", "BARCODE" },
{ "----:com.apple.iTunes:SCRIPT", "SCRIPT" },
{ "----:com.apple.iTunes:LANGUAGE", "LANGUAGE" },
{ "----:com.apple.iTunes:LICENSE", "LICENSE" },
{ "----:com.apple.iTunes:MEDIA", "MEDIA" },
};
}
static const char *keyTranslation[][2] = {
{ "\251nam", "TITLE" },
{ "\251ART", "ARTIST" },
{ "\251alb", "ALBUM" },
{ "\251cmt", "COMMENT" },
{ "\251gen", "GENRE" },
{ "\251day", "DATE" },
{ "\251wrt", "COMPOSER" },
{ "\251grp", "GROUPING" },
{ "aART", "ALBUMARTIST" },
{ "trkn", "TRACKNUMBER" },
{ "disk", "DISCNUMBER" },
{ "cpil", "COMPILATION" },
{ "tmpo", "BPM" },
{ "cprt", "COPYRIGHT" },
{ "\251lyr", "LYRICS" },
{ "\251too", "ENCODEDBY" },
{ "soal", "ALBUMSORT" },
{ "soaa", "ALBUMARTISTSORT" },
{ "soar", "ARTISTSORT" },
{ "sonm", "TITLESORT" },
{ "soco", "COMPOSERSORT" },
{ "sosn", "SHOWSORT" },
{ "----:com.apple.iTunes:MusicBrainz Track Id", "MUSICBRAINZ_TRACKID" },
{ "----:com.apple.iTunes:MusicBrainz Artist Id", "MUSICBRAINZ_ARTISTID" },
{ "----:com.apple.iTunes:MusicBrainz Album Id", "MUSICBRAINZ_ALBUMID" },
{ "----:com.apple.iTunes:MusicBrainz Album Artist Id", "MUSICBRAINZ_ALBUMARTISTID" },
{ "----:com.apple.iTunes:MusicBrainz Release Group Id", "MUSICBRAINZ_RELEASEGROUPID" },
{ "----:com.apple.iTunes:MusicBrainz Work Id", "MUSICBRAINZ_WORKID" },
{ "----:com.apple.iTunes:ASIN", "ASIN" },
{ "----:com.apple.iTunes:LABEL", "LABEL" },
{ "----:com.apple.iTunes:LYRICIST", "LYRICIST" },
{ "----:com.apple.iTunes:CONDUCTOR", "CONDUCTOR" },
{ "----:com.apple.iTunes:REMIXER", "REMIXER" },
{ "----:com.apple.iTunes:ENGINEER", "ENGINEER" },
{ "----:com.apple.iTunes:PRODUCER", "PRODUCER" },
{ "----:com.apple.iTunes:DJMIXER", "DJMIXER" },
{ "----:com.apple.iTunes:MIXER", "MIXER" },
{ "----:com.apple.iTunes:SUBTITLE", "SUBTITLE" },
{ "----:com.apple.iTunes:DISCSUBTITLE", "DISCSUBTITLE" },
{ "----:com.apple.iTunes:MOOD", "MOOD" },
{ "----:com.apple.iTunes:ISRC", "ISRC" },
{ "----:com.apple.iTunes:CATALOGNUMBER", "CATALOGNUMBER" },
{ "----:com.apple.iTunes:BARCODE", "BARCODE" },
{ "----:com.apple.iTunes:SCRIPT", "SCRIPT" },
{ "----:com.apple.iTunes:LANGUAGE", "LANGUAGE" },
{ "----:com.apple.iTunes:LICENSE", "LICENSE" },
{ "----:com.apple.iTunes:MEDIA", "MEDIA" },
};
PropertyMap MP4::Tag::properties() const
{
@ -830,7 +869,7 @@ PropertyMap MP4::Tag::properties() const
}
PropertyMap props;
MP4::ItemListMap::ConstIterator it = d->items.begin();
MP4::ItemMap::ConstIterator it = d->items.begin();
for(; it != d->items.end(); ++it) {
if(keyMap.contains(it->first)) {
String key = keyMap[it->first];

View File

@ -39,7 +39,11 @@ namespace TagLib {
namespace MP4 {
/*!
* \deprecated
*/
typedef TagLib::Map<String, Item> ItemListMap;
typedef TagLib::Map<String, Item> ItemMap;
class TAGLIB_EXPORT Tag: public TagLib::Tag
{
@ -65,7 +69,38 @@ namespace TagLib {
void setYear(uint value);
void setTrack(uint value);
ItemListMap &itemListMap();
virtual bool isEmpty() const;
/*!
* \deprecated Use the item() and setItem() API instead
*/
ItemMap &itemListMap();
/*!
* Returns a string-keyed map of the MP4::Items for this tag.
*/
const ItemMap &itemMap() const;
/*!
* \return The item, if any, corresponding to \a key.
*/
Item item(const String &key) const;
/*!
* Sets the value of \a key to \a value, overwriting any previous value.
*/
void setItem(const String &key, const Item &value);
/*!
* Removes the entry with \a key from the tag, or does nothing if it does
* not exist.
*/
void removeItem(const String &key);
/*!
* \return True if the tag contains an entry for \a key.
*/
bool contains(const String &key) const;
String toString() const;
@ -74,38 +109,42 @@ namespace TagLib {
PropertyMap setProperties(const PropertyMap &properties);
private:
AtomDataList parseData2(Atom *atom, TagLib::File *file, int expectedFlags = -1, bool freeForm = false);
TagLib::ByteVectorList parseData(Atom *atom, TagLib::File *file, int expectedFlags = -1, bool freeForm = false);
void parseText(Atom *atom, TagLib::File *file, int expectedFlags = 1);
void parseFreeForm(Atom *atom, TagLib::File *file);
void parseInt(Atom *atom, TagLib::File *file);
void parseByte(Atom *atom, TagLib::File *file);
void parseUInt(Atom *atom, TagLib::File *file);
void parseLongLong(Atom *atom, TagLib::File *file);
void parseGnre(Atom *atom, TagLib::File *file);
void parseIntPair(Atom *atom, TagLib::File *file);
void parseBool(Atom *atom, TagLib::File *file);
void parseCovr(Atom *atom, TagLib::File *file);
AtomDataList parseData2(const Atom *atom, int expectedFlags = -1,
bool freeForm = false);
ByteVectorList parseData(const Atom *atom, int expectedFlags = -1,
bool freeForm = false);
void parseText(const Atom *atom, int expectedFlags = 1);
void parseFreeForm(const Atom *atom);
void parseInt(const Atom *atom);
void parseByte(const Atom *atom);
void parseUInt(const Atom *atom);
void parseLongLong(const Atom *atom);
void parseGnre(const Atom *atom);
void parseIntPair(const Atom *atom);
void parseBool(const Atom *atom);
void parseCovr(const Atom *atom);
TagLib::ByteVector padIlst(const ByteVector &data, int length = -1);
TagLib::ByteVector renderAtom(const ByteVector &name, const TagLib::ByteVector &data);
TagLib::ByteVector renderData(const ByteVector &name, int flags, const TagLib::ByteVectorList &data);
TagLib::ByteVector renderText(const ByteVector &name, Item &item, int flags = TypeUTF8);
TagLib::ByteVector renderFreeForm(const String &name, Item &item);
TagLib::ByteVector renderBool(const ByteVector &name, Item &item);
TagLib::ByteVector renderInt(const ByteVector &name, Item &item);
TagLib::ByteVector renderByte(const ByteVector &name, Item &item);
TagLib::ByteVector renderUInt(const ByteVector &name, Item &item);
TagLib::ByteVector renderLongLong(const ByteVector &name, Item &item);
TagLib::ByteVector renderIntPair(const ByteVector &name, Item &item);
TagLib::ByteVector renderIntPairNoTrailing(const ByteVector &name, Item &item);
TagLib::ByteVector renderCovr(const ByteVector &name, Item &item);
ByteVector padIlst(const ByteVector &data, int length = -1) const;
ByteVector renderAtom(const ByteVector &name, const ByteVector &data) const;
ByteVector renderData(const ByteVector &name, int flags,
const ByteVectorList &data) const;
ByteVector renderText(const ByteVector &name, const Item &item,
int flags = TypeUTF8) const;
ByteVector renderFreeForm(const String &name, const Item &item) const;
ByteVector renderBool(const ByteVector &name, const Item &item) const;
ByteVector renderInt(const ByteVector &name, const Item &item) const;
ByteVector renderByte(const ByteVector &name, const Item &item) const;
ByteVector renderUInt(const ByteVector &name, const Item &item) const;
ByteVector renderLongLong(const ByteVector &name, const Item &item) const;
ByteVector renderIntPair(const ByteVector &name, const Item &item) const;
ByteVector renderIntPairNoTrailing(const ByteVector &name, const Item &item) const;
ByteVector renderCovr(const ByteVector &name, const Item &item) const;
void updateParents(AtomList &path, long delta, int ignore = 0);
void updateParents(const AtomList &path, long delta, int ignore = 0);
void updateOffsets(long delta, offset_t offset);
void saveNew(TagLib::ByteVector &data);
void saveExisting(TagLib::ByteVector &data, AtomList &path);
void saveNew(ByteVector data);
void saveExisting(ByteVector data, const AtomList &path);
void addItem(const String &name, const Item &value);

View File

@ -53,7 +53,6 @@ public:
ID3v2Location(-1),
ID3v2Size(0),
properties(0),
scanned(false),
hasAPE(false),
hasID3v1(false),
hasID3v2(false) {}
@ -76,7 +75,6 @@ public:
DoubleTagUnion tag;
AudioProperties *properties;
bool scanned;
// These indicate whether the file *on disk* has these tags, not if
// this data structure does. This is used in computing offsets.
@ -90,20 +88,20 @@ public:
// public members
////////////////////////////////////////////////////////////////////////////////
MPC::File::File(FileName file, bool readProperties,
AudioProperties::ReadStyle propertiesStyle) : TagLib::File(file)
MPC::File::File(FileName file, bool readProperties, AudioProperties::ReadStyle) :
TagLib::File(file),
d(new FilePrivate())
{
d = new FilePrivate;
if(isOpen())
read(readProperties, propertiesStyle);
read(readProperties);
}
MPC::File::File(IOStream *stream, bool readProperties,
AudioProperties::ReadStyle propertiesStyle) : TagLib::File(stream)
MPC::File::File(IOStream *stream, bool readProperties, AudioProperties::ReadStyle) :
TagLib::File(stream),
d(new FilePrivate())
{
d = new FilePrivate;
if(isOpen())
read(readProperties, propertiesStyle);
read(readProperties);
}
MPC::File::~File()
@ -253,7 +251,7 @@ bool MPC::File::hasAPETag() const
// private members
////////////////////////////////////////////////////////////////////////////////
void MPC::File::read(bool readProperties, AudioProperties::ReadStyle /* propertiesStyle */)
void MPC::File::read(bool readProperties)
{
// Look for an ID3v1 tag
@ -266,8 +264,6 @@ void MPC::File::read(bool readProperties, AudioProperties::ReadStyle /* properti
// Look for an APE tag
findAPE();
d->APELocation = findAPE();
if(d->APELocation >= 0) {
@ -281,7 +277,7 @@ void MPC::File::read(bool readProperties, AudioProperties::ReadStyle /* properti
if(!d->hasID3v1)
APETag(true);
// Look for and skip an ID3v2 tag
// Look for an ID3v2 tag
d->ID3v2Location = findID3v2();
@ -292,15 +288,28 @@ void MPC::File::read(bool readProperties, AudioProperties::ReadStyle /* properti
d->hasID3v2 = true;
}
if(d->hasID3v2)
seek(d->ID3v2Location + d->ID3v2Size);
else
seek(0);
// Look for MPC metadata
if(readProperties) {
d->properties = new AudioProperties(this, length() - d->ID3v2Size - d->APESize);
offset_t streamLength;
if(d->hasAPE)
streamLength = d->APELocation;
else if(d->hasID3v1)
streamLength = d->ID3v1Location;
else
streamLength = length();
if(d->hasID3v2) {
seek(d->ID3v2Location + d->ID3v2Size);
streamLength -= (d->ID3v2Location + d->ID3v2Size);
}
else {
seek(0);
}
d->properties = new AudioProperties(this, streamLength);
}
}

View File

@ -130,6 +130,11 @@ namespace TagLib {
/*!
* Saves the file.
*
* This returns true if the save was successful.
*
* \warning In the current implementation, it's dangerous to call save()
* repeatedly. At worst it will corrupt the file.
*/
virtual bool save();
@ -140,8 +145,8 @@ namespace TagLib {
* if there is no valid APE tag. If \a create is true it will create
* an APE tag if one does not exist and returns a valid pointer.
*
* \note This may return a valid pointer regardless of whether or not the
* file on disk has an ID3v1 tag. Use hasID3v1Tag() to check if the file
* \note This may return a valid pointer regardless of whether or not the
* file on disk has an ID3v1 tag. Use hasID3v1Tag() to check if the file
* on disk actually has an ID3v1 tag.
*
* \note The Tag <b>is still</b> owned by the MPEG::File and should not be
@ -157,11 +162,11 @@ namespace TagLib {
*
* If \a create is false (the default) this may return a null pointer
* if there is no valid APE tag. If \a create is true it will create
* an APE tag if one does not exist and returns a valid pointer. If
* an APE tag if one does not exist and returns a valid pointer. If
* there already be an ID3v1 tag, the new APE tag will be placed before it.
*
* \note This may return a valid pointer regardless of whether or not the
* file on disk has an APE tag. Use hasAPETag() to check if the file
* \note This may return a valid pointer regardless of whether or not the
* file on disk has an APE tag. Use hasAPETag() to check if the file
* on disk actually has an APE tag.
*
* \note The Tag <b>is still</b> owned by the MPEG::File and should not be
@ -207,8 +212,7 @@ namespace TagLib {
File(const File &);
File &operator=(const File &);
void read(bool readProperties, AudioProperties::ReadStyle propertiesStyle);
void scan();
void read(bool readProperties);
offset_t findAPE();
offset_t findID3v1();
offset_t findID3v2();

View File

@ -33,6 +33,11 @@
using namespace TagLib;
namespace
{
const TagLib::uint HeaderSize = 56;
}
class MPC::AudioProperties::PropertiesPrivate
{
public:
@ -67,17 +72,18 @@ public:
// public members
////////////////////////////////////////////////////////////////////////////////
MPC::AudioProperties::AudioProperties(File *file, offset_t streamLength, ReadStyle style) :
MPC::AudioProperties::AudioProperties(File *file, offset_t streamLength, ReadStyle) :
TagLib::AudioProperties(),
d(new PropertiesPrivate())
{
const ByteVector magic = file->readBlock(4);
ByteVector magic = file->readBlock(4);
if(magic == "MPCK") {
// Musepack version 8
readSV8(file, streamLength);
}
else {
// Musepack version 7 or older, fixed size header
readSV7(magic + file->readBlock(52), streamLength);
readSV7(magic + file->readBlock(HeaderSize - 4), streamLength);
}
}
@ -87,6 +93,16 @@ MPC::AudioProperties::~AudioProperties()
}
int MPC::AudioProperties::length() const
{
return lengthInSeconds();
}
int MPC::AudioProperties::lengthInSeconds() const
{
return d->length / 1000;
}
int MPC::AudioProperties::lengthInMilliseconds() const
{
return d->length;
}
@ -183,7 +199,7 @@ namespace
// This array looks weird, but the same as original MusePack code found at:
// https://www.musepack.net/index.php?pg=src
static const unsigned short sftable [8] = { 44100, 48000, 37800, 32000, 0, 0, 0, 0 };
const unsigned short sftable [8] = { 44100, 48000, 37800, 32000, 0, 0, 0, 0 };
}
void MPC::AudioProperties::readSV8(File *file, offset_t streamLength)
@ -230,7 +246,7 @@ void MPC::AudioProperties::readSV8(File *file, offset_t streamLength)
break;
}
ulong begSilence = readSize(data, pos);
const ulong begSilence = readSize(data, pos);
if(pos > dataSize - 2) {
debug("MPC::Properties::readSV8() - \"SH\" packet is corrupt.");
break;
@ -243,12 +259,12 @@ void MPC::AudioProperties::readSV8(File *file, offset_t streamLength)
d->channels = ((flags >> 4) & 0x0F) + 1;
const uint frameCount = d->sampleFrames - begSilence;
if(frameCount != 0 && d->sampleRate != 0) {
d->bitrate = (int)(streamLength * 8.0 * d->sampleRate / frameCount / 1000);
d->length = frameCount / d->sampleRate;
if(frameCount > 0 && d->sampleRate > 0) {
const double length = frameCount * 1000.0 / d->sampleRate;
d->length = static_cast<int>(length + 0.5);
d->bitrate = static_cast<int>(streamLength * 8.0 / length + 0.5);
}
}
else if (packetType == "RG") {
// Replay Gain
// http://trac.musepack.net/wiki/SV8Specification#ReplaygainPacket
@ -260,7 +276,7 @@ void MPC::AudioProperties::readSV8(File *file, offset_t streamLength)
readRG = true;
int replayGainVersion = data[0];
const int replayGainVersion = data[0];
if(replayGainVersion == 1) {
d->trackGain = data.toUInt16BE(1);
d->trackPeak = data.toUInt16BE(3);
@ -288,9 +304,9 @@ void MPC::AudioProperties::readSV7(const ByteVector &data, offset_t streamLength
d->totalFrames = data.toUInt32LE(4);
std::bitset<32> flags(TAGLIB_CONSTRUCT_BITSET(data.toUInt32LE(8)));
d->sampleRate = sftable[flags[17] * 2 + flags[16]];
d->channels = 2;
const uint flags = data.toUInt32LE(8);
d->sampleRate = sftable[(flags >> 16) & 0x03];
d->channels = 2;
const uint gapless = data.toUInt32LE(5);
@ -329,10 +345,10 @@ void MPC::AudioProperties::readSV7(const ByteVector &data, offset_t streamLength
else {
const uint headerData = data.toUInt32LE(0);
d->bitrate = (headerData >> 23) & 0x01ff;
d->version = (headerData >> 11) & 0x03ff;
d->bitrate = (headerData >> 23) & 0x01ff;
d->version = (headerData >> 11) & 0x03ff;
d->sampleRate = 44100;
d->channels = 2;
d->channels = 2;
if(d->version >= 5)
d->totalFrames = data.toUInt32LE(4);
@ -342,8 +358,11 @@ void MPC::AudioProperties::readSV7(const ByteVector &data, offset_t streamLength
d->sampleFrames = d->totalFrames * 1152 - 576;
}
d->length = d->sampleRate > 0 ? (d->sampleFrames + (d->sampleRate / 2)) / d->sampleRate : 0;
if(d->sampleFrames > 0 && d->sampleRate > 0) {
const double length = d->sampleFrames * 1000.0 / d->sampleRate;
d->length = static_cast<int>(length + 0.5);
if(!d->bitrate)
d->bitrate = d->length > 0 ? static_cast<int>(streamLength * 8L / d->length / 1000) : 0;
if(d->bitrate == 0)
d->bitrate = static_cast<int>(streamLength * 8.0 / length + 0.5);
}
}

View File

@ -56,17 +56,53 @@ namespace TagLib {
*/
virtual ~AudioProperties();
// Reimplementations.
/*!
* Returns the length of the file in seconds. The length is rounded down to
* the nearest whole second.
*
* \note This method is just an alias of lengthInSeconds().
*
* \deprecated
*/
virtual int length() const;
/*!
* Returns the length of the file in seconds. The length is rounded down to
* the nearest whole second.
*
* \see lengthInMilliseconds()
*/
// BIC: make virtual
int lengthInSeconds() const;
/*!
* Returns the length of the file in milliseconds.
*
* \see lengthInSeconds()
*/
// BIC: make virtual
int lengthInMilliseconds() const;
/*!
* Returns the average bit rate of the file in kb/s.
*/
virtual int bitrate() const;
/*!
* Returns the sample rate in Hz.
*/
virtual int sampleRate() const;
/*!
* Returns the number of audio channels.
*/
virtual int channels() const;
/*!
* Returns the version of the bitstream (SV4-SV8)
*/
int mpcVersion() const;
uint totalFrames() const;
uint sampleFrames() const;

View File

@ -84,7 +84,7 @@ namespace TagLib {
//! The main class in the ID3v1 implementation
/*!
* This is an implementation of the ID3v1 format. ID3v1 is both the simplist
* This is an implementation of the ID3v1 format. ID3v1 is both the simplest
* and most common of tag formats but is rather limited. Because of its
* pervasiveness and the way that applications have been written around the
* fields that it provides, the generic TagLib::Tag API is a mirror of what is

View File

@ -64,18 +64,26 @@ ChapterFrame::ChapterFrame(const ID3v2::Header *tagHeader, const ByteVector &dat
setData(data);
}
ChapterFrame::ChapterFrame(const ByteVector &eID, const TagLib::uint &sT, const TagLib::uint &eT,
const TagLib::uint &sO, const TagLib::uint &eO, const FrameList &eF) :
ChapterFrame::ChapterFrame(const ByteVector &elementID,
const TagLib::uint &startTime, const TagLib::uint &endTime,
const TagLib::uint &startOffset, const TagLib::uint &endOffset,
const FrameList &embeddedFrames) :
ID3v2::Frame("CHAP")
{
d = new ChapterFramePrivate;
d->elementID = eID;
d->startTime = sT;
d->endTime = eT;
d->startOffset = sO;
d->endOffset = eO;
FrameList l = eF;
for(FrameList::ConstIterator it = l.begin(); it != l.end(); ++it)
// setElementID has a workaround for a previously silly API where you had to
// specifically include the null byte.
setElementID(elementID);
d->startTime = startTime;
d->endTime = endTime;
d->startOffset = startOffset;
d->endOffset = endOffset;
for(FrameList::ConstIterator it = embeddedFrames.begin();
it != embeddedFrames.end(); ++it)
addEmbeddedFrame(*it);
}
@ -112,8 +120,9 @@ TagLib::uint ChapterFrame::endOffset() const
void ChapterFrame::setElementID(const ByteVector &eID)
{
d->elementID = eID;
if(eID.at(eID.size() - 1) != char(0))
d->elementID.append(char(0));
if(d->elementID.endsWith(char(0)))
d->elementID = d->elementID.mid(0, d->elementID.size() - 1);
}
void ChapterFrame::setStartTime(const TagLib::uint &sT)
@ -181,7 +190,25 @@ void ChapterFrame::removeEmbeddedFrames(const ByteVector &id)
String ChapterFrame::toString() const
{
return String::null;
String s = String(d->elementID) +
": start time: " + String::number(d->startTime) +
", end time: " + String::number(d->endTime);
if(d->startOffset != 0xFFFFFFFF)
s += ", start offset: " + String::number(d->startOffset);
if(d->endOffset != 0xFFFFFFFF)
s += ", start offset: " + String::number(d->endOffset);
if(!d->embeddedFrameList.isEmpty()) {
StringList frameIDs;
for(FrameList::ConstIterator it = d->embeddedFrameList.begin();
it != d->embeddedFrameList.end(); ++it)
frameIDs.append((*it)->frameID());
s += ", sub-frames: [ " + frameIDs.toString(", ") + " ]";
}
return s;
}
PropertyMap ChapterFrame::asProperties() const
@ -221,7 +248,6 @@ void ChapterFrame::parseFields(const ByteVector &data)
size_t pos = 0;
size_t embPos = 0;
d->elementID = readStringField(data, String::Latin1, pos).data(String::Latin1);
d->elementID.append(char(0));
d->startTime = data.toUInt32BE(pos);
pos += 4;
d->endTime = data.toUInt32BE(pos);
@ -230,9 +256,15 @@ void ChapterFrame::parseFields(const ByteVector &data)
pos += 4;
d->endOffset = data.toUInt32BE(pos);
pos += 4;
size -= pos;
while(embPos < size - header()->size()) {
size -= pos;
// Embedded frames are optional
if(size < header()->size())
return;
while(embPos < size - header()->size()) {
Frame *frame = FrameFactory::instance()->createFrame(data.mid(pos + embPos), d->tagHeader);
if(!frame)
@ -254,6 +286,7 @@ ByteVector ChapterFrame::renderFields() const
ByteVector data;
data.append(d->elementID);
data.append('\0');
data.append(ByteVector::fromUInt32BE(d->startTime));
data.append(ByteVector::fromUInt32BE(d->endTime));
data.append(ByteVector::fromUInt32BE(d->startOffset));

View File

@ -53,17 +53,24 @@ namespace TagLib {
ChapterFrame(const ID3v2::Header *tagHeader, const ByteVector &data);
/*!
* Creates a chapter frame with the element ID \a eID,
* start time \a sT, end time \a eT, start offset \a sO,
* end offset \a eO and embedded frames, that are in \a eF.
* Creates a chapter frame with the element ID \a elementID, start time
* \a startTime, end time \a endTime, start offset \a startOffset,
* end offset \a endOffset and optionally a list of embedded frames,
* whose ownership will then be taken over by this Frame, in
* \a embeededFrames;
*
* All times are in milliseconds.
*/
ChapterFrame(const ByteVector &eID, const uint &sT, const uint &eT, const uint &sO,
const uint &eO, const FrameList &eF);
// BIC: There's no reason to use const-references with uints
ChapterFrame(const ByteVector &elementID,
const uint &startTime, const uint &endTime,
const uint &startOffset, const uint &endOffset,
const FrameList &embeddedFrames = FrameList());
/*!
* Destroys the frame.
*/
~ChapterFrame();
virtual ~ChapterFrame();
/*!
* Returns the element ID of the frame. Element ID
@ -74,14 +81,14 @@ namespace TagLib {
ByteVector elementID() const;
/*!
* Returns time of chapter's start (in miliseconds).
* Returns time of chapter's start (in milliseconds).
*
* \see setStartTime()
*/
uint startTime() const;
/*!
* Returns time of chapter's end (in miliseconds).
* Returns time of chapter's end (in milliseconds).
*
* \see setEndTime()
*/
@ -114,14 +121,14 @@ namespace TagLib {
void setElementID(const ByteVector &eID);
/*!
* Sets time of chapter's start (in miliseconds) to \a sT.
* Sets time of chapter's start (in milliseconds) to \a sT.
*
* \see startTime()
*/
void setStartTime(const uint &sT);
/*!
* Sets time of chapter's end (in miliseconds) to \a eT.
* Sets time of chapter's end (in milliseconds) to \a eT.
*
* \see endTime()
*/

View File

@ -36,7 +36,7 @@ namespace TagLib {
//! An implementation of ID3v2 comments
/*!
* This implements the ID3v2 comment format. An ID3v2 comment concists of
* This implements the ID3v2 comment format. An ID3v2 comment consists of
* a language encoding, a description and a single text field.
*/
@ -106,7 +106,7 @@ namespace TagLib {
/*!
* Sets the description of the comment to \a s.
*
* \see decription()
* \see description()
*/
void setDescription(const String &s);
@ -149,7 +149,7 @@ namespace TagLib {
/*!
* Comments each have a unique description. This searches for a comment
* frame with the decription \a d and returns a pointer to it. If no
* frame with the description \a d and returns a pointer to it. If no
* frame is found that matches the given description null is returned.
*
* \see description()

View File

@ -94,21 +94,21 @@ namespace TagLib {
* \see pricePaid()
*/
void setPricePaid(const String &pricePaid);
/*!
* Returns the seller.
*
* \see setSeller()
*/
String seller() const;
/*!
* Set the seller.
*
* \see seller()
*/
void setSeller(const String &seller);
/*!
* Returns the text encoding that will be used in rendering this frame.
* This defaults to the type that was either specified in the constructor
@ -118,7 +118,7 @@ namespace TagLib {
* \see render()
*/
String::Type textEncoding() const;
/*!
* Sets the text encoding to be used when rendering this frame to
* \a encoding.

View File

@ -36,7 +36,7 @@ namespace TagLib {
//! An implementation of ID3v2 "popularimeter"
/*!
* This implements the ID3v2 popularimeter (POPM frame). It concists of
* This implements the ID3v2 popularimeter (POPM frame). It consists of
* an email, a rating and an optional counter.
*/

View File

@ -140,14 +140,14 @@ namespace TagLib {
/*
* There was a terrible API goof here, and while this can't be changed to
* the way it appears below for binary compaibility reasons, let's at
* the way it appears below for binary compatibility reasons, let's at
* least pretend that it looks clean.
*/
/*!
* Returns the relative volume adjustment "index". As indicated by the
* ID3v2 standard this is a 16-bit signed integer that reflects the
* decibils of adjustment when divided by 512.
* decibels of adjustment when divided by 512.
*
* This defaults to returning the value for the master volume channel if
* available and returns 0 if the specified channel does not exist.
@ -159,7 +159,7 @@ namespace TagLib {
/*!
* Set the volume adjustment to \a index. As indicated by the ID3v2
* standard this is a 16-bit signed integer that reflects the decibils of
* standard this is a 16-bit signed integer that reflects the decibels of
* adjustment when divided by 512.
*
* By default this sets the value for the master volume.

View File

@ -197,7 +197,7 @@ namespace TagLib {
/*!
* Sets the description of the synchronized lyrics frame to \a s.
*
* \see decription()
* \see description()
*/
void setDescription(const String &s);

View File

@ -50,6 +50,31 @@ public:
FrameList embeddedFrameList;
};
namespace {
// These functions are needed to try to aim for backward compatibility with
// an API that previously (unreasonably) required null bytes to be appeneded
// at the end of identifiers explicitly by the API user.
// BIC: remove these
ByteVector &strip(ByteVector &b)
{
if(b.endsWith('\0'))
b.resize(b.size() - 1);
return b;
}
ByteVectorList &strip(ByteVectorList &l)
{
for(ByteVectorList::Iterator it = l.begin(); it != l.end(); ++it)
{
strip(*it);
}
return l;
}
}
////////////////////////////////////////////////////////////////////////////////
// public methods
////////////////////////////////////////////////////////////////////////////////
@ -62,15 +87,17 @@ TableOfContentsFrame::TableOfContentsFrame(const ID3v2::Header *tagHeader, const
setData(data);
}
TableOfContentsFrame::TableOfContentsFrame(const ByteVector &eID, const ByteVectorList &ch,
const FrameList &eF) :
TableOfContentsFrame::TableOfContentsFrame(const ByteVector &elementID,
const ByteVectorList &children,
const FrameList &embeddedFrames) :
ID3v2::Frame("CTOC")
{
d = new TableOfContentsFramePrivate;
d->elementID = eID;
d->childElements = ch;
FrameList l = eF;
for(FrameList::ConstIterator it = l.begin(); it != l.end(); ++it)
d->elementID = elementID;
strip(d->elementID);
d->childElements = children;
for(FrameList::ConstIterator it = embeddedFrames.begin(); it != embeddedFrames.end(); ++it)
addEmbeddedFrame(*it);
}
@ -107,8 +134,7 @@ ByteVectorList TableOfContentsFrame::childElements() const
void TableOfContentsFrame::setElementID(const ByteVector &eID)
{
d->elementID = eID;
if(eID.at(eID.size() - 1) != char(0))
d->elementID.append(char(0));
strip(d->elementID);
}
void TableOfContentsFrame::setIsTopLevel(const bool &t)
@ -124,16 +150,22 @@ void TableOfContentsFrame::setIsOrdered(const bool &o)
void TableOfContentsFrame::setChildElements(const ByteVectorList &l)
{
d->childElements = l;
strip(d->childElements);
}
void TableOfContentsFrame::addChildElement(const ByteVector &cE)
{
d->childElements.append(cE);
strip(d->childElements);
}
void TableOfContentsFrame::removeChildElement(const ByteVector &cE)
{
ByteVectorList::Iterator it = d->childElements.find(cE);
if(it == d->childElements.end())
it = d->childElements.find(cE + ByteVector("\0"));
d->childElements.erase(it);
}
@ -240,18 +272,19 @@ void TableOfContentsFrame::parseFields(const ByteVector &data)
size_t pos = 0;
size_t embPos = 0;
d->elementID = readStringField(data, String::Latin1, pos).data(String::Latin1);
d->elementID.append(char(0));
d->isTopLevel = (data.at(pos) & 2) > 0;
d->isOrdered = (data.at(pos++) & 1) > 0;
TagLib::uint entryCount = data.at(pos++);
for(TagLib::uint i = 0; i < entryCount; i++)
{
for(TagLib::uint i = 0; i < entryCount; i++) {
ByteVector childElementID = readStringField(data, String::Latin1, pos).data(String::Latin1);
childElementID.append(char(0));
d->childElements.append(childElementID);
}
size -= pos;
if(size < header()->size())
return;
while(embPos < size - header()->size()) {
Frame *frame = FrameFactory::instance()->createFrame(data.mid(pos + embPos), d->tagHeader);
@ -274,6 +307,7 @@ ByteVector TableOfContentsFrame::renderFields() const
ByteVector data;
data.append(d->elementID);
data.append('\0');
char flags = 0;
if(d->isTopLevel)
flags += 2;
@ -284,6 +318,7 @@ ByteVector TableOfContentsFrame::renderFields() const
ByteVectorList::ConstIterator it = d->childElements.begin();
while(it != d->childElements.end()) {
data.append(*it);
data.append('\0');
it++;
}
FrameList l = d->embeddedFrameList;

View File

@ -52,10 +52,13 @@ namespace TagLib {
TableOfContentsFrame(const ID3v2::Header *tagHeader, const ByteVector &data);
/*!
* Creates a table of contents frame with the element ID \a eID,
* the child elements \a ch and embedded frames, that are in \a eF.
* Creates a table of contents frame with the element ID \a elementID,
* the child elements \a children and embedded frames, which become owned
* by this frame, in \a embeddedFrames.
*/
TableOfContentsFrame(const ByteVector &eID, const ByteVectorList &ch, const FrameList &eF);
TableOfContentsFrame(const ByteVector &elementID,
const ByteVectorList &children = ByteVectorList(),
const FrameList &embeddedFrames = FrameList());
/*!
* Destroys the frame.
@ -71,7 +74,7 @@ namespace TagLib {
ByteVector elementID() const;
/*!
* Returns true, if the frame is top-level (doen't have
* Returns true, if the frame is top-level (doesn't have
* any parent CTOC frame).
*
* \see setIsTopLevel()
@ -87,7 +90,7 @@ namespace TagLib {
bool isOrdered() const;
/*!
* Returns count of child elements of the frame. It allways
* Returns count of child elements of the frame. It always
* corresponds to size of child elements list.
*
* \see childElements()
@ -110,7 +113,7 @@ namespace TagLib {
void setElementID(const ByteVector &eID);
/*!
* Sets, if the frame is top-level (doen't have
* Sets, if the frame is top-level (doesn't have
* any parent CTOC frame).
*
* \see isTopLevel()

View File

@ -46,7 +46,7 @@ namespace TagLib {
public:
/*!
* Creates a uniqe file identifier frame based on \a data.
* Creates a unique file identifier frame based on \a data.
*/
UniqueFileIdentifierFrame(const ByteVector &data);

View File

@ -104,7 +104,7 @@ namespace TagLib {
/*!
* Sets the description of the unsynchronized lyrics frame to \a s.
*
* \see decription()
* \see description()
*/
void setDescription(const String &s);
@ -149,7 +149,7 @@ namespace TagLib {
/*!
* LyricsFrames each have a unique description. This searches for a lyrics
* frame with the decription \a d and returns a pointer to it. If no
* frame with the description \a d and returns a pointer to it. If no
* frame is found that matches the given description null is returned.
*
* \see description()

View File

@ -165,7 +165,7 @@ namespace TagLib {
* All other processing of \a data should be handled in a subclass.
*
* \note This need not contain anything more than a frame ID, but
* \e must constain at least that.
* \e must contain at least that.
*/
explicit Frame(const ByteVector &data);
@ -218,9 +218,9 @@ namespace TagLib {
ByteVector fieldData(const ByteVector &frameData) const;
/*!
* Reads a String of type \a encodiong from the ByteVector \a data. If \a
* Reads a String of type \a encoding from the ByteVector \a data. If \a
* position is passed in it is used both as the starting point and is
* updated to replect the position just after the string that has been read.
* updated to return the position just after the string that has been read.
* This is useful for reading strings sequentially.
*/
String readStringField(const ByteVector &data, String::Type encoding, size_t &positon);
@ -459,7 +459,7 @@ namespace TagLib {
bool readOnly() const;
/*!
* Returns true if the flag for the grouping identifity is set.
* Returns true if the flag for the grouping identity is set.
*
* \note This flag is currently ignored internally in TagLib.
*/

View File

@ -84,7 +84,7 @@ namespace TagLib {
* Returns the default text encoding for text frames. If setTextEncoding()
* has not been explicitly called this will only be used for new text
* frames. However, if this value has been set explicitly all frames will be
* converted to this type (unless it's explitly set differently for the
* converted to this type (unless it's explicitly set differently for the
* individual frame) when being rendered.
*
* \see setDefaultTextEncoding()

View File

@ -37,7 +37,7 @@ namespace TagLib {
/*!
* This class implements ID3v2 headers. It attempts to follow, both
* semantically and programatically, the structure specified in
* semantically and programmatically, the structure specified in
* the ID3v2 standard. The API is based on the properties of ID3v2 headers
* specified there. If any of the terms used in this documentation are
* unclear please check the specification in the linked section.

View File

@ -24,10 +24,10 @@
***************************************************************************/
#ifdef HAVE_CONFIG_H
#include <config.h>
#include "config.h"
#endif
#include <tfile.h>
#include "tfile.h"
#include "id3v2tag.h"
#include "id3v2header.h"
@ -37,7 +37,7 @@
#include "tbytevector.h"
#include "id3v1genres.h"
#include "tpropertymap.h"
#include <tdebug.h>
#include "tdebug.h"
#include "frames/textidentificationframe.h"
#include "frames/commentsframe.h"
@ -93,7 +93,7 @@ const TagLib::StringHandler *ID3v2::Tag::TagPrivate::stringHandler = &defaultStr
namespace
{
const uint DefaultPaddingSize = 1024;
const TagLib::uint DefaultPaddingSize = 1024;
}
////////////////////////////////////////////////////////////////////////////////
@ -109,7 +109,7 @@ String ID3v2::Latin1StringHandler::parse(const ByteVector &data) const
return String(data, String::Latin1);
}
ByteVector ID3v2::Latin1StringHandler::render(const String &s) const
ByteVector ID3v2::Latin1StringHandler::render(const String &) const
{
return ByteVector();
}
@ -602,12 +602,19 @@ ByteVector ID3v2::Tag::render(int version) const
for(FrameList::ConstIterator it = frameList.begin(); it != frameList.end(); it++) {
(*it)->header()->setVersion(version);
if((*it)->header()->frameID().size() != 4) {
debug("A frame of unsupported or unknown type \'"
debug("An ID3v2 frame of unsupported or unknown type \'"
+ String((*it)->header()->frameID()) + "\' has been discarded");
continue;
}
if(!(*it)->header()->tagAlterPreservation())
tagData.append((*it)->render());
if(!(*it)->header()->tagAlterPreservation()) {
const ByteVector frameData = (*it)->render();
if(frameData.size() == Frame::headerSize((*it)->header()->version())) {
debug("An empty ID3v2 frame \'"
+ String((*it)->header()->frameID()) + "\' has been discarded");
continue;
}
tagData.append(frameData);
}
}
// Compute the amount of padding, and append that to tagData.

View File

@ -61,13 +61,13 @@ namespace TagLib {
//! An abstraction for the ISO-8859-1 string to data encoding in ID3v2 tags.
/*!
* ID3v2 tag can store strings in ISO-8859-1 (Latin1), and TagLib only
* supports genuine ISO-8859-1 by default. However, in practice, non
* ISO-8859-1 encodings are often used instead of ISO-8859-1, such as
* ID3v2 tag can store strings in ISO-8859-1 (Latin1), and TagLib only
* supports genuine ISO-8859-1 by default. However, in practice, non
* ISO-8859-1 encodings are often used instead of ISO-8859-1, such as
* Windows-1252 for western languages, Shift_JIS for Japanese and so on.
*
* Here is an option to read such tags by subclassing this class,
* reimplementing parse() and setting your reimplementation as the default
* reimplementing parse() and setting your reimplementation as the default
* with ID3v2::Tag::setStringHandler().
*
* \note Writing non-ISO-8859-1 tags is not implemented intentionally.
@ -105,7 +105,7 @@ namespace TagLib {
* split into data components.
*
* ID3v2 tags have several parts, TagLib attempts to provide an interface
* for them all. header(), footer() and extendedHeader() corespond to those
* for them all. header(), footer() and extendedHeader() correspond to those
* data structures in the ID3v2 standard and the APIs for the classes that
* they return attempt to reflect this.
*
@ -122,7 +122,7 @@ namespace TagLib {
* class.
*
* read() and parse() pass binary data to the other ID3v2 class structures,
* they do not handle parsing of flags or fields, for instace. Those are
* they do not handle parsing of flags or fields, for instance. Those are
* handled by similar functions within those classes.
*
* \note All pointers to data structures within the tag will become invalid
@ -133,7 +133,7 @@ namespace TagLib {
* rather long, but if you're planning on messing with this class and others
* that deal with the details of ID3v2 (rather than the nice, safe, abstract
* TagLib::Tag and friends), it's worth your time to familiarize yourself
* with said spec (which is distrubuted with the TagLib sources). TagLib
* with said spec (which is distributed with the TagLib sources). TagLib
* tries to do most of the work, but with a little luck, you can still
* convince it to generate invalid ID3v2 tags. The APIs for ID3v2 assume a
* working knowledge of ID3v2 structure. You're been warned.
@ -157,7 +157,7 @@ namespace TagLib {
* \note You should be able to ignore the \a factory parameter in almost
* all situations. You would want to specify your own FrameFactory
* subclass in the case that you are extending TagLib to support additional
* frame types, which would be incorperated into your factory.
* frame types, which would be incorporated into your factory.
*
* \see FrameFactory
*/
@ -360,9 +360,9 @@ namespace TagLib {
*/
// BIC: combine with the above method
ByteVector render(int version) const;
/*!
* Gets the current string handler that decides how the "Latin-1" data
* Gets the current string handler that decides how the "Latin-1" data
* will be converted to and from binary data.
*
* \see Latin1StringHandler
@ -376,7 +376,7 @@ namespace TagLib {
* released and default ISO-8859-1 handler is restored.
*
* \note The caller is responsible for deleting the previous handler
* as needed after it is released.
* as needed after it is released.
*
* \see Latin1StringHandler
*/

View File

@ -31,14 +31,31 @@
#include <apetag.h>
#include <tdebug.h>
#include <bitset>
#include "mpegfile.h"
#include "mpegheader.h"
#include "tpropertymap.h"
using namespace TagLib;
namespace
{
/*!
* MPEG frames can be recognized by the bit pattern 11111111 111, so the
* first byte is easy to check for, however checking to see if the second byte
* starts with \e 111 is a bit more tricky, hence these functions.
*/
inline bool firstSyncByte(uchar byte)
{
return (byte == 0xFF);
}
inline bool secondSynchByte(uchar byte)
{
return ((byte & 0xE0) == 0xE0);
}
}
namespace
{
enum { ID3v2Index = 0, APEIndex = 1, ID3v1Index = 2 };
@ -60,7 +77,6 @@ public:
hasAPE(false),
properties(0)
{
}
~FilePrivate()
@ -95,33 +111,30 @@ public:
// public members
////////////////////////////////////////////////////////////////////////////////
MPEG::File::File(FileName file, bool readProperties,
AudioProperties::ReadStyle propertiesStyle) : TagLib::File(file)
MPEG::File::File(FileName file, bool readProperties, AudioProperties::ReadStyle) :
TagLib::File(file),
d(new FilePrivate())
{
d = new FilePrivate;
if(isOpen())
read(readProperties, propertiesStyle);
read(readProperties);
}
MPEG::File::File(FileName file, ID3v2::FrameFactory *frameFactory,
bool readProperties, AudioProperties::ReadStyle propertiesStyle) :
TagLib::File(file)
bool readProperties, AudioProperties::ReadStyle) :
TagLib::File(file),
d(new FilePrivate(frameFactory))
{
d = new FilePrivate(frameFactory);
if(isOpen())
read(readProperties, propertiesStyle);
read(readProperties);
}
MPEG::File::File(IOStream *stream, ID3v2::FrameFactory *frameFactory,
bool readProperties, AudioProperties::ReadStyle propertiesStyle) :
TagLib::File(stream)
bool readProperties, AudioProperties::ReadStyle) :
TagLib::File(stream),
d(new FilePrivate(frameFactory))
{
d = new FilePrivate(frameFactory);
if(isOpen())
read(readProperties, propertiesStyle);
read(readProperties);
}
MPEG::File::~File()
@ -152,11 +165,6 @@ bool MPEG::File::save()
return save(AllTags);
}
bool MPEG::File::save(int tags, bool stripOthers, int id3v2Version)
{
return save(tags, stripOthers, id3v2Version, true);
}
bool MPEG::File::save(int tags, bool stripOthers, int id3v2Version, bool duplicateTags)
{
if(tags == NoTags && stripOthers)
@ -359,11 +367,11 @@ offset_t MPEG::File::nextFrameOffset(offset_t position)
return position - 1;
for(uint i = 0; i < buffer.size() - 1; i++) {
if(uchar(buffer[i]) == 0xff && secondSynchByte(buffer[i + 1]))
if(firstSyncByte(buffer[i]) && secondSynchByte(buffer[i + 1]))
return position + i;
}
foundLastSyncPattern = uchar(buffer[buffer.size() - 1]) == 0xff;
foundLastSyncPattern = firstSyncByte(buffer[buffer.size() - 1]);
position += buffer.size();
}
}
@ -384,11 +392,11 @@ offset_t MPEG::File::previousFrameOffset(offset_t position)
if(buffer.isEmpty())
break;
if(foundFirstSyncPattern && uchar(buffer[buffer.size() - 1]) == 0xff)
if(foundFirstSyncPattern && firstSyncByte(buffer[buffer.size() - 1]))
return position + buffer.size() - 1;
for(int i = static_cast<int>(buffer.size()) - 2; i >= 0; i--) {
if(uchar(buffer[i]) == 0xff && secondSynchByte(buffer[i + 1]))
if(firstSyncByte(buffer[i]) && secondSynchByte(buffer[i + 1]))
return position + i;
}
@ -401,7 +409,7 @@ offset_t MPEG::File::firstFrameOffset()
{
offset_t position = 0;
if(ID3v2Tag()) {
if(hasID3v2Tag()) {
position = d->ID3v2Location + ID3v2Tag()->header()->completeTagSize();
// Skip duplicate ID3v2 tags.
@ -409,7 +417,7 @@ offset_t MPEG::File::firstFrameOffset()
// Workaround for some faulty files that have duplicate ID3v2 tags.
// Combination of EAC and LAME creates such files when configured incorrectly.
long location;
offset_t location;
while((location = findID3v2(position)) >= 0) {
seek(location);
const ID3v2::Header header(readBlock(ID3v2::Header::size()));
@ -424,7 +432,7 @@ offset_t MPEG::File::firstFrameOffset()
offset_t MPEG::File::lastFrameOffset()
{
return previousFrameOffset(ID3v1Tag() ? d->ID3v1Location - 1 : length());
return previousFrameOffset(hasID3v1Tag() ? d->ID3v1Location - 1 : length());
}
bool MPEG::File::hasID3v1Tag() const
@ -446,7 +454,7 @@ bool MPEG::File::hasAPETag() const
// private members
////////////////////////////////////////////////////////////////////////////////
void MPEG::File::read(bool readProperties, AudioProperties::ReadStyle propertiesStyle)
void MPEG::File::read(bool readProperties)
{
// Look for an ID3v2 tag
@ -485,7 +493,7 @@ void MPEG::File::read(bool readProperties, AudioProperties::ReadStyle properties
}
if(readProperties)
d->properties = new AudioProperties(this, propertiesStyle);
d->properties = new AudioProperties(this);
// Make sure that we have our default tag types available.
@ -496,7 +504,7 @@ void MPEG::File::read(bool readProperties, AudioProperties::ReadStyle properties
offset_t MPEG::File::findID3v2(offset_t offset)
{
// This method is based on the contents of TagLib::File::find(), but because
// of some subtlteies -- specifically the need to look for the bit pattern of
// of some subtleties -- specifically the need to look for the bit pattern of
// an MPEG sync, it has been modified for use here.
if(isValid() && ID3v2::Header::fileIdentifier().size() <= bufferSize()) {
@ -639,11 +647,3 @@ void MPEG::File::findAPE()
d->APELocation = -1;
d->APEFooterLocation = -1;
}
bool MPEG::File::secondSynchByte(char byte)
{
std::bitset<8> b(byte);
// check to see if the byte matches 111xxxxx
return b.test(7) && b.test(6) && b.test(5);
}

View File

@ -163,7 +163,10 @@ namespace TagLib {
* This is the same as calling save(AllTags);
*
* If you would like more granular control over the content of the tags,
* with the concession of generality, use paramaterized save call below.
* with the concession of generality, use parameterized save call below.
*
* \warning In the current implementation, it's dangerous to call save()
* repeatedly. At worst it will corrupt the file.
*
* \see save(int tags)
*/
@ -180,26 +183,11 @@ namespace TagLib {
*
* The \a id3v2Version parameter specifies the version of the saved
* ID3v2 tag. It can be either 4 or 3.
*
* \warning In the current implementation, it's dangerous to call save()
* repeatedly. At worst it will corrupt the file.
*/
bool save(int tags, bool stripOthers = true, int id3v2Version = 4);
/*!
* Save the file. This will attempt to save all of the tag types that are
* specified by OR-ing together TagTypes values. The save() method above
* uses AllTags. This returns true if saving was successful.
*
* If \a stripOthers is true this strips all tags not included in the mask,
* but does not modify them in memory, so later calls to save() which make
* use of these tags will remain valid. This also strips empty tags.
*
* The \a id3v2Version parameter specifies the version of the saved
* ID3v2 tag. It can be either 4 or 3.
*
* If \a duplicateTags is true and at least one tag -- ID3v1 or ID3v2 --
* exists this will duplicate its content into the other tag.
*/
// BIC: combine with the above method
bool save(int tags, bool stripOthers, int id3v2Version, bool duplicateTags);
bool save(int tags, bool stripOthers = true, int id3v2Version = 4, bool duplicateTags = true);
/*!
* Returns a pointer to the ID3v2 tag of the file.
@ -267,6 +255,8 @@ namespace TagLib {
*
* \note This will also invalidate pointers to the ID3 and APE tags
* as their memory will be freed.
*
* \note This will update the file immediately.
*/
bool strip(int tags = AllTags);
@ -277,6 +267,8 @@ namespace TagLib {
*
* If \a freeMemory is true the ID3 and APE tags will be deleted and
* pointers to them will be invalidated.
*
* \note This will update the file immediately.
*/
// BIC: merge with the method above
bool strip(int tags, bool freeMemory);
@ -285,6 +277,7 @@ namespace TagLib {
* Set the ID3v2::FrameFactory to something other than the default.
*
* \see ID3v2FrameFactory
* \deprecated This value should be passed in via the constructor
*/
void setID3v2FrameFactory(const ID3v2::FrameFactory *factory);
@ -335,18 +328,11 @@ namespace TagLib {
File(const File &);
File &operator=(const File &);
void read(bool readProperties, AudioProperties::ReadStyle propertiesStyle);
void read(bool readProperties);
offset_t findID3v2(offset_t offset);
offset_t findID3v1();
void findAPE();
/*!
* MPEG frames can be recognized by the bit pattern 11111111 111, so the
* first byte is easy to check for, however checking to see if the second byte
* starts with \e 111 is a bit more tricky, hence this member function.
*/
static bool secondSynchByte(char byte);
class FilePrivate;
FilePrivate *d;
};

View File

@ -53,8 +53,8 @@ namespace
class MPEG::Header::HeaderPrivate
{
public:
HeaderPrivate()
: data(new HeaderData())
HeaderPrivate() :
data(new HeaderData())
{
data->isValid = false;
data->layer = 0;
@ -66,7 +66,7 @@ public:
data->isCopyrighted = false;
data->isOriginal = false;
data->frameLength = 0;
data->samplesPerFrame = 0;
data->samplesPerFrame = 0;
}
SHARED_PTR<HeaderData> data;
@ -76,14 +76,14 @@ public:
// public members
////////////////////////////////////////////////////////////////////////////////
MPEG::Header::Header(const ByteVector &data)
: d(new HeaderPrivate())
MPEG::Header::Header(const ByteVector &data) :
d(new HeaderPrivate())
{
parse(data);
}
MPEG::Header::Header(const Header &h)
: d(new HeaderPrivate(*h.d))
MPEG::Header::Header(const Header &h) :
d(new HeaderPrivate(*h.d))
{
}
@ -213,8 +213,8 @@ void MPEG::Header::parse(const ByteVector &data)
}
};
const int versionIndex = d->data->version == Version1 ? 0 : 1;
const int layerIndex = d->data->layer > 0 ? d->data->layer - 1 : 0;
const int versionIndex = (d->data->version == Version1) ? 0 : 1;
const int layerIndex = (d->data->layer > 0) ? d->data->layer - 1 : 0;
// The bitrate index is encoded as the first 4 bits of the 3rd byte,
// i.e. 1111xxxx
@ -253,13 +253,6 @@ void MPEG::Header::parse(const ByteVector &data)
d->data->isCopyrighted = flags[3];
d->data->isPadded = flags[9];
// Calculate the frame length
if(d->data->layer == 1)
d->data->frameLength = 24000 * 2 * d->data->bitrate / d->data->sampleRate + int(d->data->isPadded);
else
d->data->frameLength = 72000 * d->data->bitrate / d->data->sampleRate + int(d->data->isPadded);
// Samples per frame
static const int samplesPerFrame[3][2] = {
@ -271,6 +264,15 @@ void MPEG::Header::parse(const ByteVector &data)
d->data->samplesPerFrame = samplesPerFrame[layerIndex][versionIndex];
// Calculate the frame length
static const int paddingSize[3] = { 4, 1, 1 };
d->data->frameLength = d->data->samplesPerFrame * d->data->bitrate * 125 / d->data->sampleRate;
if(d->data->isPadded)
d->data->frameLength += paddingSize[layerIndex];
// Now that we're done parsing, set this to be a valid frame.
d->data->isValid = true;

View File

@ -140,7 +140,7 @@ namespace TagLib {
bool isOriginal() const;
/*!
* Returns the frame length.
* Returns the frame length in bytes.
*/
int frameLength() const;

View File

@ -30,6 +30,8 @@
#include "mpegproperties.h"
#include "mpegfile.h"
#include "xingheader.h"
#include "apetag.h"
#include "apefooter.h"
using namespace TagLib;
@ -65,7 +67,8 @@ public:
// public members
////////////////////////////////////////////////////////////////////////////////
MPEG::AudioProperties::AudioProperties(File *file, ReadStyle style) :
MPEG::AudioProperties::AudioProperties(File *file, ReadStyle) :
TagLib::AudioProperties(),
d(new PropertiesPrivate())
{
read(file);
@ -77,6 +80,16 @@ MPEG::AudioProperties::~AudioProperties()
}
int MPEG::AudioProperties::length() const
{
return lengthInSeconds();
}
int MPEG::AudioProperties::lengthInSeconds() const
{
return d->length / 1000;
}
int MPEG::AudioProperties::lengthInMilliseconds() const
{
return d->length;
}
@ -137,105 +150,68 @@ bool MPEG::AudioProperties::isOriginal() const
void MPEG::AudioProperties::read(File *file)
{
// Since we've likely just looked for the ID3v1 tag, start at the end of the
// file where we're least likely to have to have to move the disk head.
offset_t last = file->lastFrameOffset();
if(last < 0) {
debug("MPEG::Properties::read() -- Could not find a valid last MPEG frame in the stream.");
return;
}
file->seek(last);
Header lastHeader(file->readBlock(4));
offset_t first = file->firstFrameOffset();
// Only the first frame is required if we have a VBR header.
const offset_t first = file->firstFrameOffset();
if(first < 0) {
debug("MPEG::Properties::read() -- Could not find a valid first MPEG frame in the stream.");
return;
}
if(!lastHeader.isValid()) {
offset_t pos = last;
while(pos > first) {
pos = file->previousFrameOffset(pos);
if(pos < 0)
break;
file->seek(pos);
Header header(file->readBlock(4));
if(header.isValid()) {
lastHeader = header;
last = pos;
break;
}
}
}
// Now jump back to the front of the file and read what we need from there.
file->seek(first);
Header firstHeader(file->readBlock(4));
const Header firstHeader(file->readBlock(4));
if(!firstHeader.isValid() || !lastHeader.isValid()) {
debug("MPEG::Properties::read() -- Page headers were invalid.");
if(!firstHeader.isValid()) {
debug("MPEG::Properties::read() -- The first page header is invalid.");
return;
}
// Check for a Xing header that will help us in gathering information about a
// Check for a VBR header that will help us in gathering information about a
// VBR stream.
int xingHeaderOffset = MPEG::XingHeader::offset(firstHeader.version(),
firstHeader.channelMode());
file->seek(first + xingHeaderOffset);
d->xingHeader.reset(new XingHeader(file->readBlock(16)));
// Read the length and the bitrate from the Xing header.
if(d->xingHeader->isValid() &&
firstHeader.sampleRate() > 0 &&
d->xingHeader->totalFrames() > 0)
{
double timePerFrame =
double(firstHeader.samplesPerFrame()) / firstHeader.sampleRate();
double length = timePerFrame * d->xingHeader->totalFrames();
d->length = int(length);
d->bitrate = d->length > 0 ? (int)(d->xingHeader->totalSize() * 8 / length / 1000) : 0;
}
else {
// Since there was no valid Xing header found, we hope that we're in a constant
// bitrate file.
file->seek(first + 4);
d->xingHeader.reset(new XingHeader(file->readBlock(firstHeader.frameLength() - 4)));
if(!d->xingHeader->isValid())
d->xingHeader.reset();
if(d->xingHeader && firstHeader.samplesPerFrame() > 0 && firstHeader.sampleRate() > 0) {
// Read the length and the bitrate from the VBR header.
const double timePerFrame = firstHeader.samplesPerFrame() * 1000.0 / firstHeader.sampleRate();
const double length = timePerFrame * d->xingHeader->totalFrames();
d->length = static_cast<int>(length + 0.5);
d->bitrate = static_cast<int>(d->xingHeader->totalSize() * 8.0 / length + 0.5);
}
else if(firstHeader.bitrate() > 0) {
// Since there was no valid VBR header found, we hope that we're in a constant
// bitrate file.
// TODO: Make this more robust with audio property detection for VBR without a
// Xing header.
if(firstHeader.frameLength() > 0 && firstHeader.bitrate() > 0) {
int frames = static_cast<int>((last - first) / firstHeader.frameLength() + 1);
d->bitrate = firstHeader.bitrate();
d->length = int(float(firstHeader.frameLength() * frames) /
float(firstHeader.bitrate() * 125) + 0.5);
d->bitrate = firstHeader.bitrate();
}
offset_t streamLength = file->length() - first;
if(file->hasID3v1Tag())
streamLength -= 128;
if(file->hasAPETag())
streamLength -= file->APETag()->footer()->completeTagSize();
if(streamLength > 0)
d->length = static_cast<int>(streamLength * 8.0 / d->bitrate + 0.5);
}
d->sampleRate = firstHeader.sampleRate();
d->channels = firstHeader.channelMode() == Header::SingleChannel ? 1 : 2;
d->version = firstHeader.version();
d->layer = firstHeader.layer();
d->sampleRate = firstHeader.sampleRate();
d->channels = firstHeader.channelMode() == Header::SingleChannel ? 1 : 2;
d->version = firstHeader.version();
d->layer = firstHeader.layer();
d->protectionEnabled = firstHeader.protectionEnabled();
d->channelMode = firstHeader.channelMode();
d->isCopyrighted = firstHeader.isCopyrighted();
d->isOriginal = firstHeader.isOriginal();
d->channelMode = firstHeader.channelMode();
d->isCopyrighted = firstHeader.isCopyrighted();
d->isOriginal = firstHeader.isOriginal();
}

View File

@ -49,7 +49,7 @@ namespace TagLib {
{
public:
/*!
* Creates an instance of MPEG::AudioProperties with the data read from
* Creates an instance of MPEG::AudioProperties with the data read from
* the MPEG::File \a file.
*/
AudioProperties(File *file, ReadStyle style = Average);
@ -59,18 +59,52 @@ namespace TagLib {
*/
virtual ~AudioProperties();
// Reimplementations.
/*!
* Returns the length of the file in seconds. The length is rounded down to
* the nearest whole second.
*
* \note This method is just an alias of lengthInSeconds().
*
* \deprecated
*/
virtual int length() const;
/*!
* Returns the length of the file in seconds. The length is rounded down to
* the nearest whole second.
*
* \see lengthInMilliseconds()
*/
// BIC: make virtual
int lengthInSeconds() const;
/*!
* Returns the length of the file in milliseconds.
*
* \see lengthInSeconds()
*/
// BIC: make virtual
int lengthInMilliseconds() const;
/*!
* Returns the average bit rate of the file in kb/s.
*/
virtual int bitrate() const;
/*!
* Returns the sample rate in Hz.
*/
virtual int sampleRate() const;
/*!
* Returns the number of audio channels.
*/
virtual int channels() const;
/*!
* Returns a pointer to the XingHeader if one exists or null if no
* XingHeader was found.
* Returns a pointer to the Xing/VBRI header if one exists or null if no
* Xing/VBRI header was found.
*/
const XingHeader *xingHeader() const;
/*!

View File

@ -28,6 +28,7 @@
#include <tdebug.h>
#include "xingheader.h"
#include "mpegfile.h"
using namespace TagLib;
@ -37,17 +38,21 @@ public:
XingHeaderPrivate() :
frames(0),
size(0),
valid(false)
{}
type(MPEG::XingHeader::Invalid) {}
uint frames;
uint size;
bool valid;
MPEG::XingHeader::HeaderType type;
};
MPEG::XingHeader::XingHeader(const ByteVector &data)
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////
MPEG::XingHeader::XingHeader(const ByteVector &data) :
d(new XingHeaderPrivate())
{
d = new XingHeaderPrivate;
parse(data);
}
@ -58,7 +63,7 @@ MPEG::XingHeader::~XingHeader()
bool MPEG::XingHeader::isValid() const
{
return d->valid;
return (d->type != Invalid && d->frames > 0 && d->size > 0);
}
TagLib::uint MPEG::XingHeader::totalFrames() const
@ -71,45 +76,59 @@ TagLib::uint MPEG::XingHeader::totalSize() const
return d->size;
}
int MPEG::XingHeader::offset(TagLib::MPEG::Header::Version v,
TagLib::MPEG::Header::ChannelMode c)
MPEG::XingHeader::HeaderType MPEG::XingHeader::type() const
{
if(v == MPEG::Header::Version1) {
if(c == MPEG::Header::SingleChannel)
return 0x15;
else
return 0x24;
}
else {
if(c == MPEG::Header::SingleChannel)
return 0x0D;
else
return 0x15;
}
return d->type;
}
////////////////////////////////////////////////////////////////////////////////
// private members
////////////////////////////////////////////////////////////////////////////////
void MPEG::XingHeader::parse(const ByteVector &data)
{
// Check to see if a valid Xing header is available.
// Look for a Xing header.
if(!data.startsWith("Xing") && !data.startsWith("Info"))
return;
size_t offset = data.find("Xing");
if(offset == ByteVector::npos)
offset = data.find("Info");
// If the XingHeader doesn't contain the number of frames and the total stream
// info it's invalid.
if(offset != ByteVector::npos) {
if(!(data[7] & 0x01)) {
debug("MPEG::XingHeader::parse() -- Xing header doesn't contain the total number of frames.");
return;
// Xing header found.
if(data.size() < static_cast<ulong>(offset + 16)) {
debug("MPEG::XingHeader::parse() -- Xing header found but too short.");
return;
}
if((data[offset + 7] & 0x03) != 0x03) {
debug("MPEG::XingHeader::parse() -- Xing header doesn't contain the required information.");
return;
}
d->frames = data.toUInt32BE(offset + 8);
d->size = data.toUInt32BE(offset + 12);
d->type = Xing;
}
else {
if(!(data[7] & 0x02)) {
debug("MPEG::XingHeader::parse() -- Xing header doesn't contain the total stream size.");
return;
// Xing header not found. Then look for a VBRI header.
offset = data.find("VBRI");
if(offset != ByteVector::npos) {
// VBRI header found.
if(data.size() < static_cast<ulong>(offset + 32)) {
debug("MPEG::XingHeader::parse() -- VBRI header found but too short.");
return;
}
d->frames = data.toUInt32BE(offset + 14);
d->size = data.toUInt32BE(offset + 10);
d->type = VBRI;
}
}
d->frames = data.toUInt32BE(8);
d->size = data.toUInt32BE(12);
d->valid = true;
}

View File

@ -35,24 +35,47 @@ namespace TagLib {
namespace MPEG {
//! An implementation of the Xing VBR headers
class File;
//! An implementation of the Xing/VBRI headers
/*!
* This is a minimalistic implementation of the Xing VBR headers. Xing
* headers are often added to VBR (variable bit rate) MP3 streams to make it
* easy to compute the length and quality of a VBR stream. Our implementation
* is only concerned with the total size of the stream (so that we can
* calculate the total playing time and the average bitrate). It uses
* <a href="http://home.pcisys.net/~melanson/codecs/mp3extensions.txt">this text</a>
* and the XMMS sources as references.
* This is a minimalistic implementation of the Xing/VBRI VBR headers.
* Xing/VBRI headers are often added to VBR (variable bit rate) MP3 streams
* to make it easy to compute the length and quality of a VBR stream. Our
* implementation is only concerned with the total size of the stream (so
* that we can calculate the total playing time and the average bitrate).
* It uses <a href="http://home.pcisys.net/~melanson/codecs/mp3extensions.txt">
* this text</a> and the XMMS sources as references.
*/
class TAGLIB_EXPORT XingHeader
{
public:
/*!
* Parses a Xing header based on \a data. The data must be at least 16
* bytes long (anything longer than this is discarded).
* The type of the VBR header.
*/
enum HeaderType
{
/*!
* Invalid header or no VBR header found.
*/
Invalid = 0,
/*!
* Xing header.
*/
Xing = 1,
/*!
* VBRI header.
*/
VBRI = 2,
};
/*!
* Parses an Xing/VBRI header based on \a data which contains the entire
* first MPEG frame.
*/
XingHeader(const ByteVector &data);
@ -63,7 +86,7 @@ namespace TagLib {
/*!
* Returns true if the data was parsed properly and if there is a valid
* Xing header present.
* Xing/VBRI header present.
*/
bool isValid() const;
@ -78,11 +101,9 @@ namespace TagLib {
uint totalSize() const;
/*!
* Returns the offset for the start of this Xing header, given the
* version and channels of the frame
* Returns the type of the VBR header.
*/
static int offset(TagLib::MPEG::Header::Version v,
TagLib::MPEG::Header::ChannelMode c);
HeaderType type() const;
private:
XingHeader(const XingHeader &);

View File

@ -42,7 +42,7 @@ namespace TagLib {
/*!
* This is implementation of FLAC metadata for Ogg FLAC files. For "pure"
* FLAC files look under the FLAC hiearchy.
* FLAC files look under the FLAC hierarchy.
*
* Unlike "pure" FLAC-files, Ogg FLAC only supports Xiph-comments,
* while the audio-properties are the same.
@ -64,7 +64,7 @@ namespace TagLib {
{
public:
/*!
* Constructs an Ogg/FLAC file from \a file. If \a readProperties is true
* Constructs an Ogg/FLAC file from \a file. If \a readProperties is true
* the file's audio properties will also be read.
*
* \note In the current implementation, \a propertiesStyle is ignored.
@ -73,7 +73,7 @@ namespace TagLib {
AudioProperties::ReadStyle propertiesStyle = AudioProperties::Average);
/*!
* Constructs an Ogg/FLAC file from \a stream. If \a readProperties is true
* Constructs an Ogg/FLAC file from \a stream. If \a readProperties is true
* the file's audio properties will also be read.
*
* \note TagLib will *not* take ownership of the stream, the caller is
@ -92,10 +92,10 @@ namespace TagLib {
/*!
* Returns the Tag for this file. This will always be a XiphComment.
*
* \note This always returns a valid pointer regardless of whether or not
* the file on disk has a XiphComment. Use hasXiphComment() to check if
* \note This always returns a valid pointer regardless of whether or not
* the file on disk has a XiphComment. Use hasXiphComment() to check if
* the file on disk actually has a XiphComment.
*
*
* \note The Tag <b>is still</b> owned by the FLAC::File and should not be
* deleted by the user. It will be deleted when the file (object) is
* destroyed.
@ -111,22 +111,25 @@ namespace TagLib {
virtual AudioProperties *audioProperties() const;
/*!
/*!
* Implements the unified property interface -- export function.
* This forwards directly to XiphComment::properties().
*/
PropertyMap properties() const;
/*!
/*!
* Implements the unified tag dictionary interface -- import function.
* Like properties(), this is a forwarder to the file's XiphComment.
*/
PropertyMap setProperties(const PropertyMap &);
PropertyMap setProperties(const PropertyMap &);
/*!
* Save the file. This will primarily save and update the XiphComment.
* Returns true if the save is successful.
*
* \warning In the current implementation, it's dangerous to call save()
* repeatedly. It leads to a segfault.
*/
virtual bool save();

View File

@ -41,7 +41,7 @@ namespace TagLib {
/*!
* This is an implementation of the pages that make up an Ogg stream.
* This handles parsing pages and breaking them down into packets and handles
* the details of packets spanning multiple pages and pages that contiain
* the details of packets spanning multiple pages and pages that contain
* multiple packets.
*
* In most Xiph.org formats the comments are found in the first few packets,
@ -162,7 +162,7 @@ namespace TagLib {
/*!
* Pack \a packets into Ogg pages using the \a strategy for pagination.
* The page number indicater inside of the rendered packets will start
* The page number indicator inside of the rendered packets will start
* with \a firstPage and be incremented for each page rendered.
* \a containsLastPacket should be set to true if \a packets contains the
* last page in the stream and will set the appropriate flag in the last

View File

@ -27,8 +27,6 @@
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#include <bitset>
#include <tstring.h>
#include <tdebug.h>
#include <tpropertymap.h>
@ -59,20 +57,20 @@ public:
// public members
////////////////////////////////////////////////////////////////////////////////
Opus::File::File(FileName file, bool readProperties, AudioProperties::ReadStyle propertiesStyle) :
Opus::File::File(FileName file, bool readProperties, AudioProperties::ReadStyle) :
Ogg::File(file),
d(new FilePrivate())
{
if(isOpen())
read(readProperties, propertiesStyle);
read(readProperties);
}
Opus::File::File(IOStream *stream, bool readProperties, AudioProperties::ReadStyle propertiesStyle) :
Opus::File::File(IOStream *stream, bool readProperties, AudioProperties::ReadStyle) :
Ogg::File(stream),
d(new FilePrivate())
{
if(isOpen())
read(readProperties, propertiesStyle);
read(readProperties);
}
Opus::File::~File()
@ -114,7 +112,7 @@ bool Opus::File::save()
// private members
////////////////////////////////////////////////////////////////////////////////
void Opus::File::read(bool readProperties, AudioProperties::ReadStyle propertiesStyle)
void Opus::File::read(bool readProperties)
{
ByteVector opusHeaderData = packet(0);
@ -135,5 +133,5 @@ void Opus::File::read(bool readProperties, AudioProperties::ReadStyle properties
d->comment = new Ogg::XiphComment(commentHeaderData.mid(8));
if(readProperties)
d->properties = new AudioProperties(this, propertiesStyle);
d->properties = new AudioProperties(this);
}

View File

@ -106,13 +106,21 @@ namespace TagLib {
*/
virtual AudioProperties *audioProperties() const;
/*!
* Save the file.
*
* This returns true if the save was successful.
*
* \warning In the current implementation, it's dangerous to call save()
* repeatedly. It leads to a segfault.
*/
virtual bool save();
private:
File(const File &);
File &operator=(const File &);
void read(bool readProperties, AudioProperties::ReadStyle propertiesStyle);
void read(bool readProperties);
class FilePrivate;
FilePrivate *d;

View File

@ -58,7 +58,8 @@ public:
// public members
////////////////////////////////////////////////////////////////////////////////
Opus::AudioProperties::AudioProperties(File *file, ReadStyle style) :
Opus::AudioProperties::AudioProperties(File *file, ReadStyle) :
TagLib::AudioProperties(),
d(new PropertiesPrivate())
{
read(file);
@ -70,6 +71,16 @@ Opus::AudioProperties::~AudioProperties()
}
int Opus::AudioProperties::length() const
{
return lengthInSeconds();
}
int Opus::AudioProperties::lengthInSeconds() const
{
return d->length / 1000;
}
int Opus::AudioProperties::lengthInMilliseconds() const
{
return d->length;
}
@ -147,8 +158,13 @@ void Opus::AudioProperties::read(File *file)
const long long end = last->absoluteGranularPosition();
if(start >= 0 && end >= 0) {
d->length = (int)((end - start - preSkip) / 48000);
d->bitrate = (int)(file->length() * 8.0 / (1000.0 * d->length) + 0.5);
const long long frameCount = (end - start - preSkip);
if(frameCount > 0) {
const double length = frameCount * 1000.0 / 48000.0;
d->length = static_cast<int>(length + 0.5);
d->bitrate = static_cast<int>(file->length() * 8.0 / length + 0.5);
}
}
else {
debug("Opus::Properties::read() -- The PCM values for the start or "

View File

@ -51,7 +51,7 @@ namespace TagLib {
{
public:
/*!
* Creates an instance of Opus::AudioProperties with the data read from
* Creates an instance of Opus::AudioProperties with the data read from
* the Opus::File \a file.
*/
AudioProperties(File *file, ReadStyle style = Average);
@ -61,11 +61,49 @@ namespace TagLib {
*/
virtual ~AudioProperties();
// Reimplementations.
/*!
* Returns the length of the file in seconds. The length is rounded down to
* the nearest whole second.
*
* \note This method is just an alias of lengthInSeconds().
*
* \deprecated
*/
virtual int length() const;
/*!
* Returns the length of the file in seconds. The length is rounded down to
* the nearest whole second.
*
* \see lengthInMilliseconds()
*/
// BIC: make virtual
int lengthInSeconds() const;
/*!
* Returns the length of the file in milliseconds.
*
* \see lengthInSeconds()
*/
// BIC: make virtual
int lengthInMilliseconds() const;
/*!
* Returns the average bit rate of the file in kb/s.
*/
virtual int bitrate() const;
/*!
* Returns the sample rate in Hz.
*
* \note Always returns 48000, because Opus can decode any stream at a
* sample rate of 8, 12, 16, 24, or 48 kHz,
*/
virtual int sampleRate() const;
/*!
* Returns the number of audio channels.
*/
virtual int channels() const;
/*!
@ -76,7 +114,7 @@ namespace TagLib {
int inputSampleRate() const;
/*!
* Returns the Opus version, currently "0" (as specified by the spec).
* Returns the Opus version, in the range 0...255.
*/
int opusVersion() const;

View File

@ -27,8 +27,6 @@
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#include <bitset>
#include <tstring.h>
#include <tdebug.h>
#include <tpropertymap.h>
@ -59,20 +57,20 @@ public:
// public members
////////////////////////////////////////////////////////////////////////////////
Speex::File::File(FileName file, bool readProperties,
AudioProperties::ReadStyle propertiesStyle) : Ogg::File(file)
Speex::File::File(FileName file, bool readProperties, AudioProperties::ReadStyle) :
Ogg::File(file),
d(new FilePrivate())
{
d = new FilePrivate;
if(isOpen())
read(readProperties, propertiesStyle);
read(readProperties);
}
Speex::File::File(IOStream *stream, bool readProperties,
AudioProperties::ReadStyle propertiesStyle) : Ogg::File(stream)
Speex::File::File(IOStream *stream, bool readProperties, AudioProperties::ReadStyle) :
Ogg::File(stream),
d(new FilePrivate())
{
d = new FilePrivate;
if(isOpen())
read(readProperties, propertiesStyle);
read(readProperties);
}
Speex::File::~File()
@ -114,7 +112,7 @@ bool Speex::File::save()
// private members
////////////////////////////////////////////////////////////////////////////////
void Speex::File::read(bool readProperties, AudioProperties::ReadStyle propertiesStyle)
void Speex::File::read(bool readProperties)
{
ByteVector speexHeaderData = packet(0);
@ -128,5 +126,5 @@ void Speex::File::read(bool readProperties, AudioProperties::ReadStyle propertie
d->comment = new Ogg::XiphComment(commentHeaderData);
if(readProperties)
d->properties = new AudioProperties(this, propertiesStyle);
d->properties = new AudioProperties(this);
}

View File

@ -106,15 +106,21 @@ namespace TagLib {
*/
virtual AudioProperties *audioProperties() const;
/*!
* Save the file.
*
* This returns true if the save was successful.
*
* \warning In the current implementation, it's dangerous to call save()
* repeatedly. It leads to a segfault.
*/
virtual bool save();
private:
File(const File &);
File &operator=(const File &);
void read(bool readProperties, AudioProperties::ReadStyle propertiesStyle);
void read(bool readProperties);
class FilePrivate;
FilePrivate *d;

View File

@ -43,6 +43,7 @@ public:
PropertiesPrivate() :
length(0),
bitrate(0),
bitrateNominal(0),
sampleRate(0),
channels(0),
speexVersion(0),
@ -51,6 +52,7 @@ public:
int length;
int bitrate;
int bitrateNominal;
int sampleRate;
int channels;
int speexVersion;
@ -62,7 +64,8 @@ public:
// public members
////////////////////////////////////////////////////////////////////////////////
Speex::AudioProperties::AudioProperties(File *file, ReadStyle style) :
Speex::AudioProperties::AudioProperties(File *file, ReadStyle) :
TagLib::AudioProperties(),
d(new PropertiesPrivate())
{
read(file);
@ -74,13 +77,28 @@ Speex::AudioProperties::~AudioProperties()
}
int Speex::AudioProperties::length() const
{
return lengthInSeconds();
}
int Speex::AudioProperties::lengthInSeconds() const
{
return d->length / 1000;
}
int Speex::AudioProperties::lengthInMilliseconds() const
{
return d->length;
}
int Speex::AudioProperties::bitrate() const
{
return int(float(d->bitrate) / float(1000) + 0.5);
return d->bitrate;
}
int Speex::AudioProperties::bitrateNominal() const
{
return d->bitrateNominal;
}
int Speex::AudioProperties::sampleRate() const
@ -107,6 +125,10 @@ void Speex::AudioProperties::read(File *file)
// Get the identification header from the Ogg implementation.
const ByteVector data = file->packet(0);
if(data.size() < 64) {
debug("Speex::Properties::read() -- data is too short.");
return;
}
size_t pos = 28;
@ -133,7 +155,7 @@ void Speex::AudioProperties::read(File *file)
pos += 4;
// bitrate; /**< Bit-rate used */
d->bitrate = data.toUInt32LE(pos);
d->bitrateNominal = data.toUInt32LE(pos);
pos += 4;
// frame_size; /**< Size of frames */
@ -154,12 +176,25 @@ void Speex::AudioProperties::read(File *file)
const long long start = first->absoluteGranularPosition();
const long long end = last->absoluteGranularPosition();
if(start >= 0 && end >= 0 && d->sampleRate > 0)
d->length = (int) ((end - start) / (long long) d->sampleRate);
else
if(start >= 0 && end >= 0 && d->sampleRate > 0) {
const long long frameCount = end - start;
if(frameCount > 0) {
const double length = frameCount * 1000.0 / d->sampleRate;
d->length = static_cast<int>(length + 0.5);
d->bitrate = static_cast<int>(file->length() * 8.0 / length + 0.5);
}
}
else {
debug("Speex::Properties::read() -- Either the PCM values for the start or "
"end of this file was incorrect or the sample rate is zero.");
}
}
else
debug("Speex::Properties::read() -- Could not find valid first and last Ogg pages.");
// Alternative to the actual average bitrate.
if(d->bitrate == 0 && d->bitrateNominal > 0)
d->bitrate = static_cast<int>(d->bitrateNominal / 1000.0 + 0.5);
}

View File

@ -51,7 +51,7 @@ namespace TagLib {
{
public:
/*!
* Creates an instance of Speex::AudioProperties with the data read from
* Creates an instance of Speex::AudioProperties with the data read from
* the Speex::File \a file.
*/
AudioProperties(File *file, ReadStyle style = Average);
@ -61,11 +61,51 @@ namespace TagLib {
*/
virtual ~AudioProperties();
// Reimplementations.
/*!
* Returns the length of the file in seconds. The length is rounded down to
* the nearest whole second.
*
* \note This method is just an alias of lengthInSeconds().
*
* \deprecated
*/
virtual int length() const;
/*!
* Returns the length of the file in seconds. The length is rounded down to
* the nearest whole second.
*
* \see lengthInMilliseconds()
*/
// BIC: make virtual
int lengthInSeconds() const;
/*!
* Returns the length of the file in milliseconds.
*
* \see lengthInSeconds()
*/
// BIC: make virtual
int lengthInMilliseconds() const;
/*!
* Returns the average bit rate of the file in kb/s.
*/
virtual int bitrate() const;
/*!
* Returns the nominal bit rate as read from the Speex header in kb/s.
*/
int bitrateNominal() const;
/*!
* Returns the sample rate in Hz.
*/
virtual int sampleRate() const;
/*!
* Returns the number of audio channels.
*/
virtual int channels() const;
/*!

View File

@ -63,20 +63,20 @@ namespace TagLib {
// public members
////////////////////////////////////////////////////////////////////////////////
Ogg::Vorbis::File::File(FileName file, bool readProperties,
AudioProperties::ReadStyle propertiesStyle) : Ogg::File(file)
Ogg::Vorbis::File::File(FileName file, bool readProperties, AudioProperties::ReadStyle) :
Ogg::File(file),
d(new FilePrivate())
{
d = new FilePrivate;
if(isOpen())
read(readProperties, propertiesStyle);
read(readProperties);
}
Ogg::Vorbis::File::File(IOStream *stream, bool readProperties,
AudioProperties::ReadStyle propertiesStyle) : Ogg::File(stream)
Ogg::Vorbis::File::File(IOStream *stream, bool readProperties, AudioProperties::ReadStyle) :
Ogg::File(stream),
d(new FilePrivate())
{
d = new FilePrivate;
if(isOpen())
read(readProperties, propertiesStyle);
read(readProperties);
}
Ogg::Vorbis::File::~File()
@ -121,7 +121,7 @@ bool Ogg::Vorbis::File::save()
// private members
////////////////////////////////////////////////////////////////////////////////
void Ogg::Vorbis::File::read(bool readProperties, AudioProperties::ReadStyle propertiesStyle)
void Ogg::Vorbis::File::read(bool readProperties)
{
ByteVector commentHeaderData = packet(1);
@ -134,5 +134,5 @@ void Ogg::Vorbis::File::read(bool readProperties, AudioProperties::ReadStyle pro
d->comment = new Ogg::XiphComment(commentHeaderData.mid(7));
if(readProperties)
d->properties = new AudioProperties(this, propertiesStyle);
d->properties = new AudioProperties(this);
}

View File

@ -105,13 +105,21 @@ namespace TagLib {
*/
virtual AudioProperties *audioProperties() const;
/*!
* Save the file.
*
* This returns true if the save was successful.
*
* \warning In the current implementation, it's dangerous to call save()
* repeatedly. It leads to a segfault.
*/
virtual bool save();
private:
File(const File &);
File &operator=(const File &);
void read(bool readProperties, AudioProperties::ReadStyle propertiesStyle);
void read(bool readProperties);
class FilePrivate;
FilePrivate *d;

View File

@ -68,7 +68,8 @@ namespace TagLib {
// public members
////////////////////////////////////////////////////////////////////////////////
Ogg::Vorbis::AudioProperties::AudioProperties(File *file, ReadStyle style) :
Ogg::Vorbis::AudioProperties::AudioProperties(File *file, ReadStyle) :
TagLib::AudioProperties(),
d(new PropertiesPrivate())
{
read(file);
@ -80,13 +81,23 @@ Ogg::Vorbis::AudioProperties::~AudioProperties()
}
int Ogg::Vorbis::AudioProperties::length() const
{
return lengthInSeconds();
}
int Ogg::Vorbis::AudioProperties::lengthInSeconds() const
{
return d->length / 1000;
}
int Ogg::Vorbis::AudioProperties::lengthInMilliseconds() const
{
return d->length;
}
int Ogg::Vorbis::AudioProperties::bitrate() const
{
return int(float(d->bitrate) / float(1000) + 0.5);
return d->bitrate;
}
int Ogg::Vorbis::AudioProperties::sampleRate() const
@ -137,6 +148,10 @@ void Ogg::Vorbis::AudioProperties::read(File *file)
// Get the identification header from the Ogg implementation.
const ByteVector data = file->packet(0);
if(data.size() < 28) {
debug("Vorbis::Properties::read() -- data is too short.");
return;
}
size_t pos = 0;
@ -163,9 +178,7 @@ void Ogg::Vorbis::AudioProperties::read(File *file)
pos += 4;
d->bitrateMinimum = data.toUInt32LE(pos);
// TODO: Later this should be only the "fast" mode.
d->bitrate = d->bitrateNominal;
pos += 4;
// Find the length of the file. See http://wiki.xiph.org/Ogg::VorbisStreamLength/
// for my notes on the topic.
@ -177,12 +190,26 @@ void Ogg::Vorbis::AudioProperties::read(File *file)
const long long start = first->absoluteGranularPosition();
const long long end = last->absoluteGranularPosition();
if(start >= 0 && end >= 0 && d->sampleRate > 0)
d->length = (int)((end - start) / (long long) d->sampleRate);
else
debug("Ogg::Vorbis::Properties::read() -- Either the PCM values for the start or "
if(start >= 0 && end >= 0 && d->sampleRate > 0) {
const long long frameCount = end - start;
if(frameCount > 0) {
const double length = frameCount * 1000.0 / d->sampleRate;
d->length = static_cast<int>(length + 0.5);
d->bitrate = static_cast<int>(file->length() * 8.0 / length + 0.5);
}
}
else {
debug("Vorbis::Properties::read() -- Either the PCM values for the start or "
"end of this file was incorrect or the sample rate is zero.");
}
}
else
debug("Ogg::Vorbis::Properties::read() -- Could not find valid first and last Ogg pages.");
debug("Vorbis::Properties::read() -- Could not find valid first and last Ogg pages.");
// Alternative to the actual average bitrate.
if(d->bitrate == 0 && d->bitrateNominal > 0)
d->bitrate = static_cast<int>(d->bitrateNominal / 1000.0 + 0.5);
}

View File

@ -58,11 +58,46 @@ namespace TagLib {
*/
virtual ~AudioProperties();
// Reimplementations.
/*!
* Returns the length of the file in seconds. The length is rounded down to
* the nearest whole second.
*
* \note This method is just an alias of lengthInSeconds().
*
* \deprecated
*/
virtual int length() const;
/*!
* Returns the length of the file in seconds. The length is rounded down to
* the nearest whole second.
*
* \see lengthInMilliseconds()
*/
// BIC: make virtual
int lengthInSeconds() const;
/*!
* Returns the length of the file in milliseconds.
*
* \see lengthInSeconds()
*/
// BIC: make virtual
int lengthInMilliseconds() const;
/*!
* Returns the average bit rate of the file in kb/s.
*/
virtual int bitrate() const;
/*!
* Returns the sample rate in Hz.
*/
virtual int sampleRate() const;
/*!
* Returns the number of audio channels.
*/
virtual int channels() const;
virtual String toString() const;

View File

@ -40,10 +40,7 @@ public:
properties(0),
tag(0),
tagChunkID("ID3 "),
hasID3v2(false)
{
}
hasID3v2(false) {}
~FilePrivate()
{
@ -62,20 +59,20 @@ public:
// public members
////////////////////////////////////////////////////////////////////////////////
RIFF::AIFF::File::File(FileName file, bool readProperties,
AudioProperties::ReadStyle propertiesStyle) : RIFF::File(file, BigEndian)
RIFF::AIFF::File::File(FileName file, bool readProperties, AudioProperties::ReadStyle) :
RIFF::File(file, BigEndian),
d(new FilePrivate())
{
d = new FilePrivate;
if(isOpen())
read(readProperties, propertiesStyle);
read(readProperties);
}
RIFF::AIFF::File::File(IOStream *stream, bool readProperties,
AudioProperties::ReadStyle propertiesStyle) : RIFF::File(stream, BigEndian)
RIFF::AIFF::File::File(IOStream *stream, bool readProperties, AudioProperties::ReadStyle) :
RIFF::File(stream, BigEndian),
d(new FilePrivate())
{
d = new FilePrivate;
if(isOpen())
read(readProperties, propertiesStyle);
read(readProperties);
}
RIFF::AIFF::File::~File()
@ -135,18 +132,25 @@ bool RIFF::AIFF::File::hasID3v2Tag() const
// private members
////////////////////////////////////////////////////////////////////////////////
void RIFF::AIFF::File::read(bool readProperties, AudioProperties::ReadStyle propertiesStyle)
void RIFF::AIFF::File::read(bool readProperties)
{
for(uint i = 0; i < chunkCount(); i++) {
if(chunkName(i) == "ID3 " || chunkName(i) == "id3 ") {
d->tagChunkID = chunkName(i);
d->tag = new ID3v2::Tag(this, chunkOffset(i));
d->hasID3v2 = true;
for(uint i = 0; i < chunkCount(); ++i) {
const ByteVector name = chunkName(i);
if(name == "ID3 " || name == "id3 ") {
if(!d->tag) {
d->tag = new ID3v2::Tag(this, chunkOffset(i));
d->tagChunkID = name;
d->hasID3v2 = true;
}
else {
debug("RIFF::AIFF::File::read() - Duplicate ID3v2 tag found.");
}
}
else if(chunkName(i) == "COMM" && readProperties)
d->properties = new AudioProperties(chunkData(i), propertiesStyle);
}
if(!d->tag)
d->tag = new ID3v2::Tag;
d->tag = new ID3v2::Tag();
if(readProperties)
d->properties = new AudioProperties(this, AudioProperties::Average);
}

View File

@ -86,8 +86,8 @@ namespace TagLib {
/*!
* Returns the Tag for this file.
*
* \note This always returns a valid pointer regardless of whether or not
* the file on disk has an ID3v2 tag. Use hasID3v2Tag() to check if the file
* \note This always returns a valid pointer regardless of whether or not
* the file on disk has an ID3v2 tag. Use hasID3v2Tag() to check if the file
* on disk actually has an ID3v2 tag.
*
* \see hasID3v2Tag()
@ -130,7 +130,9 @@ namespace TagLib {
File(const File &);
File &operator=(const File &);
void read(bool readProperties, AudioProperties::ReadStyle propertiesStyle);
void read(bool readProperties);
friend class AudioProperties;
class FilePrivate;
FilePrivate *d;

View File

@ -25,6 +25,7 @@
#include <tstring.h>
#include <tdebug.h>
#include "aifffile.h"
#include "aiffproperties.h"
using namespace TagLib;
@ -37,14 +38,14 @@ public:
bitrate(0),
sampleRate(0),
channels(0),
sampleWidth(0),
bitsPerSample(0),
sampleFrames(0) {}
int length;
int bitrate;
int sampleRate;
int channels;
int sampleWidth;
int bitsPerSample;
ByteVector compressionType;
String compressionName;
@ -56,10 +57,18 @@ public:
// public members
////////////////////////////////////////////////////////////////////////////////
RIFF::AIFF::AudioProperties::AudioProperties(const ByteVector &data, ReadStyle style) :
RIFF::AIFF::AudioProperties::AudioProperties(const ByteVector &, ReadStyle) :
TagLib::AudioProperties(),
d(new PropertiesPrivate())
{
read(data);
debug("RIFF::AIFF::Properties::Properties() - This constructor is no longer used.");
}
RIFF::AIFF::AudioProperties::AudioProperties(File *file, ReadStyle) :
TagLib::AudioProperties(),
d(new PropertiesPrivate())
{
read(file);
}
RIFF::AIFF::AudioProperties::~AudioProperties()
@ -73,6 +82,16 @@ bool RIFF::AIFF::AudioProperties::isNull() const
}
int RIFF::AIFF::AudioProperties::length() const
{
return lengthInSeconds();
}
int RIFF::AIFF::AudioProperties::lengthInSeconds() const
{
return d->length / 1000;
}
int RIFF::AIFF::AudioProperties::lengthInMilliseconds() const
{
return d->length;
}
@ -92,9 +111,14 @@ int RIFF::AIFF::AudioProperties::channels() const
return d->channels;
}
int RIFF::AIFF::AudioProperties::bitsPerSample() const
{
return d->bitsPerSample;
}
int RIFF::AIFF::AudioProperties::sampleWidth() const
{
return d->sampleWidth;
return bitsPerSample();
}
TagLib::uint RIFF::AIFF::AudioProperties::sampleFrames() const
@ -121,24 +145,52 @@ String RIFF::AIFF::AudioProperties::compressionName() const
// private members
////////////////////////////////////////////////////////////////////////////////
void RIFF::AIFF::AudioProperties::read(const ByteVector &data)
void RIFF::AIFF::AudioProperties::read(File *file)
{
ByteVector data;
uint streamLength = 0;
for(uint i = 0; i < file->chunkCount(); i++) {
const ByteVector name = file->chunkName(i);
if(name == "COMM") {
if(data.isEmpty())
data = file->chunkData(i);
else
debug("RIFF::AIFF::Properties::read() - Duplicate 'COMM' chunk found.");
}
else if(name == "SSND") {
if(streamLength == 0)
streamLength = file->chunkDataSize(i) + file->chunkPadding(i);
else
debug("RIFF::AIFF::Properties::read() - Duplicate 'SSND' chunk found.");
}
}
if(data.size() < 18) {
debug("RIFF::AIFF::AudioProperties::read() - \"COMM\" chunk is too short for AIFF.");
debug("RIFF::AIFF::Properties::read() - 'COMM' chunk not found or too short.");
return;
}
d->channels = data.toInt16BE(0);
d->sampleFrames = data.toUInt32BE(2);
d->sampleWidth = data.toInt16BE(6);
if(streamLength == 0) {
debug("RIFF::AIFF::Properties::read() - 'SSND' chunk not found.");
return;
}
d->channels = data.toUInt16BE(0);
d->sampleFrames = data.toUInt32BE(2);
d->bitsPerSample = data.toUInt16BE(6);
const long double sampleRate = data.toFloat80BE(8);
d->sampleRate = static_cast<int>(sampleRate);
d->bitrate = static_cast<int>((sampleRate * d->sampleWidth * d->channels) / 1000.0);
d->length = d->sampleRate > 0 ? d->sampleFrames / d->sampleRate : 0;
if(sampleRate >= 1.0)
d->sampleRate = static_cast<int>(sampleRate + 0.5);
if(d->sampleFrames > 0 && d->sampleRate > 0) {
const double length = d->sampleFrames * 1000.0 / d->sampleRate;
d->length = static_cast<int>(length + 0.5);
d->bitrate = static_cast<int>(streamLength * 8.0 / length + 0.5);
}
if(data.size() >= 23) {
d->compressionType = data.mid(18, 4);
d->compressionName = String(data.mid(23, static_cast<uchar>(data[22])));
d->compressionName = String(data.mid(23, static_cast<uchar>(data[22])), String::Latin1);
}
}

View File

@ -47,11 +47,19 @@ namespace TagLib {
{
public:
/*!
* Creates an instance of AIFF::AudioProperties with the data read from
* Create an instance of AIFF::AudioProperties with the data read from
* the ByteVector \a data.
*
* \deprecated
*/
AudioProperties(const ByteVector &data, ReadStyle style);
/*!
* Create an instance of AIFF::AudioProperties with the data read from
* the AIFF::File \a file.
*/
AudioProperties(File *file, ReadStyle style);
/*!
* Destroys this AIFF::AudioProperties instance.
*/
@ -59,14 +67,65 @@ namespace TagLib {
virtual bool isNull() const;
// Reimplementations.
/*!
* Returns the length of the file in seconds. The length is rounded down to
* the nearest whole second.
*
* \note This method is just an alias of lengthInSeconds().
*
* \deprecated
*/
virtual int length() const;
/*!
* Returns the length of the file in seconds. The length is rounded down to
* the nearest whole second.
*
* \see lengthInMilliseconds()
*/
// BIC: make virtual
int lengthInSeconds() const;
/*!
* Returns the length of the file in milliseconds.
*
* \see lengthInSeconds()
*/
// BIC: make virtual
int lengthInMilliseconds() const;
/*!
* Returns the average bit rate of the file in kb/s.
*/
virtual int bitrate() const;
/*!
* Returns the sample rate in Hz.
*/
virtual int sampleRate() const;
/*!
* Returns the number of audio channels.
*/
virtual int channels() const;
/*!
* Returns the number of bits per audio sample.
*/
int bitsPerSample() const;
/*!
* Returns the number of bits per audio sample.
*
* \note This method is just an alias of bitsPerSample().
*
* \deprecated
*/
int sampleWidth() const;
/*!
* Returns the number of sample frames
*/
uint sampleFrames() const;
/*!
@ -94,7 +153,7 @@ namespace TagLib {
String compressionName() const;
private:
void read(const ByteVector &data);
void read(File *file);
class PropertiesPrivate;
PropertiesPrivate *d;

View File

@ -144,15 +144,7 @@ ByteVector RIFF::File::chunkData(uint i)
if(i >= chunkCount())
return ByteVector::null;
// Offset for the first subchunk's data
long begin = 12 + 8;
for(uint it = 0; it < i; it++)
begin += 8 + d->chunks[it].size + d->chunks[it].padding;
seek(begin);
seek(d->chunks[i].offset);
return readBlock(d->chunks[i].size);
}

View File

@ -99,7 +99,7 @@ namespace TagLib {
ByteVector chunkData(uint i);
/*!
* Sets the data for the the specified chunk to \a data.
* Sets the data for the specified chunk to \a data.
*
* \warning This will update the file immediately.
*/
@ -119,9 +119,9 @@ namespace TagLib {
* given name already exists it will be overwritten, otherwise it will be
* created after the existing chunks.
*
* \note If \a alwaysCreate is true, a new chunk is created regardless of
* whether or not the chunk \a name exists. It should only be used for
* "LIST" chunks.
* \note If \a alwaysCreate is true, a new chunk is created regardless of
* whether or not the chunk \a name exists. It should only be used for
* "LIST" chunks.
*
* \warning This will update the file immediately.
*/

View File

@ -38,7 +38,7 @@ namespace TagLib {
class File;
//! A RIFF INFO tag implementation.
//! A RIFF INFO tag implementation.
namespace RIFF {
namespace Info {
@ -52,8 +52,8 @@ namespace TagLib {
* In practice, local encoding of each system is largely used and UTF-8 is
* popular too.
*
* Here is an option to read and write tags in your preferrd encoding
* by subclassing this class, reimplementing parse() and render() and setting
* Here is an option to read and write tags in your preferrd encoding
* by subclassing this class, reimplementing parse() and render() and setting
* your reimplementation as the default with Info::Tag::setStringHandler().
*
* \see ID3v1::Tag::setStringHandler()
@ -72,7 +72,7 @@ namespace TagLib {
/*!
* Encode a ByteVector with the data from \a s. The default implementation
* assumes that \a s is an UTF-8 string.
* assumes that \a s is an UTF-8 string.
*/
virtual ByteVector render(const String &s) const;
};
@ -80,11 +80,11 @@ namespace TagLib {
//! The main class in the RIFF INFO tag implementation
/*!
* This is the main class in the INFO tag implementation. RIFF INFO tag is
* a metadata format found in WAV audio and AVI video files. Though it is a
* part of Microsoft/IBM's RIFF specification, the author could not find the
* official documents about it. So, this implementation is referring to
* unofficial documents on the web and some applications' behaviors especially
* This is the main class in the INFO tag implementation. RIFF INFO tag is
* a metadata format found in WAV audio and AVI video files. Though it is a
* part of Microsoft/IBM's RIFF specification, the author could not find the
* official documents about it. So, this implementation is referring to
* unofficial documents on the web and some applications' behaviors especially
* Windows Explorer.
*/
class TAGLIB_EXPORT Tag : public TagLib::Tag
@ -123,7 +123,7 @@ namespace TagLib {
virtual bool isEmpty() const;
/*!
* Returns a copy of the internal fields of the tag. The returned map directly
* Returns a copy of the internal fields of the tag. The returned map directly
* reflects the contents of the "INFO" chunk.
*
* \note Modifying this map does not affect the tag's internal data.
@ -138,13 +138,13 @@ namespace TagLib {
* Gets the value of the field with the ID \a id.
*/
String fieldText(const ByteVector &id) const;
/*
* Sets the value of the field with the ID \a id to \a s.
* If the field does not exist, it is created.
* If \s is empty, the field is removed.
*
* \note fieldId must be four-byte long pure ASCII string. This function
* \note fieldId must be four-byte long pure ASCII string. This function
* performs nothing if fieldId is invalid.
*/
void setFieldText(const ByteVector &id, const String &s);
@ -157,7 +157,7 @@ namespace TagLib {
/*!
* Render the tag back to binary data, suitable to be written to disk.
*
* \note Returns empty ByteVector is the tag contains no fields.
* \note Returns empty ByteVector is the tag contains no fields.
*/
ByteVector render() const;
@ -173,14 +173,13 @@ namespace TagLib {
* \see StringHandler
*/
static void setStringHandler(const TagLib::StringHandler *handler);
protected:
/*!
* Pareses the body of the tag in \a data.
*/
void parse(const ByteVector &data);
private:
Tag(const Tag &);
Tag &operator=(const Tag &);

View File

@ -70,20 +70,20 @@ public:
// public members
////////////////////////////////////////////////////////////////////////////////
RIFF::WAV::File::File(FileName file, bool readProperties,
AudioProperties::ReadStyle propertiesStyle) : RIFF::File(file, LittleEndian)
RIFF::WAV::File::File(FileName file, bool readProperties, AudioProperties::ReadStyle) :
RIFF::File(file, LittleEndian),
d(new FilePrivate())
{
d = new FilePrivate;
if(isOpen())
read(readProperties, propertiesStyle);
read(readProperties);
}
RIFF::WAV::File::File(IOStream *stream, bool readProperties,
AudioProperties::ReadStyle propertiesStyle) : RIFF::File(stream, LittleEndian)
RIFF::WAV::File::File(IOStream *stream, bool readProperties, AudioProperties::ReadStyle) :
RIFF::File(stream, LittleEndian),
d(new FilePrivate())
{
d = new FilePrivate;
if(isOpen())
read(readProperties, propertiesStyle);
read(readProperties);
}
RIFF::WAV::File::~File()
@ -189,40 +189,42 @@ bool RIFF::WAV::File::hasInfoTag() const
// private members
////////////////////////////////////////////////////////////////////////////////
void RIFF::WAV::File::read(bool readProperties, AudioProperties::ReadStyle propertiesStyle)
void RIFF::WAV::File::read(bool readProperties)
{
ByteVector formatData;
uint streamLength = 0;
for(uint i = 0; i < chunkCount(); i++) {
String name = chunkName(i);
for(uint i = 0; i < chunkCount(); ++i) {
const ByteVector name = chunkName(i);
if(name == "ID3 " || name == "id3 ") {
d->tagChunkID = chunkName(i);
d->tag.set(ID3v2Index, new ID3v2::Tag(this, chunkOffset(i)));
d->hasID3v2 = true;
if(!d->tag[ID3v2Index]) {
d->tagChunkID = name;
d->tag.set(ID3v2Index, new ID3v2::Tag(this, chunkOffset(i)));
d->hasID3v2 = true;
}
else {
debug("RIFF::WAV::File::read() - Duplicate ID3v2 tag found.");
}
}
else if(name == "fmt " && readProperties)
formatData = chunkData(i);
else if(name == "data" && readProperties)
streamLength = chunkDataSize(i);
else if(name == "LIST") {
ByteVector data = chunkData(i);
ByteVector type = data.mid(0, 4);
if(type == "INFO") {
d->tag.set(InfoIndex, new RIFF::Info::Tag(data));
d->hasInfo = true;
const ByteVector data = chunkData(i);
if(data.startsWith("INFO")) {
if(!d->tag[InfoIndex]) {
d->tag.set(InfoIndex, new RIFF::Info::Tag(data));
d->hasInfo = true;
}
else {
debug("RIFF::WAV::File::read() - Duplicate INFO tag found.");
}
}
}
}
if (!d->tag[ID3v2Index])
d->tag.set(ID3v2Index, new ID3v2::Tag);
if(!d->tag[ID3v2Index])
d->tag.set(ID3v2Index, new ID3v2::Tag());
if (!d->tag[InfoIndex])
d->tag.set(InfoIndex, new RIFF::Info::Tag);
if(!d->tag[InfoIndex])
d->tag.set(InfoIndex, new RIFF::Info::Tag());
if(!formatData.isEmpty())
d->properties = new AudioProperties(formatData, streamLength, propertiesStyle);
if(readProperties)
d->properties = new AudioProperties(this, AudioProperties::Average);
}
void RIFF::WAV::File::strip(TagTypes tags)
@ -244,7 +246,7 @@ void RIFF::WAV::File::strip(TagTypes tags)
TagLib::uint RIFF::WAV::File::findInfoTagChunk()
{
for(uint i = 0; i < chunkCount(); ++i) {
if(chunkName(i) == "LIST" && chunkData(i).mid(0, 4) == "INFO") {
if(chunkName(i) == "LIST" && chunkData(i).startsWith("INFO")) {
return i;
}
}

View File

@ -104,8 +104,8 @@ namespace TagLib {
/*!
* Returns the ID3v2 Tag for this file.
*
* \note This always returns a valid pointer regardless of whether or not
* the file on disk has an ID3v2 tag. Use hasID3v2Tag() to check if the
* \note This always returns a valid pointer regardless of whether or not
* the file on disk has an ID3v2 tag. Use hasID3v2Tag() to check if the
* file on disk actually has an ID3v2 tag.
*
* \see hasID3v2Tag()
@ -115,8 +115,8 @@ namespace TagLib {
/*!
* Returns the RIFF INFO Tag for this file.
*
* \note This always returns a valid pointer regardless of whether or not
* the file on disk has a RIFF INFO tag. Use hasInfoTag() to check if the
* \note This always returns a valid pointer regardless of whether or not
* the file on disk has a RIFF INFO tag. Use hasInfoTag() to check if the
* file on disk actually has a RIFF INFO tag.
*
* \see hasInfoTag()
@ -149,7 +149,7 @@ namespace TagLib {
virtual bool save();
bool save(TagTypes tags, bool stripOthers = true, int id3v2Version = 4);
/*!
* Returns whether or not the file on disk actually has an ID3v2 tag.
*
@ -168,7 +168,7 @@ namespace TagLib {
File(const File &);
File &operator=(const File &);
void read(bool readProperties, AudioProperties::ReadStyle propertiesStyle);
void read(bool readProperties);
void strip(TagTypes tags);
@ -177,6 +177,8 @@ namespace TagLib {
*/
uint findInfoTagChunk();
friend class AudioProperties;
class FilePrivate;
FilePrivate *d;
};

View File

@ -25,10 +25,22 @@
#include <tstring.h>
#include <tdebug.h>
#include "wavfile.h"
#include "wavproperties.h"
using namespace TagLib;
namespace
{
// Quoted from RFC 2361.
enum WaveFormat
{
FORMAT_UNKNOWN = 0x0000,
FORMAT_PCM = 0x0001
};
}
class RIFF::WAV::AudioProperties::PropertiesPrivate
{
public:
@ -38,15 +50,15 @@ public:
bitrate(0),
sampleRate(0),
channels(0),
sampleWidth(0),
bitsPerSample(0),
sampleFrames(0) {}
short format;
int format;
int length;
int bitrate;
int sampleRate;
int channels;
int sampleWidth;
int bitsPerSample;
uint sampleFrames;
};
@ -54,11 +66,25 @@ public:
// public members
////////////////////////////////////////////////////////////////////////////////
RIFF::WAV::AudioProperties::AudioProperties(const ByteVector &data, uint streamLength,
ReadStyle style) :
RIFF::WAV::AudioProperties::AudioProperties(const ByteVector &, ReadStyle) :
TagLib::AudioProperties(),
d(new PropertiesPrivate())
{
read(data, streamLength);
debug("RIFF::WAV::Properties::Properties() -- This constructor is no longer used.");
}
RIFF::WAV::AudioProperties::AudioProperties(const ByteVector &, uint, ReadStyle) :
TagLib::AudioProperties(),
d(new PropertiesPrivate())
{
debug("RIFF::WAV::Properties::Properties() -- This constructor is no longer used.");
}
TagLib::RIFF::WAV::AudioProperties::AudioProperties(File *file, ReadStyle) :
TagLib::AudioProperties(),
d(new PropertiesPrivate())
{
read(file);
}
RIFF::WAV::AudioProperties::~AudioProperties()
@ -67,6 +93,16 @@ RIFF::WAV::AudioProperties::~AudioProperties()
}
int RIFF::WAV::AudioProperties::length() const
{
return lengthInSeconds();
}
int RIFF::WAV::AudioProperties::lengthInSeconds() const
{
return d->length / 1000;
}
int RIFF::WAV::AudioProperties::lengthInMilliseconds() const
{
return d->length;
}
@ -86,9 +122,14 @@ int RIFF::WAV::AudioProperties::channels() const
return d->channels;
}
int RIFF::WAV::AudioProperties::bitsPerSample() const
{
return d->bitsPerSample;
}
int RIFF::WAV::AudioProperties::sampleWidth() const
{
return d->sampleWidth;
return bitsPerSample();
}
TagLib::uint RIFF::WAV::AudioProperties::sampleFrames() const
@ -96,7 +137,7 @@ TagLib::uint RIFF::WAV::AudioProperties::sampleFrames() const
return d->sampleFrames;
}
TagLib::uint RIFF::WAV::AudioProperties::format() const
int RIFF::WAV::AudioProperties::format() const
{
return d->format;
}
@ -105,24 +146,69 @@ TagLib::uint RIFF::WAV::AudioProperties::format() const
// private members
////////////////////////////////////////////////////////////////////////////////
void RIFF::WAV::AudioProperties::read(const ByteVector &data, uint streamLength)
void RIFF::WAV::AudioProperties::read(File *file)
{
ByteVector data;
uint streamLength = 0;
uint totalSamples = 0;
for(uint i = 0; i < file->chunkCount(); ++i) {
const ByteVector name = file->chunkName(i);
if(name == "fmt ") {
if(data.isEmpty())
data = file->chunkData(i);
else
debug("RIFF::WAV::Properties::read() - Duplicate 'fmt ' chunk found.");
}
else if(name == "data") {
if(streamLength == 0)
streamLength = file->chunkDataSize(i) + file->chunkPadding(i);
else
debug("RIFF::WAV::Properties::read() - Duplicate 'data' chunk found.");
}
else if(name == "fact") {
if(totalSamples == 0)
totalSamples = file->chunkData(i).toUInt32LE(0);
else
debug("RIFF::WAV::Properties::read() - Duplicate 'fact' chunk found.");
}
}
if(data.size() < 16) {
debug("RIFF::WAV::AudioProperties::read() - \"fmt \" chunk is too short for WAV.");
debug("RIFF::WAV::Properties::read() - 'fmt ' chunk not found or too short.");
return;
}
d->format = data.toInt16LE(0);
d->channels = data.toInt16LE(2);
d->sampleRate = data.toUInt32LE(4);
d->sampleWidth = data.toInt16LE(14);
if(streamLength == 0) {
debug("RIFF::WAV::Properties::read() - 'data' chunk not found.");
return;
}
const uint byteRate = data.toUInt32LE(8);
d->bitrate = byteRate * 8 / 1000;
d->format = data.toUInt16LE(0);
if(d->format != FORMAT_PCM && totalSamples == 0) {
debug("RIFF::WAV::Properties::read() - Non-PCM format, but 'fact' chunk not found.");
return;
}
d->length = byteRate > 0 ? streamLength / byteRate : 0;
d->channels = data.toUInt16LE(2);
d->sampleRate = data.toUInt32LE(4);
d->bitsPerSample = data.toUInt16LE(14);
// format ID 1 means uncompressed PCM.
if(d->format == 1 && d->channels > 0 && d->sampleWidth > 0)
d->sampleFrames = streamLength / (d->channels * ((d->sampleWidth + 7) / 8));
if(totalSamples > 0)
d->sampleFrames = totalSamples;
else if(d->channels > 0 && d->bitsPerSample > 0)
d->sampleFrames = streamLength / (d->channels * ((d->bitsPerSample + 7) / 8));
if(d->sampleFrames > 0 && d->sampleRate > 0) {
const double length = d->sampleFrames * 1000.0 / d->sampleRate;
d->length = static_cast<int>(length + 0.5);
d->bitrate = static_cast<int>(streamLength * 8.0 / length + 0.5);
}
else {
const uint byteRate = data.toUInt32LE(8);
if(byteRate > 0) {
d->length = static_cast<int>(streamLength * 1000.0 / byteRate + 0.5);
d->bitrate = static_cast<int>(byteRate * 8.0 / 1000.0 + 0.5);
}
}
}

View File

@ -50,25 +50,85 @@ namespace TagLib {
{
public:
/*!
* Creates an instance of WAV::Properties with the data read from the
* Create an instance of WAV::Properties with the data read from the
* ByteVector \a data.
*
* \deprecated
*/
AudioProperties(const ByteVector &data, ReadStyle style);
/*!
* Create an instance of WAV::Properties with the data read from the
* ByteVector \a data and the length calculated using \a streamLength.
*
* \deprecated
*/
AudioProperties(const ByteVector &data, uint streamLength, ReadStyle style);
/*!
* Destroys this WAV::AudioProperties instance.
* Create an instance of WAV::Properties with the data read from the
* WAV::File \a file.
*/
AudioProperties(File *file, ReadStyle style);
/*!
* Destroys this WAV::Properties instance.
*/
virtual ~AudioProperties();
// Reimplementations.
/*!
* Returns the length of the file in seconds. The length is rounded down to
* the nearest whole second.
*
* \note This method is just an alias of lengthInSeconds().
*
* \deprecated
*/
virtual int length() const;
/*!
* Returns the length of the file in seconds. The length is rounded down to
* the nearest whole second.
*
* \see lengthInMilliseconds()
*/
// BIC: make virtual
int lengthInSeconds() const;
/*!
* Returns the length of the file in milliseconds.
*
* \see lengthInSeconds()
*/
// BIC: make virtual
int lengthInMilliseconds() const;
/*!
* Returns the average bit rate of the file in kb/s.
*/
virtual int bitrate() const;
/*!
* Returns the sample rate in Hz.
*/
virtual int sampleRate() const;
/*!
* Returns the number of audio channels.
*/
virtual int channels() const;
/*!
* Returns the count of bits per sample.
* Returns the number of bits per audio sample.
*/
int bitsPerSample() const;
/*!
* Returns the number of bits per audio sample.
*
* \note This method is just an alias of bitsPerSample().
*
* \deprecated
*/
int sampleWidth() const;
@ -82,13 +142,17 @@ namespace TagLib {
uint sampleFrames() const;
/*!
* Returns the format ID of the WAVE file. For example, 0 for Unknown,
* 1 for PCM and so forth.
* Returns the format ID of the file.
* 0 for unknown, 1 for PCM, 2 for ADPCM, 3 for 32/64-bit IEEE754, and
* so forth.
*
* \note For further information, refer to the WAVE Form Registration
* Numbers in RFC 2361.
*/
uint format() const;
int format() const;
private:
void read(const ByteVector &data, uint streamLength);
void read(File *file);
class PropertiesPrivate;
PropertiesPrivate *d;

View File

@ -59,7 +59,8 @@ public:
// public members
////////////////////////////////////////////////////////////////////////////////
S3M::AudioProperties::AudioProperties(AudioProperties::ReadStyle propertiesStyle) :
S3M::AudioProperties::AudioProperties(AudioProperties::ReadStyle) :
TagLib::AudioProperties(),
d(new PropertiesPrivate())
{
}
@ -74,6 +75,16 @@ int S3M::AudioProperties::length() const
return 0;
}
int S3M::AudioProperties::lengthInSeconds() const
{
return 0;
}
int S3M::AudioProperties::lengthInMilliseconds() const
{
return 0;
}
int S3M::AudioProperties::bitrate() const
{
return 0;

Some files were not shown because too many files have changed in this diff Show More