mirror of
https://github.com/taglib/taglib.git
synced 2025-05-27 21:20:26 -04:00
Merge branch 'master' into merge-master-to-taglib2
# Conflicts: # ConfigureChecks.cmake # bindings/c/tag_c.cpp # taglib-config.cmd.cmake # taglib/asf/asfattribute.cpp # taglib/asf/asffile.cpp # taglib/audioproperties.cpp # taglib/mp4/mp4atom.h # taglib/mp4/mp4coverart.cpp # taglib/mp4/mp4file.cpp # taglib/mp4/mp4item.cpp # taglib/mp4/mp4tag.cpp # taglib/mpeg/id3v2/id3v2frame.cpp # taglib/mpeg/id3v2/id3v2tag.cpp # taglib/toolkit/tbytevector.cpp # taglib/toolkit/tbytevector.h # taglib/toolkit/tfile.cpp # taglib/toolkit/tfile.h # taglib/toolkit/tlist.h # taglib/toolkit/tstring.cpp # taglib/toolkit/tstring.h # tests/test_apetag.cpp
This commit is contained in:
commit
c2753f8d3c
@ -38,15 +38,15 @@ set(LIB_INSTALL_DIR "${EXEC_INSTALL_PREFIX}/lib${LIB_SUFFIX}" CACHE PATH "The su
|
||||
set(INCLUDE_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/include" CACHE PATH "The subdirectory to the header prefix" FORCE)
|
||||
|
||||
if(APPLE)
|
||||
option(BUILD_FRAMEWORK "Build an OS X framework" OFF)
|
||||
set(FRAMEWORK_INSTALL_DIR "/Library/Frameworks" CACHE STRING "Directory to install frameworks to.")
|
||||
option(BUILD_FRAMEWORK "Build an OS X framework" OFF)
|
||||
set(FRAMEWORK_INSTALL_DIR "/Library/Frameworks" CACHE STRING "Directory to install frameworks to.")
|
||||
endif()
|
||||
|
||||
if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX)
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
|
||||
endif()
|
||||
if (MSVC AND ENABLE_STATIC_RUNTIME)
|
||||
if(MSVC AND ENABLE_STATIC_RUNTIME)
|
||||
foreach(flag_var CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO)
|
||||
string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}")
|
||||
endforeach(flag_var)
|
||||
@ -56,14 +56,18 @@ set(TAGLIB_LIB_MAJOR_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}")
|
||||
if("${TAGLIB_LIB_PATCH_VERSION}" EQUAL "0")
|
||||
set(TAGLIB_LIB_VERSION_STRING "${TAGLIB_LIB_MAJOR_VERSION}.${TAGLIB_LIB_MINOR_VERSION}")
|
||||
else()
|
||||
set(TAGLIB_LIB_VERSION_STRING "${TAGLIB_LIB_MAJOR_VERSION}.${TAGLIB_LIB_MINOR_VERSION}.${TAGLIB_LIB_PATCH_VERSION}")
|
||||
endif()
|
||||
|
||||
# 1. If the library source code has changed at all since the last update, then increment revision.
|
||||
# 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 16)
|
||||
set(TAGLIB_SOVERSION_REVISION 0)
|
||||
set(TAGLIB_SOVERSION_REVISION 1)
|
||||
set(TAGLIB_SOVERSION_AGE 15)
|
||||
|
||||
math(EXPR TAGLIB_SOVERSION_MAJOR "${TAGLIB_SOVERSION_CURRENT} - ${TAGLIB_SOVERSION_AGE}")
|
||||
@ -103,8 +107,8 @@ endif()
|
||||
add_subdirectory(taglib)
|
||||
add_subdirectory(bindings)
|
||||
if(BUILD_TESTS)
|
||||
enable_testing()
|
||||
add_subdirectory(tests)
|
||||
enable_testing()
|
||||
add_subdirectory(tests)
|
||||
endif(BUILD_TESTS)
|
||||
add_subdirectory(examples)
|
||||
|
||||
@ -118,7 +122,7 @@ configure_file(
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake"
|
||||
IMMEDIATE @ONLY)
|
||||
|
||||
if (NOT TARGET uninstall)
|
||||
if(NOT TARGET uninstall)
|
||||
add_custom_target(uninstall
|
||||
COMMAND "${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake")
|
||||
endif()
|
||||
|
@ -142,57 +142,69 @@ endif()
|
||||
# Determine which kind of byte swap functions your compiler supports.
|
||||
|
||||
check_cxx_source_compiles("
|
||||
#include <boost/endian/conversion.hpp>
|
||||
int main() {
|
||||
__builtin_bswap16(0);
|
||||
__builtin_bswap32(0);
|
||||
__builtin_bswap64(0);
|
||||
boost::endian::endian_reverse(static_cast<uint16_t>(1));
|
||||
boost::endian::endian_reverse(static_cast<uint32_t>(1));
|
||||
boost::endian::endian_reverse(static_cast<uint64_t>(1));
|
||||
return 0;
|
||||
}
|
||||
" HAVE_GCC_BYTESWAP)
|
||||
" HAVE_BOOST_BYTESWAP)
|
||||
|
||||
if(NOT HAVE_GCC_BYTESWAP)
|
||||
if(NOT HAVE_BOOST_BYTESWAP)
|
||||
check_cxx_source_compiles("
|
||||
#include <byteswap.h>
|
||||
int main() {
|
||||
__bswap_16(0);
|
||||
__bswap_32(0);
|
||||
__bswap_64(0);
|
||||
__builtin_bswap16(0);
|
||||
__builtin_bswap32(0);
|
||||
__builtin_bswap64(0);
|
||||
return 0;
|
||||
}
|
||||
" HAVE_GLIBC_BYTESWAP)
|
||||
" HAVE_GCC_BYTESWAP)
|
||||
|
||||
if(NOT HAVE_GLIBC_BYTESWAP)
|
||||
if(NOT HAVE_GCC_BYTESWAP)
|
||||
check_cxx_source_compiles("
|
||||
#include <stdlib.h>
|
||||
#include <byteswap.h>
|
||||
int main() {
|
||||
_byteswap_ushort(0);
|
||||
_byteswap_ulong(0);
|
||||
_byteswap_uint64(0);
|
||||
__bswap_16(0);
|
||||
__bswap_32(0);
|
||||
__bswap_64(0);
|
||||
return 0;
|
||||
}
|
||||
" HAVE_MSC_BYTESWAP)
|
||||
" HAVE_GLIBC_BYTESWAP)
|
||||
|
||||
if(NOT HAVE_MSC_BYTESWAP)
|
||||
if(NOT HAVE_GLIBC_BYTESWAP)
|
||||
check_cxx_source_compiles("
|
||||
#include <libkern/OSByteOrder.h>
|
||||
#include <stdlib.h>
|
||||
int main() {
|
||||
OSSwapInt16(0);
|
||||
OSSwapInt32(0);
|
||||
OSSwapInt64(0);
|
||||
_byteswap_ushort(0);
|
||||
_byteswap_ulong(0);
|
||||
_byteswap_uint64(0);
|
||||
return 0;
|
||||
}
|
||||
" HAVE_MAC_BYTESWAP)
|
||||
" HAVE_MSC_BYTESWAP)
|
||||
|
||||
if(NOT HAVE_MAC_BYTESWAP)
|
||||
if(NOT HAVE_MSC_BYTESWAP)
|
||||
check_cxx_source_compiles("
|
||||
#include <sys/endian.h>
|
||||
#include <libkern/OSByteOrder.h>
|
||||
int main() {
|
||||
swap16(0);
|
||||
swap32(0);
|
||||
swap64(0);
|
||||
OSSwapInt16(0);
|
||||
OSSwapInt32(0);
|
||||
OSSwapInt64(0);
|
||||
return 0;
|
||||
}
|
||||
" HAVE_OPENBSD_BYTESWAP)
|
||||
" HAVE_MAC_BYTESWAP)
|
||||
|
||||
if(NOT HAVE_MAC_BYTESWAP)
|
||||
check_cxx_source_compiles("
|
||||
#include <sys/endian.h>
|
||||
int main() {
|
||||
swap16(0);
|
||||
swap32(0);
|
||||
swap64(0);
|
||||
return 0;
|
||||
}
|
||||
" HAVE_OPENBSD_BYTESWAP)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
@ -224,7 +236,7 @@ if(NOT HAVE_VSNPRINTF)
|
||||
" HAVE_VSPRINTF_S)
|
||||
endif()
|
||||
|
||||
# Check which your compiler supports ISO _strdup.
|
||||
# Determine whether your compiler supports ISO _strdup.
|
||||
|
||||
check_cxx_source_compiles("
|
||||
#include <cstring>
|
||||
|
8
NEWS
8
NEWS
@ -1,6 +1,12 @@
|
||||
TagLib 1.10 (Aug 23, 2015)
|
||||
TagLib 1.10 (Nov 11, 2015)
|
||||
==========================
|
||||
|
||||
1.10:
|
||||
|
||||
* Added new options to the tagwriter example.
|
||||
* Fixed self-assignment operator in some types.
|
||||
* Fixed extraction of MP4 tag keys with an empty list.
|
||||
|
||||
1.10 BETA:
|
||||
|
||||
* New API for the audio length in milliseconds.
|
||||
|
@ -7,6 +7,6 @@ includedir=${INCLUDE_INSTALL_DIR}
|
||||
Name: TagLib C Bindings
|
||||
Description: Audio meta-data library (C bindings)
|
||||
Requires: taglib
|
||||
Version: ${TAGLIB_LIB_MAJOR_VERSION}.${TAGLIB_LIB_MINOR_VERSION}.${TAGLIB_LIB_PATCH_VERSION}
|
||||
Version: ${TAGLIB_LIB_VERSION_STRING}
|
||||
Libs: -L${LIB_INSTALL_DIR} -ltag_c
|
||||
Cflags: -I${INCLUDE_INSTALL_DIR}/taglib
|
||||
|
@ -6,6 +6,7 @@
|
||||
#cmakedefine _FILE_OFFSET_BITS ${_FILE_OFFSET_BITS}
|
||||
|
||||
/* Defined if your compiler supports some byte swap functions */
|
||||
#cmakedefine HAVE_BOOST_BYTESWAP 1
|
||||
#cmakedefine HAVE_GCC_BYTESWAP 1
|
||||
#cmakedefine HAVE_GLIBC_BYTESWAP 1
|
||||
#cmakedefine HAVE_MSC_BYTESWAP 1
|
||||
|
@ -23,6 +23,7 @@
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <string.h>
|
||||
|
||||
#include <stdio.h>
|
||||
@ -34,6 +35,7 @@
|
||||
#include <fileref.h>
|
||||
#include <tfile.h>
|
||||
#include <tag.h>
|
||||
#include <tpropertymap.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
@ -65,11 +67,32 @@ void usage()
|
||||
cout << " -g <genre>" << endl;
|
||||
cout << " -y <year>" << endl;
|
||||
cout << " -T <track>" << endl;
|
||||
cout << " -R <tagname> <tagvalue>" << endl;
|
||||
cout << " -I <tagname> <tagvalue>" << endl;
|
||||
cout << " -D <tagname>" << endl;
|
||||
cout << endl;
|
||||
|
||||
exit(1);
|
||||
}
|
||||
|
||||
void checkForRejectedProperties(const TagLib::PropertyMap &tags)
|
||||
{ // stolen from tagreader.cpp
|
||||
if(tags.size() > 0) {
|
||||
unsigned int longest = 0;
|
||||
for(TagLib::PropertyMap::ConstIterator i = tags.begin(); i != tags.end(); ++i) {
|
||||
if(i->first.size() > longest) {
|
||||
longest = i->first.size();
|
||||
}
|
||||
}
|
||||
cout << "-- rejected TAGs (properties) --" << endl;
|
||||
for(TagLib::PropertyMap::ConstIterator i = tags.begin(); i != tags.end(); ++i) {
|
||||
for(TagLib::StringList::ConstIterator j = i->second.begin(); j != i->second.end(); ++j) {
|
||||
cout << left << std::setw(longest) << i->first << " - " << '"' << *j << '"' << endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
TagLib::List<TagLib::FileRef> fileList;
|
||||
@ -121,6 +144,29 @@ int main(int argc, char *argv[])
|
||||
case 'T':
|
||||
t->setTrack(value.toInt());
|
||||
break;
|
||||
case 'R':
|
||||
case 'I':
|
||||
if(i + 2 < argc) {
|
||||
TagLib::PropertyMap map = (*it).file()->properties ();
|
||||
if(field == 'R') {
|
||||
map.replace(value, TagLib::String(argv[i + 2]));
|
||||
}
|
||||
else {
|
||||
map.insert(value, TagLib::String(argv[i + 2]));
|
||||
}
|
||||
++i;
|
||||
checkForRejectedProperties((*it).file()->setProperties(map));
|
||||
}
|
||||
else {
|
||||
usage();
|
||||
}
|
||||
break;
|
||||
case 'D': {
|
||||
TagLib::PropertyMap map = (*it).file()->properties();
|
||||
map.erase(value);
|
||||
checkForRejectedProperties((*it).file()->setProperties(map));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
usage();
|
||||
break;
|
||||
|
@ -35,7 +35,7 @@ do
|
||||
flags="$flags -I$includedir/taglib"
|
||||
;;
|
||||
--version)
|
||||
echo ${TAGLIB_LIB_MAJOR_VERSION}.${TAGLIB_LIB_MINOR_VERSION}.${TAGLIB_LIB_PATCH_VERSION}
|
||||
echo ${TAGLIB_LIB_VERSION_STRING}
|
||||
;;
|
||||
--prefix)
|
||||
echo $prefix
|
||||
|
@ -29,8 +29,7 @@ goto theend
|
||||
:doit
|
||||
if /i "%1#" == "--libs#" echo -L${LIB_INSTALL_DIR} -llibtag
|
||||
if /i "%1#" == "--cflags#" echo -I${INCLUDE_INSTALL_DIR}/taglib
|
||||
if /i "%1#" == "--version#" echo ${TAGLIB_LIB_MAJOR_VERSION}.${TAGLIB_LIB_MINOR_VERSION}.${TAGLIB_LIB_PATCH_VERSION}
|
||||
if /i "%1#" == "--version#" echo ${TAGLIB_LIB_VERSION_STRING}
|
||||
if /i "%1#" == "--prefix#" echo ${CMAKE_INSTALL_PREFIX}
|
||||
|
||||
:theend
|
||||
|
||||
|
@ -6,6 +6,6 @@ includedir=${INCLUDE_INSTALL_DIR}
|
||||
Name: TagLib
|
||||
Description: Audio meta-data library
|
||||
Requires:
|
||||
Version: ${TAGLIB_LIB_MAJOR_VERSION}.${TAGLIB_LIB_MINOR_VERSION}.${TAGLIB_LIB_PATCH_VERSION}
|
||||
Version: ${TAGLIB_LIB_VERSION_STRING}
|
||||
Libs: -L${LIB_INSTALL_DIR} -ltag
|
||||
Cflags: -I${INCLUDE_INSTALL_DIR}/taglib
|
||||
|
@ -86,6 +86,7 @@ set(tag_HDRS
|
||||
mpeg/id3v2/frames/urllinkframe.h
|
||||
mpeg/id3v2/frames/chapterframe.h
|
||||
mpeg/id3v2/frames/tableofcontentsframe.h
|
||||
mpeg/id3v2/frames/podcastframe.h
|
||||
ogg/oggfile.h
|
||||
ogg/oggpage.h
|
||||
ogg/oggpageheader.h
|
||||
@ -188,6 +189,7 @@ set(frames_SRCS
|
||||
mpeg/id3v2/frames/urllinkframe.cpp
|
||||
mpeg/id3v2/frames/chapterframe.cpp
|
||||
mpeg/id3v2/frames/tableofcontentsframe.cpp
|
||||
mpeg/id3v2/frames/podcastframe.cpp
|
||||
)
|
||||
|
||||
set(ogg_SRCS
|
||||
|
@ -86,8 +86,10 @@ APE::Item::~Item()
|
||||
|
||||
Item &APE::Item::operator=(const Item &item)
|
||||
{
|
||||
delete d;
|
||||
d = new ItemPrivate(*item.d);
|
||||
if(&item != this) {
|
||||
delete d;
|
||||
d = new ItemPrivate(*item.d);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
@ -167,7 +169,7 @@ int APE::Item::size() const
|
||||
size_t result = 8 + d->key.size() /* d->key.data(String::UTF8).size() */ + 1;
|
||||
switch (d->type) {
|
||||
case Text:
|
||||
if(d->text.size()) {
|
||||
if(!d->text.isEmpty()) {
|
||||
StringList::ConstIterator it = d->text.begin();
|
||||
|
||||
result += it->data(String::UTF8).size();
|
||||
@ -234,7 +236,7 @@ void APE::Item::parse(const ByteVector &data)
|
||||
setReadOnly(flags & 1);
|
||||
setType(ItemTypes((flags >> 1) & 3));
|
||||
|
||||
if(Text == d->type)
|
||||
if(Text == d->type)
|
||||
d->text = StringList(ByteVectorList::split(value, '\0'), String::UTF8);
|
||||
else
|
||||
d->value = value;
|
||||
|
@ -23,7 +23,7 @@
|
||||
* http://www.mozilla.org/MPL/ *
|
||||
***************************************************************************/
|
||||
|
||||
#ifdef __SUNPRO_CC
|
||||
#if defined(__SUNPRO_CC) && (__SUNPRO_CC < 0x5130)
|
||||
// Sun Studio finds multiple specializations of Map because
|
||||
// it considers specializations with and without class types
|
||||
// to be different; this define forces Map to use only the
|
||||
@ -240,7 +240,7 @@ PropertyMap APE::Tag::setProperties(const PropertyMap &origProps)
|
||||
if(!checkKey(tagName))
|
||||
invalid.insert(it->first, it->second);
|
||||
else if(!(itemListMap().contains(tagName)) || !(itemListMap()[tagName].values() == it->second)) {
|
||||
if(it->second.size() == 0)
|
||||
if(it->second.isEmpty())
|
||||
removeItem(tagName);
|
||||
else {
|
||||
StringList::ConstIterator valueIt = it->second.begin();
|
||||
|
@ -196,7 +196,7 @@ void ASF::File::FilePrivate::BaseObject::parse(ASF::File *file, unsigned int siz
|
||||
if(size > 24 && static_cast<offset_t>(size) <= file->length())
|
||||
data = file->readBlock(size - 24);
|
||||
else
|
||||
data = ByteVector::null;
|
||||
data = ByteVector();
|
||||
}
|
||||
|
||||
ByteVector ASF::File::FilePrivate::BaseObject::render(ASF::File * /*file*/)
|
||||
@ -312,7 +312,7 @@ ByteVector ASF::File::FilePrivate::ExtendedContentDescriptionObject::render(ASF:
|
||||
{
|
||||
data.clear();
|
||||
data.append(ByteVector::fromUInt16LE(attributeData.size()));
|
||||
data.append(attributeData.toByteVector(ByteVector::null));
|
||||
data.append(attributeData.toByteVector(""));
|
||||
return BaseObject::render(file);
|
||||
}
|
||||
|
||||
@ -336,7 +336,7 @@ ByteVector ASF::File::FilePrivate::MetadataObject::render(ASF::File *file)
|
||||
{
|
||||
data.clear();
|
||||
data.append(ByteVector::fromUInt16LE(attributeData.size()));
|
||||
data.append(attributeData.toByteVector(ByteVector::null));
|
||||
data.append(attributeData.toByteVector(""));
|
||||
return BaseObject::render(file);
|
||||
}
|
||||
|
||||
@ -360,7 +360,7 @@ ByteVector ASF::File::FilePrivate::MetadataLibraryObject::render(ASF::File *file
|
||||
{
|
||||
data.clear();
|
||||
data.append(ByteVector::fromUInt16LE(attributeData.size()));
|
||||
data.append(attributeData.toByteVector(ByteVector::null));
|
||||
data.append(attributeData.toByteVector(""));
|
||||
return BaseObject::render(file);
|
||||
}
|
||||
|
||||
|
@ -137,7 +137,8 @@ ASF::Picture& ASF::Picture::operator=(const ASF::Picture& other)
|
||||
ByteVector ASF::Picture::render() const
|
||||
{
|
||||
if(!isValid())
|
||||
return ByteVector::null;
|
||||
return ByteVector();
|
||||
|
||||
return
|
||||
ByteVector((char)d->data->type) +
|
||||
ByteVector::fromUInt32LE(d->data->picture.size()) +
|
||||
|
@ -41,6 +41,8 @@ const char *MP4::Atom::containers[11] = {
|
||||
|
||||
MP4::Atom::Atom(File *file)
|
||||
{
|
||||
children.setAutoDelete(true);
|
||||
|
||||
offset = file->tell();
|
||||
ByteVector header = file->readBlock(8);
|
||||
if (header.size() != 8) {
|
||||
@ -89,10 +91,6 @@ MP4::Atom::Atom(File *file)
|
||||
|
||||
MP4::Atom::~Atom()
|
||||
{
|
||||
for(unsigned int i = 0; i < children.size(); i++) {
|
||||
delete children[i];
|
||||
}
|
||||
children.clear();
|
||||
}
|
||||
|
||||
MP4::Atom *
|
||||
@ -101,9 +99,9 @@ MP4::Atom::find(const char *name1, const char *name2, const char *name3, const c
|
||||
if(name1 == 0) {
|
||||
return this;
|
||||
}
|
||||
for(unsigned int i = 0; i < children.size(); i++) {
|
||||
if(children[i]->name == name1) {
|
||||
return children[i]->find(name2, name3, name4);
|
||||
for(AtomList::ConstIterator it = children.begin(); it != children.end(); ++it) {
|
||||
if((*it)->name == name1) {
|
||||
return (*it)->find(name2, name3, name4);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
@ -113,12 +111,12 @@ MP4::AtomList
|
||||
MP4::Atom::findall(const char *name, bool recursive)
|
||||
{
|
||||
MP4::AtomList result;
|
||||
for(unsigned int i = 0; i < children.size(); i++) {
|
||||
if(children[i]->name == name) {
|
||||
result.append(children[i]);
|
||||
for(AtomList::ConstIterator it = children.begin(); it != children.end(); ++it) {
|
||||
if((*it)->name == name) {
|
||||
result.append(*it);
|
||||
}
|
||||
if(recursive) {
|
||||
result.append(children[i]->findall(name, recursive));
|
||||
result.append((*it)->findall(name, recursive));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
@ -131,9 +129,9 @@ MP4::Atom::path(MP4::AtomList &path, const char *name1, const char *name2, const
|
||||
if(name1 == 0) {
|
||||
return true;
|
||||
}
|
||||
for(unsigned int i = 0; i < children.size(); i++) {
|
||||
if(children[i]->name == name1) {
|
||||
return children[i]->path(path, name2, name3);
|
||||
for(AtomList::ConstIterator it = children.begin(); it != children.end(); ++it) {
|
||||
if((*it)->name == name1) {
|
||||
return (*it)->path(path, name2, name3);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@ -141,6 +139,8 @@ MP4::Atom::path(MP4::AtomList &path, const char *name1, const char *name2, const
|
||||
|
||||
MP4::Atoms::Atoms(File *file)
|
||||
{
|
||||
atoms.setAutoDelete(true);
|
||||
|
||||
file->seek(0, File::End);
|
||||
offset_t end = file->tell();
|
||||
file->seek(0);
|
||||
@ -154,18 +154,14 @@ MP4::Atoms::Atoms(File *file)
|
||||
|
||||
MP4::Atoms::~Atoms()
|
||||
{
|
||||
for(unsigned int i = 0; i < atoms.size(); i++) {
|
||||
delete atoms[i];
|
||||
}
|
||||
atoms.clear();
|
||||
}
|
||||
|
||||
MP4::Atom *
|
||||
MP4::Atoms::find(const char *name1, const char *name2, const char *name3, const char *name4)
|
||||
{
|
||||
for(unsigned int i = 0; i < atoms.size(); i++) {
|
||||
if(atoms[i]->name == name1) {
|
||||
return atoms[i]->find(name2, name3, name4);
|
||||
for(AtomList::ConstIterator it = atoms.begin(); it != atoms.end(); ++it) {
|
||||
if((*it)->name == name1) {
|
||||
return (*it)->find(name2, name3, name4);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
@ -175,9 +171,9 @@ MP4::AtomList
|
||||
MP4::Atoms::path(const char *name1, const char *name2, const char *name3, const char *name4)
|
||||
{
|
||||
MP4::AtomList path;
|
||||
for(unsigned int i = 0; i < atoms.size(); i++) {
|
||||
if(atoms[i]->name == name1) {
|
||||
if(!atoms[i]->path(path, name2, name3, name4)) {
|
||||
for(AtomList::ConstIterator it = atoms.begin(); it != atoms.end(); ++it) {
|
||||
if((*it)->name == name1) {
|
||||
if(!(*it)->path(path, name2, name3, name4)) {
|
||||
path.clear();
|
||||
}
|
||||
return path;
|
||||
@ -185,4 +181,3 @@ MP4::Atoms::path(const char *name1, const char *name2, const char *name3, const
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
|
@ -77,29 +77,29 @@ namespace TagLib {
|
||||
class Atom
|
||||
{
|
||||
public:
|
||||
Atom(File *file);
|
||||
~Atom();
|
||||
Atom *find(const char *name1, const char *name2 = 0, const char *name3 = 0, const char *name4 = 0);
|
||||
bool path(AtomList &path, const char *name1, const char *name2 = 0, const char *name3 = 0);
|
||||
AtomList findall(const char *name, bool recursive = false);
|
||||
offset_t offset;
|
||||
offset_t length;
|
||||
TagLib::ByteVector name;
|
||||
AtomList children;
|
||||
Atom(File *file);
|
||||
~Atom();
|
||||
Atom *find(const char *name1, const char *name2 = 0, const char *name3 = 0, const char *name4 = 0);
|
||||
bool path(AtomList &path, const char *name1, const char *name2 = 0, const char *name3 = 0);
|
||||
AtomList findall(const char *name, bool recursive = false);
|
||||
offset_t offset;
|
||||
offset_t length;
|
||||
TagLib::ByteVector name;
|
||||
AtomList children;
|
||||
private:
|
||||
static const int numContainers = 11;
|
||||
static const char *containers[11];
|
||||
static const int numContainers = 11;
|
||||
static const char *containers[11];
|
||||
};
|
||||
|
||||
//! Root-level atoms
|
||||
class Atoms
|
||||
{
|
||||
public:
|
||||
Atoms(File *file);
|
||||
~Atoms();
|
||||
Atom *find(const char *name1, const char *name2 = 0, const char *name3 = 0, const char *name4 = 0);
|
||||
AtomList path(const char *name1, const char *name2 = 0, const char *name3 = 0, const char *name4 = 0);
|
||||
AtomList atoms;
|
||||
Atoms(File *file);
|
||||
~Atoms();
|
||||
Atom *find(const char *name1, const char *name2 = 0, const char *name3 = 0, const char *name4 = 0);
|
||||
AtomList path(const char *name1, const char *name2 = 0, const char *name3 = 0, const char *name4 = 0);
|
||||
AtomList atoms;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -39,10 +39,10 @@ namespace
|
||||
};
|
||||
}
|
||||
|
||||
class MP4::CoverArt::CoverArtPrivate
|
||||
class MP4::CoverArt::CoverArtPrivate
|
||||
{
|
||||
public:
|
||||
CoverArtPrivate(Format f, const ByteVector &v)
|
||||
CoverArtPrivate(Format f, const ByteVector &v)
|
||||
: data(new CoverArtData())
|
||||
{
|
||||
data->format = f;
|
||||
@ -57,7 +57,7 @@ MP4::CoverArt::CoverArt(Format format, const ByteVector &data)
|
||||
{
|
||||
}
|
||||
|
||||
MP4::CoverArt::CoverArt(const CoverArt &item)
|
||||
MP4::CoverArt::CoverArt(const CoverArt &item)
|
||||
: d(new CoverArtPrivate(*item.d))
|
||||
{
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ using namespace TagLib;
|
||||
|
||||
namespace
|
||||
{
|
||||
bool checkValid(const MP4::AtomList &list)
|
||||
inline bool checkValid(const MP4::AtomList &list)
|
||||
{
|
||||
for(MP4::AtomList::ConstIterator it = list.begin(); it != list.end(); ++it) {
|
||||
|
||||
@ -55,7 +55,8 @@ public:
|
||||
FilePrivate() :
|
||||
tag(0),
|
||||
atoms(0),
|
||||
properties(0) {}
|
||||
properties(0),
|
||||
hasMP4Tag(false) {}
|
||||
|
||||
~FilePrivate()
|
||||
{
|
||||
@ -67,6 +68,8 @@ public:
|
||||
MP4::Tag *tag;
|
||||
MP4::Atoms *atoms;
|
||||
MP4::AudioProperties *properties;
|
||||
|
||||
bool hasMP4Tag;
|
||||
};
|
||||
|
||||
MP4::File::File(FileName file, bool readProperties, AudioProperties::ReadStyle) :
|
||||
@ -115,12 +118,15 @@ MP4::File::read(bool readProperties)
|
||||
}
|
||||
|
||||
// must have a moov atom, otherwise consider it invalid
|
||||
MP4::Atom *moov = d->atoms->find("moov");
|
||||
if(!moov) {
|
||||
if(!d->atoms->find("moov")) {
|
||||
setValid(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if(d->atoms->find("moov", "udta", "meta", "ilst")) {
|
||||
d->hasMP4Tag = true;
|
||||
}
|
||||
|
||||
d->tag = new Tag(this, d->atoms);
|
||||
if(readProperties) {
|
||||
d->properties = new AudioProperties(this, d->atoms);
|
||||
@ -140,6 +146,16 @@ MP4::File::save()
|
||||
return false;
|
||||
}
|
||||
|
||||
return d->tag->save();
|
||||
const bool success = d->tag->save();
|
||||
if(success) {
|
||||
d->hasMP4Tag = true;
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool
|
||||
MP4::File::hasMP4Tag() const
|
||||
{
|
||||
return d->hasMP4Tag;
|
||||
}
|
||||
|
@ -101,6 +101,12 @@ namespace TagLib {
|
||||
*/
|
||||
bool save();
|
||||
|
||||
/*!
|
||||
* Returns whether or not the file on disk actually has an MP4 tag, or the
|
||||
* file has a Metadata Item List (ilst) atom.
|
||||
*/
|
||||
bool hasMP4Tag() const;
|
||||
|
||||
private:
|
||||
void read(bool readProperties);
|
||||
|
||||
|
@ -54,10 +54,10 @@ namespace
|
||||
};
|
||||
}
|
||||
|
||||
class MP4::Item::ItemPrivate
|
||||
class MP4::Item::ItemPrivate
|
||||
{
|
||||
public:
|
||||
ItemPrivate()
|
||||
ItemPrivate()
|
||||
: data(new ItemData())
|
||||
{
|
||||
data->valid = true;
|
||||
@ -74,7 +74,7 @@ MP4::Item::Item()
|
||||
d->data->valid = false;
|
||||
}
|
||||
|
||||
MP4::Item::Item(const Item &item)
|
||||
MP4::Item::Item(const Item &item)
|
||||
: d(new ItemPrivate(*item.d))
|
||||
{
|
||||
}
|
||||
|
@ -35,21 +35,23 @@ using namespace TagLib;
|
||||
class MP4::Tag::TagPrivate
|
||||
{
|
||||
public:
|
||||
TagPrivate() : file(0), atoms(0) {}
|
||||
~TagPrivate() {}
|
||||
TagPrivate() :
|
||||
file(0),
|
||||
atoms(0) {}
|
||||
|
||||
TagLib::File *file;
|
||||
Atoms *atoms;
|
||||
ItemMap items;
|
||||
};
|
||||
|
||||
MP4::Tag::Tag()
|
||||
MP4::Tag::Tag() :
|
||||
d(new TagPrivate())
|
||||
{
|
||||
d = new TagPrivate;
|
||||
}
|
||||
|
||||
MP4::Tag::Tag(TagLib::File *file, MP4::Atoms *atoms)
|
||||
MP4::Tag::Tag(TagLib::File *file, MP4::Atoms *atoms) :
|
||||
d(new TagPrivate())
|
||||
{
|
||||
d = new TagPrivate;
|
||||
d->file = file;
|
||||
d->atoms = atoms;
|
||||
|
||||
@ -59,8 +61,8 @@ MP4::Tag::Tag(TagLib::File *file, MP4::Atoms *atoms)
|
||||
return;
|
||||
}
|
||||
|
||||
for(unsigned int i = 0; i < ilst->children.size(); i++) {
|
||||
MP4::Atom *atom = ilst->children[i];
|
||||
for(AtomList::ConstIterator it = ilst->children.begin(); it != ilst->children.end(); ++it) {
|
||||
MP4::Atom *atom = *it;
|
||||
file->seek(atom->offset + 8);
|
||||
if(atom->name == "----") {
|
||||
parseFreeForm(atom);
|
||||
@ -149,8 +151,8 @@ MP4::Tag::parseData(const MP4::Atom *atom, int expectedFlags, bool freeForm)
|
||||
{
|
||||
AtomDataList data = parseData2(atom, expectedFlags, freeForm);
|
||||
ByteVectorList result;
|
||||
for(uint i = 0; i < data.size(); i++) {
|
||||
result.append(data[i].data);
|
||||
for(AtomDataList::ConstIterator it = data.begin(); it != data.end(); ++it) {
|
||||
result.append(it->data);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@ -159,8 +161,8 @@ void
|
||||
MP4::Tag::parseInt(const MP4::Atom *atom)
|
||||
{
|
||||
ByteVectorList data = parseData(atom);
|
||||
if(data.size()) {
|
||||
d->items.insert(atom->name, (int)data[0].toInt16BE(0));
|
||||
if(!data.isEmpty()) {
|
||||
addItem(atom->name, (int)data[0].toInt16BE(0));
|
||||
}
|
||||
}
|
||||
|
||||
@ -168,8 +170,8 @@ void
|
||||
MP4::Tag::parseUInt(const MP4::Atom *atom)
|
||||
{
|
||||
ByteVectorList data = parseData(atom);
|
||||
if(data.size()) {
|
||||
d->items.insert(atom->name, data[0].toUInt32BE(0));
|
||||
if(!data.isEmpty()) {
|
||||
addItem(atom->name, data[0].toUInt32BE(0));
|
||||
}
|
||||
}
|
||||
|
||||
@ -177,8 +179,8 @@ void
|
||||
MP4::Tag::parseLongLong(const MP4::Atom *atom)
|
||||
{
|
||||
ByteVectorList data = parseData(atom);
|
||||
if(data.size()) {
|
||||
d->items.insert(atom->name, data[0].toInt64BE(0));
|
||||
if(!data.isEmpty()) {
|
||||
addItem(atom->name, data[0].toInt64BE(0));
|
||||
}
|
||||
}
|
||||
|
||||
@ -186,7 +188,7 @@ void
|
||||
MP4::Tag::parseByte(const MP4::Atom *atom)
|
||||
{
|
||||
ByteVectorList data = parseData(atom);
|
||||
if(data.size()) {
|
||||
if(!data.isEmpty()) {
|
||||
addItem(atom->name, (uchar)data[0].at(0));
|
||||
}
|
||||
}
|
||||
@ -195,10 +197,10 @@ void
|
||||
MP4::Tag::parseGnre(const MP4::Atom *atom)
|
||||
{
|
||||
ByteVectorList data = parseData(atom);
|
||||
if(data.size()) {
|
||||
const int idx = (int)data[0].toInt16BE(0);
|
||||
if(!d->items.contains("\251gen") && idx > 0) {
|
||||
d->items.insert("\251gen", StringList(ID3v1::genre(idx - 1)));
|
||||
if(!data.isEmpty()) {
|
||||
int idx = (int)data[0].toInt16BE(0);
|
||||
if(idx > 0) {
|
||||
addItem("\251gen", StringList(ID3v1::genre(idx - 1)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -207,10 +209,10 @@ void
|
||||
MP4::Tag::parseIntPair(const MP4::Atom *atom)
|
||||
{
|
||||
ByteVectorList data = parseData(atom);
|
||||
if(data.size()) {
|
||||
if(!data.isEmpty()) {
|
||||
const int a = data[0].toInt16BE(2);
|
||||
const int b = data[0].toInt16BE(4);
|
||||
d->items.insert(atom->name, MP4::Item(a, b));
|
||||
addItem(atom->name, MP4::Item(a, b));
|
||||
}
|
||||
}
|
||||
|
||||
@ -218,7 +220,7 @@ void
|
||||
MP4::Tag::parseBool(const MP4::Atom *atom)
|
||||
{
|
||||
ByteVectorList data = parseData(atom);
|
||||
if(data.size()) {
|
||||
if(!data.isEmpty()) {
|
||||
bool value = data[0].size() ? data[0][0] != '\0' : false;
|
||||
addItem(atom->name, value);
|
||||
}
|
||||
@ -228,10 +230,10 @@ void
|
||||
MP4::Tag::parseText(const MP4::Atom *atom, int expectedFlags)
|
||||
{
|
||||
ByteVectorList data = parseData(atom, expectedFlags);
|
||||
if(data.size()) {
|
||||
if(!data.isEmpty()) {
|
||||
StringList value;
|
||||
for(unsigned int i = 0; i < data.size(); i++) {
|
||||
value.append(String(data[i], String::UTF8));
|
||||
for(ByteVectorList::ConstIterator it = data.begin(); it != data.end(); ++it) {
|
||||
value.append(String(*it, String::UTF8));
|
||||
}
|
||||
addItem(atom->name, value);
|
||||
}
|
||||
@ -242,18 +244,25 @@ MP4::Tag::parseFreeForm(const MP4::Atom *atom)
|
||||
{
|
||||
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;
|
||||
for(uint i = 2; i < data.size(); i++) {
|
||||
if(data[i].type != type) {
|
||||
AtomDataList::ConstIterator itBegin = data.begin();
|
||||
|
||||
String name = "----:";
|
||||
name += String((itBegin++)->data, String::UTF8); // data[0].data
|
||||
name += ':';
|
||||
name += String((itBegin++)->data, String::UTF8); // data[1].data
|
||||
|
||||
AtomDataType type = itBegin->type; // data[2].type
|
||||
|
||||
for(AtomDataList::ConstIterator it = itBegin; it != data.end(); ++it) {
|
||||
if(it->type != type) {
|
||||
debug("MP4: We currently don't support values with multiple types");
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(type == TypeUTF8) {
|
||||
StringList value;
|
||||
for(uint i = 2; i < data.size(); i++) {
|
||||
value.append(String(data[i].data, String::UTF8));
|
||||
for(AtomDataList::ConstIterator it = itBegin; it != data.end(); ++it) {
|
||||
value.append(String(it->data, String::UTF8));
|
||||
}
|
||||
Item item(value);
|
||||
item.setAtomDataType(type);
|
||||
@ -261,8 +270,8 @@ MP4::Tag::parseFreeForm(const MP4::Atom *atom)
|
||||
}
|
||||
else {
|
||||
ByteVectorList value;
|
||||
for(uint i = 2; i < data.size(); i++) {
|
||||
value.append(data[i].data);
|
||||
for(AtomDataList::ConstIterator it = itBegin; it != data.end(); ++it) {
|
||||
value.append(it->data);
|
||||
}
|
||||
Item item(value);
|
||||
item.setAtomDataType(type);
|
||||
@ -300,7 +309,7 @@ MP4::Tag::parseCovr(const MP4::Atom *atom)
|
||||
}
|
||||
pos += length;
|
||||
}
|
||||
if(value.size() > 0)
|
||||
if(!value.isEmpty())
|
||||
addItem(atom->name, value);
|
||||
}
|
||||
|
||||
@ -323,8 +332,8 @@ ByteVector
|
||||
MP4::Tag::renderData(const ByteVector &name, int flags, const ByteVectorList &data) const
|
||||
{
|
||||
ByteVector result;
|
||||
for(unsigned int i = 0; i < data.size(); i++) {
|
||||
result.append(renderAtom("data", ByteVector::fromUInt32BE(flags) + ByteVector(4, '\0') + data[i]));
|
||||
for(ByteVectorList::ConstIterator it = data.begin(); it != data.end(); ++it) {
|
||||
result.append(renderAtom("data", ByteVector::fromUInt32BE(flags) + ByteVector(4, '\0') + *it));
|
||||
}
|
||||
return renderAtom(name, result);
|
||||
}
|
||||
@ -395,8 +404,8 @@ MP4::Tag::renderText(const ByteVector &name, const MP4::Item &item, int flags) c
|
||||
{
|
||||
ByteVectorList data;
|
||||
StringList value = item.toStringList();
|
||||
for(unsigned int i = 0; i < value.size(); i++) {
|
||||
data.append(value[i].data(String::UTF8));
|
||||
for(StringList::ConstIterator it = value.begin(); it != value.end(); ++it) {
|
||||
data.append(it->data(String::UTF8));
|
||||
}
|
||||
return renderData(name, flags, data);
|
||||
}
|
||||
@ -406,9 +415,9 @@ MP4::Tag::renderCovr(const ByteVector &name, const MP4::Item &item) const
|
||||
{
|
||||
ByteVector data;
|
||||
MP4::CoverArtList value = item.toCoverArtList();
|
||||
for(unsigned int i = 0; i < value.size(); i++) {
|
||||
data.append(renderAtom("data", ByteVector::fromUInt32BE(value[i].format()) +
|
||||
ByteVector(4, '\0') + value[i].data()));
|
||||
for(MP4::CoverArtList::ConstIterator it = value.begin(); it != value.end(); ++it) {
|
||||
data.append(renderAtom("data", ByteVector::fromUInt32BE(it->format()) +
|
||||
ByteVector(4, '\0') + it->data()));
|
||||
}
|
||||
return renderAtom(name, data);
|
||||
}
|
||||
@ -417,9 +426,9 @@ ByteVector
|
||||
MP4::Tag::renderFreeForm(const String &name, const MP4::Item &item) const
|
||||
{
|
||||
StringList header = StringList::split(name, ":");
|
||||
if (header.size() != 3) {
|
||||
if(header.size() != 3) {
|
||||
debug("MP4: Invalid free-form item name \"" + name + "\"");
|
||||
return ByteVector::null;
|
||||
return ByteVector();
|
||||
}
|
||||
ByteVector data;
|
||||
data.append(renderAtom("mean", ByteVector::fromUInt32BE(0) + header[1].data(String::UTF8)));
|
||||
@ -435,14 +444,14 @@ MP4::Tag::renderFreeForm(const String &name, const MP4::Item &item) const
|
||||
}
|
||||
if(type == TypeUTF8) {
|
||||
StringList value = item.toStringList();
|
||||
for(unsigned int i = 0; i < value.size(); i++) {
|
||||
data.append(renderAtom("data", ByteVector::fromUInt32BE(type) + ByteVector(4, '\0') + value[i].data(String::UTF8)));
|
||||
for(StringList::ConstIterator it = value.begin(); it != value.end(); ++it) {
|
||||
data.append(renderAtom("data", ByteVector::fromUInt32BE(type) + ByteVector(4, '\0') + it->data(String::UTF8)));
|
||||
}
|
||||
}
|
||||
else {
|
||||
ByteVectorList value = item.toByteVectorList();
|
||||
for(unsigned int i = 0; i < value.size(); i++) {
|
||||
data.append(renderAtom("data", ByteVector::fromUInt32BE(type) + ByteVector(4, '\0') + value[i]));
|
||||
for(ByteVectorList::ConstIterator it = value.begin(); it != value.end(); ++it) {
|
||||
data.append(renderAtom("data", ByteVector::fromUInt32BE(type) + ByteVector(4, '\0') + *it));
|
||||
}
|
||||
}
|
||||
return renderAtom("----", data);
|
||||
@ -505,20 +514,26 @@ MP4::Tag::save()
|
||||
void
|
||||
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);
|
||||
if(static_cast<int>(path.size()) <= ignore)
|
||||
return;
|
||||
|
||||
AtomList::ConstIterator itEnd = path.end();
|
||||
std::advance(itEnd, 0 - ignore);
|
||||
|
||||
for(AtomList::ConstIterator it = path.begin(); it != itEnd; ++it) {
|
||||
d->file->seek((*it)->offset);
|
||||
long size = d->file->readBlock(4).toUInt32BE(0);
|
||||
// 64-bit
|
||||
if (size == 1) {
|
||||
d->file->seek(4, File::Current); // Skip name
|
||||
long long longSize = d->file->readBlock(8).toInt64BE(0);
|
||||
// Seek the offset of the 64-bit size
|
||||
d->file->seek(path[i]->offset + 8);
|
||||
d->file->seek((*it)->offset + 8);
|
||||
d->file->writeBlock(ByteVector::fromUInt64BE(longSize + delta));
|
||||
}
|
||||
// 32-bit
|
||||
else {
|
||||
d->file->seek(path[i]->offset);
|
||||
d->file->seek((*it)->offset);
|
||||
d->file->writeBlock(ByteVector::fromUInt32BE(size + delta));
|
||||
}
|
||||
}
|
||||
@ -530,8 +545,8 @@ MP4::Tag::updateOffsets(long delta, offset_t offset)
|
||||
MP4::Atom *moov = d->atoms->find("moov");
|
||||
if(moov) {
|
||||
MP4::AtomList stco = moov->findall("stco", true);
|
||||
for(unsigned int i = 0; i < stco.size(); i++) {
|
||||
MP4::Atom *atom = stco[i];
|
||||
for(MP4::AtomList::ConstIterator it = stco.begin(); it != stco.end(); ++it) {
|
||||
MP4::Atom *atom = *it;
|
||||
if(atom->offset > offset) {
|
||||
atom->offset += delta;
|
||||
}
|
||||
@ -551,8 +566,8 @@ MP4::Tag::updateOffsets(long delta, offset_t offset)
|
||||
}
|
||||
|
||||
MP4::AtomList co64 = moov->findall("co64", true);
|
||||
for(size_t i = 0; i < co64.size(); i++) {
|
||||
MP4::Atom *atom = co64[i];
|
||||
for(MP4::AtomList::ConstIterator it = co64.begin(); it != co64.end(); ++it) {
|
||||
MP4::Atom *atom = *it;
|
||||
if(atom->offset > offset) {
|
||||
atom->offset += delta;
|
||||
}
|
||||
@ -575,8 +590,8 @@ MP4::Tag::updateOffsets(long delta, offset_t offset)
|
||||
MP4::Atom *moof = d->atoms->find("moof");
|
||||
if(moof) {
|
||||
MP4::AtomList tfhd = moof->findall("tfhd", true);
|
||||
for(size_t i = 0; i < tfhd.size(); i++) {
|
||||
MP4::Atom *atom = tfhd[i];
|
||||
for(MP4::AtomList::ConstIterator it = tfhd.begin(); it != tfhd.end(); ++it) {
|
||||
MP4::Atom *atom = *it;
|
||||
if(atom->offset > offset) {
|
||||
atom->offset += delta;
|
||||
}
|
||||
@ -609,7 +624,7 @@ MP4::Tag::saveNew(ByteVector data)
|
||||
data = renderAtom("udta", data);
|
||||
}
|
||||
|
||||
offset_t offset = path[path.size() - 1]->offset + 8;
|
||||
offset_t offset = path.back()->offset + 8;
|
||||
d->file->insert(data, offset, 0);
|
||||
|
||||
updateParents(path, static_cast<long>(data.size()));
|
||||
@ -619,11 +634,13 @@ MP4::Tag::saveNew(ByteVector data)
|
||||
void
|
||||
MP4::Tag::saveExisting(ByteVector data, const AtomList &path)
|
||||
{
|
||||
MP4::Atom *ilst = path[path.size() - 1];
|
||||
AtomList::ConstIterator it = path.end();
|
||||
|
||||
MP4::Atom *ilst = *(--it);
|
||||
offset_t offset = ilst->offset;
|
||||
offset_t length = ilst->length;
|
||||
|
||||
MP4::Atom *meta = path[path.size() - 2];
|
||||
MP4::Atom *meta = *(--it);
|
||||
AtomList::ConstIterator index = meta->children.find(ilst);
|
||||
|
||||
// check if there is an atom before 'ilst', and possibly use it as padding
|
||||
@ -869,8 +886,7 @@ PropertyMap MP4::Tag::properties() const
|
||||
}
|
||||
|
||||
PropertyMap props;
|
||||
MP4::ItemMap::ConstIterator it = d->items.begin();
|
||||
for(; it != d->items.end(); ++it) {
|
||||
for(MP4::ItemMap::ConstIterator it = d->items.begin(); it != d->items.end(); ++it) {
|
||||
if(keyMap.contains(it->first)) {
|
||||
String key = keyMap[it->first];
|
||||
if(key == "TRACKNUMBER" || key == "DISCNUMBER") {
|
||||
@ -900,8 +916,7 @@ PropertyMap MP4::Tag::properties() const
|
||||
|
||||
void MP4::Tag::removeUnsupportedProperties(const StringList &props)
|
||||
{
|
||||
StringList::ConstIterator it = props.begin();
|
||||
for(; it != props.end(); ++it)
|
||||
for(StringList::ConstIterator it = props.begin(); it != props.end(); ++it)
|
||||
d->items.erase(*it);
|
||||
}
|
||||
|
||||
@ -916,22 +931,20 @@ PropertyMap MP4::Tag::setProperties(const PropertyMap &props)
|
||||
}
|
||||
|
||||
PropertyMap origProps = properties();
|
||||
PropertyMap::ConstIterator it = origProps.begin();
|
||||
for(; it != origProps.end(); ++it) {
|
||||
for(PropertyMap::ConstIterator it = origProps.begin(); it != origProps.end(); ++it) {
|
||||
if(!props.contains(it->first) || props[it->first].isEmpty()) {
|
||||
d->items.erase(reverseKeyMap[it->first]);
|
||||
}
|
||||
}
|
||||
|
||||
PropertyMap ignoredProps;
|
||||
it = props.begin();
|
||||
for(; it != props.end(); ++it) {
|
||||
for(PropertyMap::ConstIterator it = props.begin(); it != props.end(); ++it) {
|
||||
if(reverseKeyMap.contains(it->first)) {
|
||||
String name = reverseKeyMap[it->first];
|
||||
if(it->first == "TRACKNUMBER" || it->first == "DISCNUMBER") {
|
||||
if((it->first == "TRACKNUMBER" || it->first == "DISCNUMBER") && !it->second.isEmpty()) {
|
||||
int first = 0, second = 0;
|
||||
StringList parts = StringList::split(it->second.front(), "/");
|
||||
if(parts.size() > 0) {
|
||||
if(!parts.isEmpty()) {
|
||||
first = parts[0].toInt();
|
||||
if(parts.size() > 1) {
|
||||
second = parts[1].toInt();
|
||||
@ -939,11 +952,11 @@ PropertyMap MP4::Tag::setProperties(const PropertyMap &props)
|
||||
d->items[name] = MP4::Item(first, second);
|
||||
}
|
||||
}
|
||||
else if(it->first == "BPM") {
|
||||
else if(it->first == "BPM" && !it->second.isEmpty()) {
|
||||
int value = it->second.front().toInt();
|
||||
d->items[name] = MP4::Item(value);
|
||||
}
|
||||
else if(it->first == "COMPILATION") {
|
||||
else if(it->first == "COMPILATION" && !it->second.isEmpty()) {
|
||||
bool value = (it->second.front().toInt() != 0);
|
||||
d->items[name] = MP4::Item(value);
|
||||
}
|
||||
|
79
taglib/mpeg/id3v2/frames/podcastframe.cpp
Normal file
79
taglib/mpeg/id3v2/frames/podcastframe.cpp
Normal file
@ -0,0 +1,79 @@
|
||||
/***************************************************************************
|
||||
copyright : (C) 2015 by Urs Fleisch
|
||||
email : ufleisch@users.sourceforge.net
|
||||
***************************************************************************/
|
||||
|
||||
/***************************************************************************
|
||||
* This library is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Lesser General Public License version *
|
||||
* 2.1 as published by the Free Software Foundation. *
|
||||
* *
|
||||
* This library is distributed in the hope that it will be useful, but *
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
|
||||
* Lesser General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Lesser General Public *
|
||||
* License along with this library; if not, write to the Free Software *
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
|
||||
* 02110-1301 USA *
|
||||
* *
|
||||
* Alternatively, this file is available under the Mozilla Public *
|
||||
* License Version 1.1. You may obtain a copy of the License at *
|
||||
* http://www.mozilla.org/MPL/ *
|
||||
***************************************************************************/
|
||||
|
||||
#include "podcastframe.h"
|
||||
|
||||
using namespace TagLib;
|
||||
using namespace ID3v2;
|
||||
|
||||
class PodcastFrame::PodcastFramePrivate
|
||||
{
|
||||
public:
|
||||
ByteVector fieldData;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// public members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
PodcastFrame::PodcastFrame() : Frame("PCST")
|
||||
{
|
||||
d = new PodcastFramePrivate;
|
||||
d->fieldData = ByteVector(4, '\0');
|
||||
}
|
||||
|
||||
PodcastFrame::~PodcastFrame()
|
||||
{
|
||||
delete d;
|
||||
}
|
||||
|
||||
String PodcastFrame::toString() const
|
||||
{
|
||||
return String();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// protected members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void PodcastFrame::parseFields(const ByteVector &data)
|
||||
{
|
||||
d->fieldData = data;
|
||||
}
|
||||
|
||||
ByteVector PodcastFrame::renderFields() const
|
||||
{
|
||||
return d->fieldData;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// private members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
PodcastFrame::PodcastFrame(const ByteVector &data, Header *h) : Frame(h)
|
||||
{
|
||||
d = new PodcastFramePrivate;
|
||||
parseFields(fieldData(data));
|
||||
}
|
80
taglib/mpeg/id3v2/frames/podcastframe.h
Normal file
80
taglib/mpeg/id3v2/frames/podcastframe.h
Normal file
@ -0,0 +1,80 @@
|
||||
/***************************************************************************
|
||||
copyright : (C) 2015 by Urs Fleisch
|
||||
email : ufleisch@users.sourceforge.net
|
||||
***************************************************************************/
|
||||
|
||||
/***************************************************************************
|
||||
* 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_PODCASTFRAME_H
|
||||
#define TAGLIB_PODCASTFRAME_H
|
||||
|
||||
#include "id3v2frame.h"
|
||||
#include "taglib_export.h"
|
||||
|
||||
namespace TagLib {
|
||||
|
||||
namespace ID3v2 {
|
||||
|
||||
//! ID3v2 podcast frame
|
||||
/*!
|
||||
* An implementation of ID3v2 podcast flag, a frame with four zero bytes.
|
||||
*/
|
||||
class TAGLIB_EXPORT PodcastFrame : public Frame
|
||||
{
|
||||
friend class FrameFactory;
|
||||
|
||||
public:
|
||||
/*!
|
||||
* Construct a podcast frame.
|
||||
*/
|
||||
PodcastFrame();
|
||||
|
||||
/*!
|
||||
* Destroys this PodcastFrame instance.
|
||||
*/
|
||||
virtual ~PodcastFrame();
|
||||
|
||||
/*!
|
||||
* Returns a null string.
|
||||
*/
|
||||
virtual String toString() const;
|
||||
|
||||
protected:
|
||||
// Reimplementations.
|
||||
|
||||
virtual void parseFields(const ByteVector &data);
|
||||
virtual ByteVector renderFields() const;
|
||||
|
||||
private:
|
||||
/*!
|
||||
* The constructor used by the FrameFactory.
|
||||
*/
|
||||
PodcastFrame(const ByteVector &data, Header *h);
|
||||
PodcastFrame(const PodcastFrame &);
|
||||
PodcastFrame &operator=(const PodcastFrame &);
|
||||
|
||||
class PodcastFramePrivate;
|
||||
PodcastFramePrivate *d;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
#endif
|
@ -149,7 +149,7 @@ PropertyMap TextIdentificationFrame::asProperties() const
|
||||
return makeTMCLProperties();
|
||||
PropertyMap map;
|
||||
String tagName = frameIDToKey(frameID());
|
||||
if(tagName.isNull()) {
|
||||
if(tagName.isEmpty()) {
|
||||
map.unsupportedData().append(frameID());
|
||||
return map;
|
||||
}
|
||||
|
@ -84,7 +84,7 @@ PropertyMap UrlLinkFrame::asProperties() const
|
||||
{
|
||||
String key = frameIDToKey(frameID());
|
||||
PropertyMap map;
|
||||
if(key.isNull())
|
||||
if(key.isEmpty())
|
||||
// unknown W*** frame - this normally shouldn't happen
|
||||
map.unsupportedData().append(frameID());
|
||||
else
|
||||
|
@ -116,8 +116,9 @@ Frame *Frame::createTextualFrame(const String &key, const StringList &values) //
|
||||
{
|
||||
// check if the key is contained in the key<=>frameID mapping
|
||||
ByteVector frameID = keyToFrameID(key);
|
||||
if(!frameID.isNull()) {
|
||||
if(frameID[0] == 'T'){ // text frame
|
||||
if(!frameID.isEmpty()) {
|
||||
// Apple proprietary WFED (Podcast URL) is in fact a text frame.
|
||||
if(frameID[0] == 'T' || frameID == "WFED"){ // text frame
|
||||
TextIdentificationFrame *frame = new TextIdentificationFrame(frameID, String::UTF8);
|
||||
frame->setText(values);
|
||||
return frame;
|
||||
@ -169,7 +170,7 @@ ByteVector Frame::frameID() const
|
||||
if(d->header)
|
||||
return d->header->frameID();
|
||||
else
|
||||
return ByteVector::null;
|
||||
return ByteVector();
|
||||
}
|
||||
|
||||
TagLib::uint Frame::size() const
|
||||
@ -357,165 +358,142 @@ String::Type Frame::checkTextEncoding(const StringList &fields, String::Type enc
|
||||
return checkEncoding(fields, encoding, header()->version());
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
static const TagLib::uint frameTranslationSize = 51;
|
||||
static const char *frameTranslation[][2] = {
|
||||
// Text information frames
|
||||
{ "TALB", "ALBUM" },
|
||||
{ "TBPM", "BPM" },
|
||||
{ "TCOM", "COMPOSER" },
|
||||
{ "TCON", "GENRE" },
|
||||
{ "TCOP", "COPYRIGHT" },
|
||||
{ "TDEN", "ENCODINGTIME" },
|
||||
{ "TDLY", "PLAYLISTDELAY" },
|
||||
{ "TDOR", "ORIGINALDATE" },
|
||||
{ "TDRC", "DATE" },
|
||||
// { "TRDA", "DATE" }, // id3 v2.3, replaced by TDRC in v2.4
|
||||
// { "TDAT", "DATE" }, // id3 v2.3, replaced by TDRC in v2.4
|
||||
// { "TYER", "DATE" }, // id3 v2.3, replaced by TDRC in v2.4
|
||||
// { "TIME", "DATE" }, // id3 v2.3, replaced by TDRC in v2.4
|
||||
{ "TDRL", "RELEASEDATE" },
|
||||
{ "TDTG", "TAGGINGDATE" },
|
||||
{ "TENC", "ENCODEDBY" },
|
||||
{ "TEXT", "LYRICIST" },
|
||||
{ "TFLT", "FILETYPE" },
|
||||
//{ "TIPL", "INVOLVEDPEOPLE" }, handled separately
|
||||
{ "TIT1", "CONTENTGROUP" },
|
||||
{ "TIT2", "TITLE"},
|
||||
{ "TIT3", "SUBTITLE" },
|
||||
{ "TKEY", "INITIALKEY" },
|
||||
{ "TLAN", "LANGUAGE" },
|
||||
{ "TLEN", "LENGTH" },
|
||||
//{ "TMCL", "MUSICIANCREDITS" }, handled separately
|
||||
{ "TMED", "MEDIA" },
|
||||
{ "TMOO", "MOOD" },
|
||||
{ "TOAL", "ORIGINALALBUM" },
|
||||
{ "TOFN", "ORIGINALFILENAME" },
|
||||
{ "TOLY", "ORIGINALLYRICIST" },
|
||||
{ "TOPE", "ORIGINALARTIST" },
|
||||
{ "TOWN", "OWNER" },
|
||||
{ "TPE1", "ARTIST"},
|
||||
{ "TPE2", "ALBUMARTIST" }, // id3's spec says 'PERFORMER', but most programs use 'ALBUMARTIST'
|
||||
{ "TPE3", "CONDUCTOR" },
|
||||
{ "TPE4", "REMIXER" }, // could also be ARRANGER
|
||||
{ "TPOS", "DISCNUMBER" },
|
||||
{ "TPRO", "PRODUCEDNOTICE" },
|
||||
{ "TPUB", "LABEL" },
|
||||
{ "TRCK", "TRACKNUMBER" },
|
||||
{ "TRSN", "RADIOSTATION" },
|
||||
{ "TRSO", "RADIOSTATIONOWNER" },
|
||||
{ "TSOA", "ALBUMSORT" },
|
||||
{ "TSOP", "ARTISTSORT" },
|
||||
{ "TSOT", "TITLESORT" },
|
||||
{ "TSO2", "ALBUMARTISTSORT" }, // non-standard, used by iTunes
|
||||
{ "TSRC", "ISRC" },
|
||||
{ "TSSE", "ENCODING" },
|
||||
// URL frames
|
||||
{ "WCOP", "COPYRIGHTURL" },
|
||||
{ "WOAF", "FILEWEBPAGE" },
|
||||
{ "WOAR", "ARTISTWEBPAGE" },
|
||||
{ "WOAS", "AUDIOSOURCEWEBPAGE" },
|
||||
{ "WORS", "RADIOSTATIONWEBPAGE" },
|
||||
{ "WPAY", "PAYMENTWEBPAGE" },
|
||||
{ "WPUB", "PUBLISHERWEBPAGE" },
|
||||
//{ "WXXX", "URL"}, handled specially
|
||||
// Other frames
|
||||
{ "COMM", "COMMENT" },
|
||||
//{ "USLT", "LYRICS" }, handled specially
|
||||
};
|
||||
static const size_t frameTranslationSize = 51;
|
||||
static const char *frameTranslation[][2] = {
|
||||
// Text information frames
|
||||
{ "TALB", "ALBUM"},
|
||||
{ "TBPM", "BPM" },
|
||||
{ "TCOM", "COMPOSER" },
|
||||
{ "TCON", "GENRE" },
|
||||
{ "TCOP", "COPYRIGHT" },
|
||||
{ "TDEN", "ENCODINGTIME" },
|
||||
{ "TDLY", "PLAYLISTDELAY" },
|
||||
{ "TDOR", "ORIGINALDATE" },
|
||||
{ "TDRC", "DATE" },
|
||||
// { "TRDA", "DATE" }, // id3 v2.3, replaced by TDRC in v2.4
|
||||
// { "TDAT", "DATE" }, // id3 v2.3, replaced by TDRC in v2.4
|
||||
// { "TYER", "DATE" }, // id3 v2.3, replaced by TDRC in v2.4
|
||||
// { "TIME", "DATE" }, // id3 v2.3, replaced by TDRC in v2.4
|
||||
{ "TDRL", "RELEASEDATE" },
|
||||
{ "TDTG", "TAGGINGDATE" },
|
||||
{ "TENC", "ENCODEDBY" },
|
||||
{ "TEXT", "LYRICIST" },
|
||||
{ "TFLT", "FILETYPE" },
|
||||
//{ "TIPL", "INVOLVEDPEOPLE" }, handled separately
|
||||
{ "TIT1", "CONTENTGROUP" },
|
||||
{ "TIT2", "TITLE"},
|
||||
{ "TIT3", "SUBTITLE" },
|
||||
{ "TKEY", "INITIALKEY" },
|
||||
{ "TLAN", "LANGUAGE" },
|
||||
{ "TLEN", "LENGTH" },
|
||||
//{ "TMCL", "MUSICIANCREDITS" }, handled separately
|
||||
{ "TMED", "MEDIA" },
|
||||
{ "TMOO", "MOOD" },
|
||||
{ "TOAL", "ORIGINALALBUM" },
|
||||
{ "TOFN", "ORIGINALFILENAME" },
|
||||
{ "TOLY", "ORIGINALLYRICIST" },
|
||||
{ "TOPE", "ORIGINALARTIST" },
|
||||
{ "TOWN", "OWNER" },
|
||||
{ "TPE1", "ARTIST"},
|
||||
{ "TPE2", "ALBUMARTIST" }, // id3's spec says 'PERFORMER', but most programs use 'ALBUMARTIST'
|
||||
{ "TPE3", "CONDUCTOR" },
|
||||
{ "TPE4", "REMIXER" }, // could also be ARRANGER
|
||||
{ "TPOS", "DISCNUMBER" },
|
||||
{ "TPRO", "PRODUCEDNOTICE" },
|
||||
{ "TPUB", "LABEL" },
|
||||
{ "TRCK", "TRACKNUMBER" },
|
||||
{ "TRSN", "RADIOSTATION" },
|
||||
{ "TRSO", "RADIOSTATIONOWNER" },
|
||||
{ "TSOA", "ALBUMSORT" },
|
||||
{ "TSOP", "ARTISTSORT" },
|
||||
{ "TSOT", "TITLESORT" },
|
||||
{ "TSO2", "ALBUMARTISTSORT" }, // non-standard, used by iTunes
|
||||
{ "TSRC", "ISRC" },
|
||||
{ "TSSE", "ENCODING" },
|
||||
// URL frames
|
||||
{ "WCOP", "COPYRIGHTURL" },
|
||||
{ "WOAF", "FILEWEBPAGE" },
|
||||
{ "WOAR", "ARTISTWEBPAGE" },
|
||||
{ "WOAS", "AUDIOSOURCEWEBPAGE" },
|
||||
{ "WORS", "RADIOSTATIONWEBPAGE" },
|
||||
{ "WPAY", "PAYMENTWEBPAGE" },
|
||||
{ "WPUB", "PUBLISHERWEBPAGE" },
|
||||
//{ "WXXX", "URL"}, handled specially
|
||||
// Other frames
|
||||
{ "COMM", "COMMENT" },
|
||||
//{ "USLT", "LYRICS" }, handled specially
|
||||
// Apple iTunes proprietary frames
|
||||
{ "PCST", "PODCAST" },
|
||||
{ "TCAT", "PODCASTCATEGORY" },
|
||||
{ "TDES", "PODCASTDESC" },
|
||||
{ "TGID", "PODCASTID" },
|
||||
{ "WFED", "PODCASTURL" },
|
||||
};
|
||||
|
||||
static const TagLib::uint txxxFrameTranslationSize = 8;
|
||||
static const char *txxxFrameTranslation[][2] = {
|
||||
{ "MusicBrainz Album Id", "MUSICBRAINZ_ALBUMID" },
|
||||
{ "MusicBrainz Artist Id", "MUSICBRAINZ_ARTISTID" },
|
||||
{ "MusicBrainz Album Artist Id", "MUSICBRAINZ_ALBUMARTISTID" },
|
||||
{ "MusicBrainz Release Group Id", "MUSICBRAINZ_RELEASEGROUPID" },
|
||||
{ "MusicBrainz Work Id", "MUSICBRAINZ_WORKID" },
|
||||
{ "Acoustid Id", "ACOUSTID_ID" },
|
||||
{ "Acoustid Fingerprint", "ACOUSTID_FINGERPRINT" },
|
||||
{ "MusicIP PUID", "MUSICIP_PUID" },
|
||||
};
|
||||
static const size_t txxxFrameTranslationSize = 8;
|
||||
static const char *txxxFrameTranslation[][2] = {
|
||||
{ "MUSICBRAINZ ALBUM ID", "MUSICBRAINZ_ALBUMID" },
|
||||
{ "MUSICBRAINZ ARTIST ID", "MUSICBRAINZ_ARTISTID" },
|
||||
{ "MUSICBRAINZ ALBUM ARTIST ID", "MUSICBRAINZ_ALBUMARTISTID" },
|
||||
{ "MUSICBRAINZ RELEASE GROUP ID", "MUSICBRAINZ_RELEASEGROUPID" },
|
||||
{ "MUSICBRAINZ WORK ID", "MUSICBRAINZ_WORKID" },
|
||||
{ "ACOUSTID ID", "ACOUSTID_ID" },
|
||||
{ "ACOUSTID FINGERPRINT", "ACOUSTID_FINGERPRINT" },
|
||||
{ "MUSICIP PUID", "MUSICIP_PUID" },
|
||||
};
|
||||
|
||||
Map<ByteVector, String> &idMap()
|
||||
{
|
||||
static Map<ByteVector, String> m;
|
||||
if(m.isEmpty())
|
||||
for(size_t i = 0; i < frameTranslationSize; ++i)
|
||||
m[frameTranslation[i][0]] = frameTranslation[i][1];
|
||||
return m;
|
||||
}
|
||||
|
||||
Map<String, String> &txxxMap()
|
||||
{
|
||||
static Map<String, String> m;
|
||||
if(m.isEmpty()) {
|
||||
for(size_t i = 0; i < txxxFrameTranslationSize; ++i) {
|
||||
String key = String(txxxFrameTranslation[i][0]).upper();
|
||||
m[key] = txxxFrameTranslation[i][1];
|
||||
}
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
// list of deprecated frames and their successors
|
||||
static const TagLib::uint deprecatedFramesSize = 4;
|
||||
static const char *deprecatedFrames[][2] = {
|
||||
{"TRDA", "TDRC"}, // 2.3 -> 2.4 (http://en.wikipedia.org/wiki/ID3)
|
||||
{"TDAT", "TDRC"}, // 2.3 -> 2.4
|
||||
{"TYER", "TDRC"}, // 2.3 -> 2.4
|
||||
{"TIME", "TDRC"}, // 2.3 -> 2.4
|
||||
};
|
||||
|
||||
|
||||
Map<ByteVector,ByteVector> &deprecationMap()
|
||||
{
|
||||
static Map<ByteVector,ByteVector> depMap;
|
||||
if(depMap.isEmpty())
|
||||
for(TagLib::uint i = 0; i < deprecatedFramesSize; ++i)
|
||||
depMap[deprecatedFrames[i][0]] = deprecatedFrames[i][1];
|
||||
return depMap;
|
||||
}
|
||||
}
|
||||
// list of deprecated frames and their successors
|
||||
static const size_t deprecatedFramesSize = 4;
|
||||
static const char *deprecatedFrames[][2] = {
|
||||
{"TRDA", "TDRC"}, // 2.3 -> 2.4 (http://en.wikipedia.org/wiki/ID3)
|
||||
{"TDAT", "TDRC"}, // 2.3 -> 2.4
|
||||
{"TYER", "TDRC"}, // 2.3 -> 2.4
|
||||
{"TIME", "TDRC"}, // 2.3 -> 2.4
|
||||
};
|
||||
|
||||
String Frame::frameIDToKey(const ByteVector &id)
|
||||
{
|
||||
Map<ByteVector, String> &m = idMap();
|
||||
if(m.contains(id))
|
||||
return m[id];
|
||||
if(deprecationMap().contains(id))
|
||||
return m[deprecationMap()[id]];
|
||||
return String::null;
|
||||
ByteVector id24 = id;
|
||||
for(size_t i = 0; i < deprecatedFramesSize; ++i) {
|
||||
if(id24 == deprecatedFrames[i][0]) {
|
||||
id24 = deprecatedFrames[i][1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
for(size_t i = 0; i < frameTranslationSize; ++i) {
|
||||
if(id24 == frameTranslation[i][0])
|
||||
return frameTranslation[i][1];
|
||||
}
|
||||
return String();
|
||||
}
|
||||
|
||||
ByteVector Frame::keyToFrameID(const String &s)
|
||||
{
|
||||
static Map<String, ByteVector> m;
|
||||
if(m.isEmpty())
|
||||
for(size_t i = 0; i < frameTranslationSize; ++i)
|
||||
m[frameTranslation[i][1]] = frameTranslation[i][0];
|
||||
if(m.contains(s.upper()))
|
||||
return m[s];
|
||||
return ByteVector::null;
|
||||
const String key = s.upper();
|
||||
for(size_t i = 0; i < frameTranslationSize; ++i) {
|
||||
if(key == frameTranslation[i][1])
|
||||
return frameTranslation[i][0];
|
||||
}
|
||||
return ByteVector();
|
||||
}
|
||||
|
||||
String Frame::txxxToKey(const String &description)
|
||||
{
|
||||
Map<String, String> &m = txxxMap();
|
||||
String d = description.upper();
|
||||
if(m.contains(d))
|
||||
return m[d];
|
||||
const String d = description.upper();
|
||||
for(size_t i = 0; i < txxxFrameTranslationSize; ++i) {
|
||||
if(d == txxxFrameTranslation[i][0])
|
||||
return txxxFrameTranslation[i][1];
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
String Frame::keyToTXXX(const String &s)
|
||||
{
|
||||
static Map<String, String> m;
|
||||
if(m.isEmpty())
|
||||
for(size_t i = 0; i < txxxFrameTranslationSize; ++i)
|
||||
m[txxxFrameTranslation[i][1]] = txxxFrameTranslation[i][0];
|
||||
if(m.contains(s.upper()))
|
||||
return m[s];
|
||||
const String key = s.upper();
|
||||
for(size_t i = 0; i < txxxFrameTranslationSize; ++i) {
|
||||
if(key == txxxFrameTranslation[i][1])
|
||||
return txxxFrameTranslation[i][0];
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
@ -530,7 +508,8 @@ PropertyMap Frame::asProperties() const
|
||||
// workaround until this function is virtual
|
||||
if(id == "TXXX")
|
||||
return dynamic_cast< const UserTextIdentificationFrame* >(this)->asProperties();
|
||||
else if(id[0] == 'T')
|
||||
// Apple proprietary WFED (Podcast URL) is in fact a text frame.
|
||||
else if(id[0] == 'T' || id == "WFED")
|
||||
return dynamic_cast< const TextIdentificationFrame* >(this)->asProperties();
|
||||
else if(id == "WXXX")
|
||||
return dynamic_cast< const UserUrlLinkFrame* >(this)->asProperties();
|
||||
@ -550,7 +529,6 @@ PropertyMap Frame::asProperties() const
|
||||
void Frame::splitProperties(const PropertyMap &original, PropertyMap &singleFrameProperties,
|
||||
PropertyMap &tiplProperties, PropertyMap &tmclProperties)
|
||||
{
|
||||
|
||||
singleFrameProperties.clear();
|
||||
tiplProperties.clear();
|
||||
tmclProperties.clear();
|
||||
|
@ -263,13 +263,13 @@ namespace TagLib {
|
||||
|
||||
/*!
|
||||
* Returns an appropriate ID3 frame ID for the given free-form tag key. This method
|
||||
* will return ByteVector::null if no specialized translation is found.
|
||||
* will return an empty ByteVector if no specialized translation is found.
|
||||
*/
|
||||
static ByteVector keyToFrameID(const String &);
|
||||
|
||||
/*!
|
||||
* Returns a free-form tag name for the given ID3 frame ID. Note that this does not work
|
||||
* for general frame IDs such as TXXX or WXXX; in such a case String::null is returned.
|
||||
* for general frame IDs such as TXXX or WXXX; in such a case an empty string is returned.
|
||||
*/
|
||||
static String frameIDToKey(const ByteVector &);
|
||||
|
||||
|
@ -49,6 +49,7 @@
|
||||
#include "frames/eventtimingcodesframe.h"
|
||||
#include "frames/chapterframe.h"
|
||||
#include "frames/tableofcontentsframe.h"
|
||||
#include "frames/podcastframe.h"
|
||||
|
||||
using namespace TagLib;
|
||||
using namespace ID3v2;
|
||||
@ -155,7 +156,8 @@ Frame *FrameFactory::createFrame(const ByteVector &origData, const Header *tagHe
|
||||
|
||||
// Text Identification (frames 4.2)
|
||||
|
||||
if(frameID.startsWith("T")) {
|
||||
// Apple proprietary WFED (Podcast URL) is in fact a text frame.
|
||||
if(frameID.startsWith("T") || frameID == "WFED") {
|
||||
|
||||
TextIdentificationFrame *f = frameID != "TXXX"
|
||||
? new TextIdentificationFrame(data, header)
|
||||
@ -275,6 +277,11 @@ Frame *FrameFactory::createFrame(const ByteVector &origData, const Header *tagHe
|
||||
if(frameID == "CTOC")
|
||||
return new TableOfContentsFrame(tagHeader, data, header);
|
||||
|
||||
// Apple proprietary PCST (Podcast)
|
||||
|
||||
if(frameID == "PCST")
|
||||
return new PodcastFrame(data, header);
|
||||
|
||||
return new UnknownFrame(data, header);
|
||||
}
|
||||
|
||||
@ -411,6 +418,14 @@ bool FrameFactory::updateFrame(Frame::Header *header) const
|
||||
convertFrame("WPB", "WPUB", header);
|
||||
convertFrame("WXX", "WXXX", header);
|
||||
|
||||
// Apple iTunes nonstandard frames
|
||||
convertFrame("PCS", "PCST", header);
|
||||
convertFrame("TCT", "TCAT", header);
|
||||
convertFrame("TDR", "TDRL", header);
|
||||
convertFrame("TDS", "TDES", header);
|
||||
convertFrame("TID", "TGID", header);
|
||||
convertFrame("WFD", "WFED", header);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -39,15 +39,14 @@ using namespace ID3v2;
|
||||
class Header::HeaderPrivate
|
||||
{
|
||||
public:
|
||||
HeaderPrivate() : majorVersion(4),
|
||||
revisionNumber(0),
|
||||
unsynchronisation(false),
|
||||
extendedHeader(false),
|
||||
experimentalIndicator(false),
|
||||
footerPresent(false),
|
||||
tagSize(0) {}
|
||||
|
||||
~HeaderPrivate() {}
|
||||
HeaderPrivate() :
|
||||
majorVersion(4),
|
||||
revisionNumber(0),
|
||||
unsynchronisation(false),
|
||||
extendedHeader(false),
|
||||
experimentalIndicator(false),
|
||||
footerPresent(false),
|
||||
tagSize(0) {}
|
||||
|
||||
uint majorVersion;
|
||||
uint revisionNumber;
|
||||
@ -58,8 +57,6 @@ public:
|
||||
bool footerPresent;
|
||||
|
||||
uint tagSize;
|
||||
|
||||
static const uint size = 10;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
@ -68,7 +65,7 @@ public:
|
||||
|
||||
TagLib::uint Header::size()
|
||||
{
|
||||
return HeaderPrivate::size;
|
||||
return 10;
|
||||
}
|
||||
|
||||
ByteVector Header::fileIdentifier()
|
||||
@ -80,14 +77,14 @@ ByteVector Header::fileIdentifier()
|
||||
// public members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
Header::Header()
|
||||
Header::Header() :
|
||||
d(new HeaderPrivate())
|
||||
{
|
||||
d = new HeaderPrivate;
|
||||
}
|
||||
|
||||
Header::Header(const ByteVector &data)
|
||||
Header::Header(const ByteVector &data) :
|
||||
d(new HeaderPrivate())
|
||||
{
|
||||
d = new HeaderPrivate;
|
||||
parse(data);
|
||||
}
|
||||
|
||||
@ -139,9 +136,9 @@ TagLib::uint Header::tagSize() const
|
||||
TagLib::uint Header::completeTagSize() const
|
||||
{
|
||||
if(d->footerPresent)
|
||||
return d->tagSize + d->size + Footer::size();
|
||||
return d->tagSize + size() + Footer::size();
|
||||
else
|
||||
return d->tagSize + d->size;
|
||||
return d->tagSize + size();
|
||||
}
|
||||
|
||||
void Header::setTagSize(uint s)
|
||||
@ -199,7 +196,6 @@ void Header::parse(const ByteVector &data)
|
||||
if(data.size() < size())
|
||||
return;
|
||||
|
||||
|
||||
// do some sanity checking -- even in ID3v2.3.0 and less the tag size is a
|
||||
// synch-safe integer, so all bytes must be less than 128. If this is not
|
||||
// true then this is an invalid tag.
|
||||
|
@ -27,6 +27,8 @@
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "tfile.h"
|
||||
|
||||
#include "id3v2tag.h"
|
||||
@ -52,12 +54,11 @@ using namespace ID3v2;
|
||||
class ID3v2::Tag::TagPrivate
|
||||
{
|
||||
public:
|
||||
TagPrivate()
|
||||
: file(0)
|
||||
, tagOffset(-1)
|
||||
, extendedHeader(0)
|
||||
, footer(0)
|
||||
, paddingSize(0)
|
||||
TagPrivate() :
|
||||
file(0),
|
||||
tagOffset(-1),
|
||||
extendedHeader(0),
|
||||
footer(0)
|
||||
{
|
||||
frameList.setAutoDelete(true);
|
||||
}
|
||||
@ -76,8 +77,6 @@ public:
|
||||
ExtendedHeader *extendedHeader;
|
||||
Footer *footer;
|
||||
|
||||
int paddingSize;
|
||||
|
||||
FrameListMap frameListMap;
|
||||
FrameList frameList;
|
||||
|
||||
@ -93,7 +92,8 @@ const TagLib::StringHandler *ID3v2::Tag::TagPrivate::stringHandler = &defaultStr
|
||||
|
||||
namespace
|
||||
{
|
||||
const TagLib::uint DefaultPaddingSize = 1024;
|
||||
const offset_t MinPaddingSize = 1024;
|
||||
const offset_t MaxPaddingSize = 1024 * 1024;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
@ -118,17 +118,17 @@ ByteVector ID3v2::Latin1StringHandler::render(const String &) const
|
||||
// public members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
ID3v2::Tag::Tag() : TagLib::Tag()
|
||||
ID3v2::Tag::Tag() :
|
||||
TagLib::Tag(),
|
||||
d(new TagPrivate())
|
||||
{
|
||||
d = new TagPrivate;
|
||||
d->factory = FrameFactory::instance();
|
||||
}
|
||||
|
||||
ID3v2::Tag::Tag(File *file, offset_t tagOffset, const FrameFactory *factory) :
|
||||
TagLib::Tag()
|
||||
TagLib::Tag(),
|
||||
d(new TagPrivate())
|
||||
{
|
||||
d = new TagPrivate;
|
||||
|
||||
d->file = file;
|
||||
d->tagOffset = tagOffset;
|
||||
d->factory = factory;
|
||||
@ -577,8 +577,6 @@ ByteVector ID3v2::Tag::render(int version) const
|
||||
// in ID3v2::Header::tagSize() -- includes the extended header, frames and
|
||||
// padding, but does not include the tag's header or footer.
|
||||
|
||||
ByteVector tagData;
|
||||
|
||||
if(version != 3 && version != 4) {
|
||||
debug("Unknown ID3v2 version, using ID3v2.4");
|
||||
version = 4;
|
||||
@ -586,7 +584,7 @@ ByteVector ID3v2::Tag::render(int version) const
|
||||
|
||||
// TODO: Render the extended header.
|
||||
|
||||
// Loop through the frames rendering them and adding them to the tagData.
|
||||
// Downgrade the frames that ID3v2.3 doesn't support.
|
||||
|
||||
FrameList newFrames;
|
||||
newFrames.setAutoDelete(true);
|
||||
@ -599,6 +597,12 @@ ByteVector ID3v2::Tag::render(int version) const
|
||||
downgradeFrames(&frameList, &newFrames);
|
||||
}
|
||||
|
||||
// Reserve a 10-byte blank space for an ID3v2 tag header.
|
||||
|
||||
ByteVector tagData(Header::size(), '\0');
|
||||
|
||||
// Loop through the frames rendering them and adding them to the tagData.
|
||||
|
||||
for(FrameList::ConstIterator it = frameList.begin(); it != frameList.end(); it++) {
|
||||
(*it)->header()->setVersion(version);
|
||||
if((*it)->header()->frameID().size() != 4) {
|
||||
@ -619,28 +623,33 @@ ByteVector ID3v2::Tag::render(int version) const
|
||||
|
||||
// Compute the amount of padding, and append that to tagData.
|
||||
|
||||
size_t paddingSize = DefaultPaddingSize;
|
||||
offset_t paddingSize = d->header.tagSize() - (tagData.size() - Header::size());
|
||||
|
||||
if(d->file && tagData.size() < d->header.tagSize()) {
|
||||
paddingSize = d->header.tagSize() - tagData.size();
|
||||
if(paddingSize <= 0) {
|
||||
paddingSize = MinPaddingSize;
|
||||
}
|
||||
else {
|
||||
// Padding won't increase beyond 1% of the file size or 1MB.
|
||||
|
||||
// Padding won't increase beyond 1% of the file size.
|
||||
offset_t threshold = d->file ? d->file->length() / 100 : 0;
|
||||
threshold = std::max(threshold, MinPaddingSize);
|
||||
threshold = std::min(threshold, MaxPaddingSize);
|
||||
|
||||
if(paddingSize > DefaultPaddingSize) {
|
||||
const ulonglong threshold = d->file->length() / 100;
|
||||
if(paddingSize > threshold)
|
||||
paddingSize = DefaultPaddingSize;
|
||||
}
|
||||
if(paddingSize > threshold)
|
||||
paddingSize = MinPaddingSize;
|
||||
}
|
||||
|
||||
tagData.append(ByteVector(paddingSize, '\0'));
|
||||
tagData.resize(static_cast<uint>(tagData.size() + paddingSize), '\0');
|
||||
|
||||
// Set the version and data size.
|
||||
d->header.setMajorVersion(version);
|
||||
d->header.setTagSize(static_cast<uint>(tagData.size()));
|
||||
d->header.setTagSize(static_cast<TagLib::uint>(tagData.size() - Header::size()));
|
||||
|
||||
// TODO: This should eventually include d->footer->render().
|
||||
return d->header.render() + tagData;
|
||||
const ByteVector headerData = d->header.render();
|
||||
std::copy(headerData.begin(), headerData.end(), tagData.begin());
|
||||
|
||||
return tagData;
|
||||
}
|
||||
|
||||
TagLib::StringHandler const *ID3v2::Tag::latin1StringHandler()
|
||||
@ -721,7 +730,6 @@ void ID3v2::Tag::parse(const ByteVector &origData)
|
||||
debug("Padding *and* a footer found. This is not allowed by the spec.");
|
||||
}
|
||||
|
||||
d->paddingSize = static_cast<int>(frameDataLength - frameDataPosition);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -432,7 +432,16 @@ offset_t MPEG::File::firstFrameOffset()
|
||||
|
||||
offset_t MPEG::File::lastFrameOffset()
|
||||
{
|
||||
return previousFrameOffset(hasID3v1Tag() ? d->ID3v1Location - 1 : length());
|
||||
offset_t position;
|
||||
|
||||
if(hasAPETag())
|
||||
position = d->APELocation - 1;
|
||||
else if(hasID3v1Tag())
|
||||
position = d->ID3v1Location - 1;
|
||||
else
|
||||
position = length();
|
||||
|
||||
return previousFrameOffset(position);
|
||||
}
|
||||
|
||||
bool MPEG::File::hasID3v1Tag() const
|
||||
|
@ -92,7 +92,7 @@ ByteVector Ogg::File::packet(uint i)
|
||||
while(d->packetToPageMap.size() <= i) {
|
||||
if(!nextPage()) {
|
||||
debug("Ogg::File::packet() -- Could not find the requested packet.");
|
||||
return ByteVector::null;
|
||||
return ByteVector();
|
||||
}
|
||||
}
|
||||
|
||||
@ -125,7 +125,7 @@ ByteVector Ogg::File::packet(uint i)
|
||||
if(pageIndex == d->pages.size()) {
|
||||
if(!nextPage()) {
|
||||
debug("Ogg::File::packet() -- Could not find the requested packet.");
|
||||
return ByteVector::null;
|
||||
return ByteVector();
|
||||
}
|
||||
}
|
||||
d->currentPacketPage = d->pages[pageIndex];
|
||||
|
@ -214,7 +214,7 @@ PropertyMap Ogg::XiphComment::setProperties(const PropertyMap &properties)
|
||||
invalid.insert(it->first, it->second);
|
||||
else if(!d->fieldListMap.contains(it->first) || !(it->second == d->fieldListMap[it->first])) {
|
||||
const StringList &sl = it->second;
|
||||
if(sl.size() == 0)
|
||||
if(sl.isEmpty())
|
||||
// zero size string list -> remove the tag with all values
|
||||
removeField(it->first);
|
||||
else {
|
||||
|
@ -177,7 +177,7 @@ void RIFF::AIFF::AudioProperties::read(File *file)
|
||||
d->sampleRate = static_cast<int>(sampleRate + 0.5);
|
||||
|
||||
if(d->sampleFrames > 0 && d->sampleRate > 0) {
|
||||
const double length = d->sampleFrames * 1000.0 / d->sampleRate;
|
||||
const double length = d->sampleFrames * 1000.0 / sampleRate;
|
||||
d->length = static_cast<int>(length + 0.5);
|
||||
d->bitrate = static_cast<int>(streamLength * 8.0 / length + 0.5);
|
||||
}
|
||||
|
@ -134,7 +134,7 @@ TagLib::uint RIFF::File::chunkPadding(uint i) const
|
||||
ByteVector RIFF::File::chunkName(uint i) const
|
||||
{
|
||||
if(i >= chunkCount())
|
||||
return ByteVector::null;
|
||||
return ByteVector();
|
||||
|
||||
return d->chunks[i].name;
|
||||
}
|
||||
@ -142,7 +142,7 @@ ByteVector RIFF::File::chunkName(uint i) const
|
||||
ByteVector RIFF::File::chunkData(uint i)
|
||||
{
|
||||
if(i >= chunkCount())
|
||||
return ByteVector::null;
|
||||
return ByteVector();
|
||||
|
||||
seek(d->chunks[i].offset);
|
||||
return readBlock(d->chunks[i].size);
|
||||
|
@ -49,8 +49,6 @@
|
||||
//
|
||||
// http://www.informit.com/isapi/product_id~{9C84DAB4-FE6E-49C5-BB0A-FB50331233EA}/content/index.asp
|
||||
|
||||
#define DATA(x) (&(*(x->data))[0])
|
||||
|
||||
namespace TagLib {
|
||||
|
||||
static const char hexTable[17] = "0123456789abcdef";
|
||||
@ -101,10 +99,6 @@ static const uint crcTable[256] = {
|
||||
0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4
|
||||
};
|
||||
|
||||
/*!
|
||||
* A templatized straightforward find that works with the types
|
||||
* std::vector<char>::iterator and std::vector<char>::reverse_iterator.
|
||||
*/
|
||||
template <class TIterator>
|
||||
size_t findChar(
|
||||
const TIterator dataBegin, const TIterator dataEnd,
|
||||
@ -127,10 +121,6 @@ size_t findChar(
|
||||
return ByteVector::npos;
|
||||
}
|
||||
|
||||
/*!
|
||||
* A templatized KMP find that works with the types
|
||||
* std::vector<char>::iterator and std::vector<char>::reverse_iterator.
|
||||
*/
|
||||
template <class TIterator>
|
||||
size_t findVector(
|
||||
const TIterator dataBegin, const TIterator dataEnd,
|
||||
@ -142,46 +132,33 @@ size_t findVector(
|
||||
if(patternSize == 0 || offset + patternSize > dataSize)
|
||||
return ByteVector::npos;
|
||||
|
||||
// n % 0 is invalid
|
||||
|
||||
if(byteAlign == 0)
|
||||
return ByteVector::npos;
|
||||
|
||||
// Special case that the pattern contains just single char.
|
||||
// Special case that pattern contains just single char.
|
||||
|
||||
if(patternSize == 1)
|
||||
return findChar(dataBegin, dataEnd, *patternBegin, offset, byteAlign);
|
||||
|
||||
size_t lastOccurrence[256];
|
||||
// n % 0 is invalid
|
||||
|
||||
for(size_t i = 0; i < 256; ++i)
|
||||
lastOccurrence[i] = patternSize;
|
||||
if(byteAlign == 0)
|
||||
return -1;
|
||||
|
||||
for(size_t i = 0; i < patternSize - 1; ++i)
|
||||
lastOccurrence[static_cast<uchar>(*(patternBegin + i))] = patternSize - i - 1;
|
||||
// We don't use sophisticated algorithms like Knuth-Morris-Pratt here.
|
||||
|
||||
TIterator it = dataBegin + patternSize - 1 + offset;
|
||||
while(true) {
|
||||
TIterator itBuffer = it;
|
||||
TIterator itPattern = patternBegin + patternSize - 1;
|
||||
// In the current implementation of TagLib, data and patterns are too small
|
||||
// for such algorithms to work effectively.
|
||||
|
||||
while(*itBuffer == *itPattern) {
|
||||
if(itPattern == patternBegin) {
|
||||
if((itBuffer - dataBegin - offset) % byteAlign == 0)
|
||||
return (itBuffer - dataBegin);
|
||||
else
|
||||
break;
|
||||
}
|
||||
for(TIterator it = dataBegin + offset; it < dataEnd - patternSize + 1; it += byteAlign) {
|
||||
|
||||
--itBuffer;
|
||||
--itPattern;
|
||||
TIterator itData = it;
|
||||
TIterator itPattern = patternBegin;
|
||||
|
||||
while(*itData == *itPattern) {
|
||||
++itData;
|
||||
++itPattern;
|
||||
|
||||
if(itPattern == patternEnd)
|
||||
return (it - dataBegin);
|
||||
}
|
||||
|
||||
const size_t step = lastOccurrence[static_cast<uchar>(*it)];
|
||||
if(dataEnd - step <= it)
|
||||
break;
|
||||
|
||||
it += step;
|
||||
}
|
||||
|
||||
return ByteVector::npos;
|
||||
@ -263,6 +240,8 @@ ByteVector fromFloat(TFloat value)
|
||||
template <ByteOrder ENDIAN>
|
||||
long double toFloat80(const ByteVector &v, size_t offset)
|
||||
{
|
||||
using std::swap;
|
||||
|
||||
if(offset > v.size() - 10) {
|
||||
debug("toFloat80() - offset is out of range. Returning 0.");
|
||||
return 0.0;
|
||||
@ -272,11 +251,11 @@ long double toFloat80(const ByteVector &v, size_t offset)
|
||||
::memcpy(bytes, v.data() + offset, 10);
|
||||
|
||||
if(ENDIAN == LittleEndian) {
|
||||
std::swap(bytes[0], bytes[9]);
|
||||
std::swap(bytes[1], bytes[8]);
|
||||
std::swap(bytes[2], bytes[7]);
|
||||
std::swap(bytes[3], bytes[6]);
|
||||
std::swap(bytes[4], bytes[5]);
|
||||
swap(bytes[0], bytes[9]);
|
||||
swap(bytes[1], bytes[8]);
|
||||
swap(bytes[2], bytes[7]);
|
||||
swap(bytes[3], bytes[6]);
|
||||
swap(bytes[4], bytes[5]);
|
||||
}
|
||||
|
||||
// 1-bit sign
|
||||
@ -456,25 +435,25 @@ ByteVector::~ByteVector()
|
||||
|
||||
ByteVector &ByteVector::setData(const char *data, size_t length)
|
||||
{
|
||||
*this = ByteVector(data, length);
|
||||
ByteVector(data, length).swap(*this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
ByteVector &ByteVector::setData(const char *data)
|
||||
{
|
||||
*this = ByteVector(data);
|
||||
ByteVector(data).swap(*this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
char *ByteVector::data()
|
||||
{
|
||||
detach();
|
||||
return !isEmpty() ? (DATA(d) + d->offset) : 0;
|
||||
return (size() > 0) ? (&(*d->data)[d->offset]) : 0;
|
||||
}
|
||||
|
||||
const char *ByteVector::data() const
|
||||
{
|
||||
return !isEmpty() ? (DATA(d) + d->offset) : 0;
|
||||
return (size() > 0) ? (&(*d->data)[d->offset]) : 0;
|
||||
}
|
||||
|
||||
ByteVector ByteVector::mid(size_t index, size_t length) const
|
||||
@ -487,7 +466,7 @@ ByteVector ByteVector::mid(size_t index, size_t length) const
|
||||
|
||||
char ByteVector::at(size_t index) const
|
||||
{
|
||||
return index < size() ? DATA(d)[d->offset + index] : 0;
|
||||
return (index < size()) ? (*d->data)[d->offset + index] : 0;
|
||||
}
|
||||
|
||||
size_t ByteVector::find(const ByteVector &pattern, size_t offset, size_t byteAlign) const
|
||||
@ -605,11 +584,9 @@ size_t ByteVector::endsWithPartialMatch(const ByteVector &pattern) const
|
||||
|
||||
ByteVector &ByteVector::append(const ByteVector &v)
|
||||
{
|
||||
if(!v.isEmpty())
|
||||
{
|
||||
if(v.d->length != 0) {
|
||||
detach();
|
||||
|
||||
const size_t originalSize = size();
|
||||
size_t originalSize = size();
|
||||
resize(originalSize + v.size());
|
||||
::memcpy(data() + originalSize, v.data(), v.size());
|
||||
}
|
||||
@ -625,9 +602,7 @@ ByteVector &ByteVector::append(char c)
|
||||
|
||||
ByteVector &ByteVector::clear()
|
||||
{
|
||||
detach();
|
||||
*d = *ByteVector::null.d;
|
||||
|
||||
ByteVector().swap(*this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
@ -684,7 +659,10 @@ ByteVector::ReverseIterator ByteVector::rbegin()
|
||||
|
||||
ByteVector::ConstReverseIterator ByteVector::rbegin() const
|
||||
{
|
||||
return d->data->rbegin() + (d->data->size() - (d->offset + d->length));
|
||||
// Workaround for the Solaris Studio 12.4 compiler.
|
||||
// We need a const reference to the data vector so we can ensure the const version of rbegin() is called.
|
||||
const std::vector<char> &v = *d->data;
|
||||
return v.rbegin() + (v.size() - (d->offset + d->length));
|
||||
}
|
||||
|
||||
ByteVector::ReverseIterator ByteVector::rend()
|
||||
@ -695,7 +673,10 @@ ByteVector::ReverseIterator ByteVector::rend()
|
||||
|
||||
ByteVector::ConstReverseIterator ByteVector::rend() const
|
||||
{
|
||||
return d->data->rbegin() + (d->data->size() - d->offset);
|
||||
// Workaround for the Solaris Studio 12.4 compiler.
|
||||
// We need a const reference to the data vector so we can ensure the const version of rbegin() is called.
|
||||
const std::vector<char> &v = *d->data;
|
||||
return v.rbegin() + (v.size() - d->offset);
|
||||
}
|
||||
|
||||
bool ByteVector::isNull() const
|
||||
@ -798,13 +779,13 @@ long double ByteVector::toFloat80BE(size_t offset) const
|
||||
|
||||
const char &ByteVector::operator[](size_t index) const
|
||||
{
|
||||
return DATA(d)[d->offset + index];
|
||||
return (*d->data)[d->offset + index];
|
||||
}
|
||||
|
||||
char &ByteVector::operator[](size_t index)
|
||||
{
|
||||
detach();
|
||||
return DATA(d)[d->offset + index];
|
||||
return (*d->data)[d->offset + index];
|
||||
}
|
||||
|
||||
bool ByteVector::operator==(const ByteVector &v) const
|
||||
@ -817,7 +798,7 @@ bool ByteVector::operator==(const ByteVector &v) const
|
||||
|
||||
bool ByteVector::operator!=(const ByteVector &v) const
|
||||
{
|
||||
return !operator==(v);
|
||||
return !(*this == v);
|
||||
}
|
||||
|
||||
bool ByteVector::operator==(const char *s) const
|
||||
@ -830,7 +811,7 @@ bool ByteVector::operator==(const char *s) const
|
||||
|
||||
bool ByteVector::operator!=(const char *s) const
|
||||
{
|
||||
return !operator==(s);
|
||||
return !(*this == s);
|
||||
}
|
||||
|
||||
bool ByteVector::operator<(const ByteVector &v) const
|
||||
@ -844,7 +825,7 @@ bool ByteVector::operator<(const ByteVector &v) const
|
||||
|
||||
bool ByteVector::operator>(const ByteVector &v) const
|
||||
{
|
||||
return v < *this;
|
||||
return (v < *this);
|
||||
}
|
||||
|
||||
ByteVector ByteVector::operator+(const ByteVector &v) const
|
||||
@ -856,22 +837,29 @@ ByteVector ByteVector::operator+(const ByteVector &v) const
|
||||
|
||||
ByteVector &ByteVector::operator=(const ByteVector &v)
|
||||
{
|
||||
*d = *v.d;
|
||||
ByteVector(v).swap(*this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
ByteVector &ByteVector::operator=(char c)
|
||||
{
|
||||
*this = ByteVector(c);
|
||||
ByteVector(c).swap(*this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
ByteVector &ByteVector::operator=(const char *data)
|
||||
{
|
||||
*this = ByteVector(data);
|
||||
ByteVector(data).swap(*this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void ByteVector::swap(ByteVector &v)
|
||||
{
|
||||
using std::swap;
|
||||
|
||||
swap(d, v.d);
|
||||
}
|
||||
|
||||
ByteVector ByteVector::toHex() const
|
||||
{
|
||||
ByteVector encoded(size() * 2);
|
||||
@ -893,10 +881,10 @@ ByteVector ByteVector::toHex() const
|
||||
void ByteVector::detach()
|
||||
{
|
||||
if(!d->data.unique()) {
|
||||
std::vector<char>::const_iterator begin = d->data->begin() + d->offset;
|
||||
std::vector<char>::const_iterator end = begin + d->length;
|
||||
d->data.reset(new std::vector<char>(begin, end));
|
||||
d->offset = 0;
|
||||
if(!isEmpty())
|
||||
ByteVector(&d->data->front() + d->offset, d->length).swap(*this);
|
||||
else
|
||||
ByteVector().swap(*this);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -266,9 +266,14 @@ namespace TagLib {
|
||||
/*!
|
||||
* Returns true if the vector is null.
|
||||
*
|
||||
* \note A vector may be empty without being null.
|
||||
* \note A vector may be empty without being null. So do not use this
|
||||
* method to check if the vector is empty.
|
||||
*
|
||||
* \see isEmpty()
|
||||
*
|
||||
* \deprecated
|
||||
*/
|
||||
// BIC: remove
|
||||
bool isNull() const;
|
||||
|
||||
/*!
|
||||
@ -558,9 +563,19 @@ namespace TagLib {
|
||||
*/
|
||||
ByteVector &operator=(const char *data);
|
||||
|
||||
/*!
|
||||
* Exchanges the content of the ByteVector by the content of \a v.
|
||||
*/
|
||||
void swap(ByteVector &v);
|
||||
|
||||
/*!
|
||||
* A static, empty ByteVector which is convenient and fast (since returning
|
||||
* an empty or "null" value does not require instantiating a new ByteVector).
|
||||
*
|
||||
* \warning Do not modify this variable. It will mess up the internal state
|
||||
* of TagLib.
|
||||
*
|
||||
* \deprecated
|
||||
*/
|
||||
static const ByteVector null;
|
||||
|
||||
|
@ -44,7 +44,7 @@ ByteVectorList ByteVectorList::split(
|
||||
if(offset - previousOffset >= 1)
|
||||
l.append(v.mid(previousOffset, offset - previousOffset));
|
||||
else
|
||||
l.append(ByteVector::null);
|
||||
l.append(ByteVector());
|
||||
|
||||
previousOffset = offset + pattern.size();
|
||||
}
|
||||
|
@ -71,7 +71,7 @@ FileName ByteVectorStream::name() const
|
||||
ByteVector ByteVectorStream::readBlock(size_t length)
|
||||
{
|
||||
if(length == 0)
|
||||
return ByteVector::null;
|
||||
return ByteVector();
|
||||
|
||||
ByteVector v = d->data.mid(static_cast<size_t>(d->position), length);
|
||||
d->position += v.size();
|
||||
|
@ -179,7 +179,7 @@ offset_t File::find(const ByteVector &pattern, offset_t fromOffset, const ByteVe
|
||||
}
|
||||
}
|
||||
|
||||
if(!before.isNull()
|
||||
if(!before.isEmpty()
|
||||
&& beforePreviousPartialMatch != ByteVector::npos
|
||||
&& bufferSize() > beforePreviousPartialMatch)
|
||||
{
|
||||
@ -198,7 +198,7 @@ offset_t File::find(const ByteVector &pattern, offset_t fromOffset, const ByteVe
|
||||
return bufferOffset + location;
|
||||
}
|
||||
|
||||
if(!before.isNull() && buffer.find(before) != ByteVector::npos) {
|
||||
if(!before.isEmpty() && buffer.find(before) != ByteVector::npos) {
|
||||
seek(originalPosition);
|
||||
return -1;
|
||||
}
|
||||
@ -207,7 +207,7 @@ offset_t File::find(const ByteVector &pattern, offset_t fromOffset, const ByteVe
|
||||
|
||||
previousPartialMatch = buffer.endsWithPartialMatch(pattern);
|
||||
|
||||
if(!before.isNull())
|
||||
if(!before.isEmpty())
|
||||
beforePreviousPartialMatch = buffer.endsWithPartialMatch(before);
|
||||
|
||||
bufferOffset += bufferSize();
|
||||
@ -268,7 +268,7 @@ offset_t File::rfind(const ByteVector &pattern, offset_t fromOffset, const ByteV
|
||||
return bufferOffset + location;
|
||||
}
|
||||
|
||||
if(!before.isNull() && buffer.find(before) != ByteVector::npos) {
|
||||
if(!before.isEmpty() && buffer.find(before) != ByteVector::npos) {
|
||||
seek(originalPosition);
|
||||
return -1;
|
||||
}
|
||||
|
@ -160,7 +160,7 @@ namespace TagLib {
|
||||
*/
|
||||
offset_t find(const ByteVector &pattern,
|
||||
offset_t fromOffset = 0,
|
||||
const ByteVector &before = ByteVector::null);
|
||||
const ByteVector &before = ByteVector());
|
||||
|
||||
/*!
|
||||
* Returns the offset in the file that \a pattern occurs at or -1 if it can
|
||||
@ -176,7 +176,7 @@ namespace TagLib {
|
||||
*/
|
||||
offset_t rfind(const ByteVector &pattern,
|
||||
offset_t fromOffset = 0,
|
||||
const ByteVector &before = ByteVector::null);
|
||||
const ByteVector &before = ByteVector());
|
||||
|
||||
/*!
|
||||
* Insert \a data at position \a start in the file overwriting \a replace
|
||||
|
@ -179,11 +179,11 @@ ByteVector FileStream::readBlock(size_t length)
|
||||
{
|
||||
if(!isOpen()) {
|
||||
debug("FileStream::readBlock() -- invalid file.");
|
||||
return ByteVector::null;
|
||||
return ByteVector();
|
||||
}
|
||||
|
||||
if(length == 0)
|
||||
return ByteVector::null;
|
||||
return ByteVector();
|
||||
|
||||
const offset_t streamLength = FileStream::length();
|
||||
if(length > bufferSize() && static_cast<offset_t>(length) > streamLength)
|
||||
|
@ -146,8 +146,16 @@ namespace TagLib {
|
||||
|
||||
/*!
|
||||
* Returns the number of elements in the list.
|
||||
*
|
||||
* \see isEmpty()
|
||||
*/
|
||||
size_t size() const;
|
||||
|
||||
/*!
|
||||
* Returns whether or not the list is empty.
|
||||
*
|
||||
* \see size()
|
||||
*/
|
||||
bool isEmpty() const;
|
||||
|
||||
/*!
|
||||
|
@ -335,6 +335,12 @@ String &String::append(const String &s)
|
||||
return *this;
|
||||
}
|
||||
|
||||
String & String::clear()
|
||||
{
|
||||
*this = String();
|
||||
return *this;
|
||||
}
|
||||
|
||||
String String::upper() const
|
||||
{
|
||||
static const int shift = 'A' - 'a';
|
||||
@ -394,7 +400,7 @@ ByteVector String::data(Type t) const
|
||||
return v;
|
||||
}
|
||||
else {
|
||||
return ByteVector::null;
|
||||
return ByteVector();
|
||||
}
|
||||
case UTF16:
|
||||
{
|
||||
@ -440,7 +446,7 @@ ByteVector String::data(Type t) const
|
||||
default:
|
||||
{
|
||||
debug("String::data() - Invalid Type value.");
|
||||
return ByteVector::null;
|
||||
return ByteVector();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -38,7 +38,7 @@
|
||||
* conversion happening in the background
|
||||
*/
|
||||
|
||||
#if QT_VERSION >= 0x040000
|
||||
#if defined(QT_VERSION) && (QT_VERSION >= 0x040000)
|
||||
#define QStringToTString(s) TagLib::String(s.toUtf8().data(), TagLib::String::UTF8)
|
||||
#else
|
||||
#define QStringToTString(s) TagLib::String(s.utf8().data(), TagLib::String::UTF8)
|
||||
@ -199,7 +199,8 @@ namespace TagLib {
|
||||
|
||||
/*!
|
||||
* Returns a deep copy of this String as a wstring. The returned string is
|
||||
* encoded in UTF-16 (without BOM/CPU byte order).
|
||||
* encoded in UTF-16 (without BOM/CPU byte order), not UTF-32 even if wchar_t
|
||||
* is 32-bit wide.
|
||||
*
|
||||
* \see toCWString()
|
||||
*/
|
||||
@ -228,7 +229,7 @@ namespace TagLib {
|
||||
/*!
|
||||
* Returns a standard C-style (null-terminated) wide character version of
|
||||
* this String. The returned string is encoded in UTF-16 (without BOM/CPU byte
|
||||
* order).
|
||||
* order), not UTF-32 even if wchar_t is 32-bit wide.
|
||||
*
|
||||
* The returned string is still owned by this String and should not be deleted
|
||||
* by the user.
|
||||
@ -300,6 +301,11 @@ namespace TagLib {
|
||||
*/
|
||||
String &append(const String &s);
|
||||
|
||||
/*!
|
||||
* Clears the string.
|
||||
*/
|
||||
String &clear();
|
||||
|
||||
/*!
|
||||
* Returns an upper case version of the string.
|
||||
*
|
||||
@ -328,9 +334,14 @@ namespace TagLib {
|
||||
* Returns true if this string is null -- i.e. it is a copy of the
|
||||
* String::null string.
|
||||
*
|
||||
* \note A string can be empty and not null.
|
||||
* \note A string can be empty and not null. So do not use this method to
|
||||
* check if the string is empty.
|
||||
*
|
||||
* \see isEmpty()
|
||||
*
|
||||
* \deprecated
|
||||
*/
|
||||
// BIC: remove
|
||||
bool isNull() const;
|
||||
|
||||
/*!
|
||||
@ -495,14 +506,19 @@ namespace TagLib {
|
||||
|
||||
/*!
|
||||
* A null string provided for convenience.
|
||||
*
|
||||
* \warning Do not modify this variable. It will mess up the internal state
|
||||
* of TagLib.
|
||||
*
|
||||
* \deprecated
|
||||
*/
|
||||
static const String null;
|
||||
|
||||
/*!
|
||||
* When used as the value for a \a length parameter in String's member
|
||||
* functions, means "until the end of the string".
|
||||
* As a return value, it is usually used to indicate no matches.
|
||||
*/
|
||||
* When used as the value for a \a length parameter in String's member
|
||||
* functions, means "until the end of the string".
|
||||
* As a return value, it is usually used to indicate no matches.
|
||||
*/
|
||||
static const size_t npos;
|
||||
|
||||
protected:
|
||||
|
@ -34,7 +34,9 @@
|
||||
# include <config.h>
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_MSC_BYTESWAP)
|
||||
#if defined(HAVE_BOOST_BYTESWAP)
|
||||
# include <boost/endian/conversion.hpp>
|
||||
#elif defined(HAVE_MSC_BYTESWAP)
|
||||
# include <stdlib.h>
|
||||
#elif defined(HAVE_GLIBC_BYTESWAP)
|
||||
# include <byteswap.h>
|
||||
@ -59,7 +61,11 @@ namespace TagLib
|
||||
*/
|
||||
inline ushort byteSwap(ushort x)
|
||||
{
|
||||
#if defined(HAVE_GCC_BYTESWAP)
|
||||
#if defined(HAVE_BOOST_BYTESWAP)
|
||||
|
||||
return boost::endian::endian_reverse(static_cast<uint16_t>(x));
|
||||
|
||||
#elif defined(HAVE_GCC_BYTESWAP)
|
||||
|
||||
return __builtin_bswap16(x);
|
||||
|
||||
@ -91,7 +97,11 @@ namespace TagLib
|
||||
*/
|
||||
inline uint byteSwap(uint x)
|
||||
{
|
||||
#if defined(HAVE_GCC_BYTESWAP)
|
||||
#if defined(HAVE_BOOST_BYTESWAP)
|
||||
|
||||
return boost::endian::endian_reverse(static_cast<uint32_t>(x));
|
||||
|
||||
#elif defined(HAVE_GCC_BYTESWAP)
|
||||
|
||||
return __builtin_bswap32(x);
|
||||
|
||||
@ -126,7 +136,11 @@ namespace TagLib
|
||||
*/
|
||||
inline ulonglong byteSwap(ulonglong x)
|
||||
{
|
||||
#if defined(HAVE_GCC_BYTESWAP)
|
||||
#if defined(HAVE_BOOST_BYTESWAP)
|
||||
|
||||
return boost::endian::endian_reverse(static_cast<uint64_t>(x));
|
||||
|
||||
#elif defined(HAVE_GCC_BYTESWAP)
|
||||
|
||||
return __builtin_bswap64(x);
|
||||
|
||||
@ -199,10 +213,10 @@ namespace TagLib
|
||||
|
||||
va_end(args);
|
||||
|
||||
if(length != -1)
|
||||
if(length > 0)
|
||||
return String(buf);
|
||||
else
|
||||
return String::null;
|
||||
return String();
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -638,7 +638,7 @@ void XM::File::read(bool)
|
||||
|
||||
d->properties.setSampleCount(sumSampleCount);
|
||||
String comment(intrumentNames.toString("\n"));
|
||||
if(sampleNames.size() > 0) {
|
||||
if(!sampleNames.isEmpty()) {
|
||||
comment += "\n";
|
||||
comment += sampleNames.toString("\n");
|
||||
}
|
||||
|
@ -103,7 +103,7 @@ public:
|
||||
{
|
||||
APE::Item item = APE::Item("DUMMY", "Test Text");
|
||||
CPPUNIT_ASSERT_EQUAL(String("Test Text"), item.toString());
|
||||
CPPUNIT_ASSERT_EQUAL(ByteVector::null, item.binaryData());
|
||||
CPPUNIT_ASSERT_EQUAL(ByteVector(), item.binaryData());
|
||||
|
||||
ByteVector data("Test Data");
|
||||
item.setBinaryData(data);
|
||||
@ -113,7 +113,7 @@ public:
|
||||
|
||||
item.setValue("Test Text 2");
|
||||
CPPUNIT_ASSERT_EQUAL(String("Test Text 2"), item.toString());
|
||||
CPPUNIT_ASSERT_EQUAL(ByteVector::null, item.binaryData());
|
||||
CPPUNIT_ASSERT_EQUAL(ByteVector(), item.binaryData());
|
||||
}
|
||||
|
||||
};
|
||||
|
@ -43,6 +43,7 @@ class TestByteVector : public CppUnit::TestFixture
|
||||
CPPUNIT_TEST(testReplace);
|
||||
CPPUNIT_TEST(testIterator);
|
||||
CPPUNIT_TEST(testResize);
|
||||
CPPUNIT_TEST(testAppend);
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
|
||||
public:
|
||||
@ -81,6 +82,10 @@ public:
|
||||
CPPUNIT_ASSERT(i.containsAt(j, 5, 0));
|
||||
CPPUNIT_ASSERT(i.containsAt(j, 6, 1));
|
||||
CPPUNIT_ASSERT(i.containsAt(j, 6, 1, 3));
|
||||
|
||||
i.clear();
|
||||
CPPUNIT_ASSERT(i.isEmpty());
|
||||
CPPUNIT_ASSERT(!i.isNull()); // deprecated, but worth it to check.
|
||||
}
|
||||
|
||||
void testFind1()
|
||||
@ -292,6 +297,8 @@ public:
|
||||
*it2 = 'I';
|
||||
CPPUNIT_ASSERT_EQUAL('i', *it1);
|
||||
CPPUNIT_ASSERT_EQUAL('I', *it2);
|
||||
CPPUNIT_ASSERT_EQUAL(ByteVector("taglib"), v1);
|
||||
CPPUNIT_ASSERT_EQUAL(ByteVector("taglIb"), v2);
|
||||
|
||||
ByteVector::ReverseIterator it3 = v1.rbegin();
|
||||
ByteVector::ReverseIterator it4 = v2.rbegin();
|
||||
@ -304,6 +311,8 @@ public:
|
||||
*it4 = 'A';
|
||||
CPPUNIT_ASSERT_EQUAL('a', *it3);
|
||||
CPPUNIT_ASSERT_EQUAL('A', *it4);
|
||||
CPPUNIT_ASSERT_EQUAL(ByteVector("taglib"), v1);
|
||||
CPPUNIT_ASSERT_EQUAL(ByteVector("tAglIb"), v2);
|
||||
|
||||
ByteVector v3;
|
||||
v3 = ByteVector("0123456789").mid(3, 4);
|
||||
@ -363,6 +372,20 @@ public:
|
||||
CPPUNIT_ASSERT_EQUAL(ByteVector::npos, c.find('C'));
|
||||
}
|
||||
|
||||
void testAppend()
|
||||
{
|
||||
ByteVector v1("taglib");
|
||||
ByteVector v2 = v1;
|
||||
|
||||
v1.append("ABC");
|
||||
CPPUNIT_ASSERT_EQUAL(ByteVector("taglibABC"), v1);
|
||||
v1.append('1');
|
||||
v1.append('2');
|
||||
v1.append('3');
|
||||
CPPUNIT_ASSERT_EQUAL(ByteVector("taglibABC123"), v1);
|
||||
CPPUNIT_ASSERT_EQUAL(ByteVector("taglib"), v2);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
CPPUNIT_TEST_SUITE_REGISTRATION(TestByteVector);
|
||||
|
@ -56,7 +56,7 @@ public:
|
||||
CPPUNIT_ASSERT_EQUAL(ByteVector("a"), stream.readBlock(1));
|
||||
CPPUNIT_ASSERT_EQUAL(ByteVector("bc"), stream.readBlock(2));
|
||||
CPPUNIT_ASSERT_EQUAL(ByteVector("d"), stream.readBlock(3));
|
||||
CPPUNIT_ASSERT_EQUAL(ByteVector::null, stream.readBlock(3));
|
||||
CPPUNIT_ASSERT_EQUAL(ByteVector(""), stream.readBlock(3));
|
||||
}
|
||||
|
||||
void testRemoveBlock()
|
||||
|
@ -40,7 +40,7 @@ class PublicFrame : public ID3v2::Frame
|
||||
}
|
||||
virtual String toString() const { return String::null; }
|
||||
virtual void parseFields(const ByteVector &) {}
|
||||
virtual ByteVector renderFields() const { return ByteVector::null; }
|
||||
virtual ByteVector renderFields() const { return ByteVector(); }
|
||||
};
|
||||
|
||||
class TestID3v2 : public CppUnit::TestFixture
|
||||
|
@ -19,6 +19,7 @@ class TestMP4 : public CppUnit::TestFixture
|
||||
CPPUNIT_TEST(testPropertiesALAC);
|
||||
CPPUNIT_TEST(testFreeForm);
|
||||
CPPUNIT_TEST(testCheckValid);
|
||||
CPPUNIT_TEST(testHasTag);
|
||||
CPPUNIT_TEST(testIsEmpty);
|
||||
CPPUNIT_TEST(testUpdateStco);
|
||||
CPPUNIT_TEST(testSaveExisingWhenIlstIsLast);
|
||||
@ -67,8 +68,30 @@ public:
|
||||
{
|
||||
MP4::File f(TEST_FILE_PATH_C("empty.aiff"));
|
||||
CPPUNIT_ASSERT(!f.isValid());
|
||||
MP4::File f2(TEST_FILE_PATH_C("has-tags.m4a"));
|
||||
CPPUNIT_ASSERT(f2.isValid());
|
||||
}
|
||||
|
||||
void testHasTag()
|
||||
{
|
||||
{
|
||||
MP4::File f(TEST_FILE_PATH_C("has-tags.m4a"));
|
||||
CPPUNIT_ASSERT(f.isValid());
|
||||
CPPUNIT_ASSERT(f.hasMP4Tag());
|
||||
}
|
||||
|
||||
ScopedFileCopy copy("no-tags", ".m4a");
|
||||
|
||||
{
|
||||
MP4::File f(copy.fileName().c_str());
|
||||
CPPUNIT_ASSERT(f.isValid());
|
||||
CPPUNIT_ASSERT(!f.hasMP4Tag());
|
||||
f.tag()->setTitle("TITLE");
|
||||
f.save();
|
||||
}
|
||||
{
|
||||
MP4::File f(copy.fileName().c_str());
|
||||
CPPUNIT_ASSERT(f.isValid());
|
||||
CPPUNIT_ASSERT(f.hasMP4Tag());
|
||||
}
|
||||
}
|
||||
|
||||
void testIsEmpty()
|
||||
@ -305,6 +328,14 @@ public:
|
||||
CPPUNIT_ASSERT(f.tag()->contains("cpil"));
|
||||
CPPUNIT_ASSERT_EQUAL(false, f.tag()->item("cpil").toBool());
|
||||
CPPUNIT_ASSERT_EQUAL(StringList("0"), tags["COMPILATION"]);
|
||||
|
||||
// Empty properties do not result in access violations
|
||||
// when converting integers
|
||||
tags["TRACKNUMBER"] = StringList();
|
||||
tags["DISCNUMBER"] = StringList();
|
||||
tags["BPM"] = StringList();
|
||||
tags["COMPILATION"] = StringList();
|
||||
f.setProperties(tags);
|
||||
}
|
||||
|
||||
void testFuzzedFile()
|
||||
|
@ -21,6 +21,7 @@ class TestOGG : public CppUnit::TestFixture
|
||||
CPPUNIT_TEST(testDictInterface1);
|
||||
CPPUNIT_TEST(testDictInterface2);
|
||||
CPPUNIT_TEST(testAudioProperties);
|
||||
CPPUNIT_TEST(testPageChecksum);
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
|
||||
public:
|
||||
@ -134,6 +135,30 @@ public:
|
||||
CPPUNIT_ASSERT_EQUAL(112000, f.audioProperties()->bitrateNominal());
|
||||
CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->bitrateMinimum());
|
||||
}
|
||||
|
||||
void testPageChecksum()
|
||||
{
|
||||
ScopedFileCopy copy("empty", ".ogg");
|
||||
|
||||
{
|
||||
Ogg::Vorbis::File f(copy.fileName().c_str());
|
||||
f.tag()->setArtist("The Artist");
|
||||
f.save();
|
||||
|
||||
f.seek(0x50);
|
||||
CPPUNIT_ASSERT_EQUAL((TagLib::uint)0x3d3bd92d, f.readBlock(4).toUInt32BE(0));
|
||||
}
|
||||
{
|
||||
Ogg::Vorbis::File f(copy.fileName().c_str());
|
||||
f.tag()->setArtist("The Artist 2");
|
||||
f.save();
|
||||
|
||||
f.seek(0x50);
|
||||
CPPUNIT_ASSERT_EQUAL((TagLib::uint)0xd985291c, f.readBlock(4).toUInt32BE(0));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
CPPUNIT_TEST_SUITE_REGISTRATION(TestOGG);
|
||||
|
@ -73,6 +73,10 @@ public:
|
||||
char str[] = "taglib string";
|
||||
CPPUNIT_ASSERT(strcmp(s.toCString(), str) == 0);
|
||||
|
||||
s.clear();
|
||||
CPPUNIT_ASSERT(s.isEmpty());
|
||||
CPPUNIT_ASSERT(!s.isNull());
|
||||
|
||||
String unicode("José Carlos", String::UTF8);
|
||||
CPPUNIT_ASSERT(strcmp(unicode.toCString(), "Jos\xe9 Carlos") == 0);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user