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:
Tsuda Kageyu 2015-11-19 11:37:18 +09:00
commit c2753f8d3c
54 changed files with 890 additions and 492 deletions

View File

@ -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()

View File

@ -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
View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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();

View File

@ -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);
}

View 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()) +

View File

@ -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;
}

View File

@ -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;
};
}

View File

@ -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))
{
}

View File

@ -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;
}

View File

@ -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);

View File

@ -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))
{
}

View File

@ -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);
}

View 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));
}

View 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

View File

@ -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;
}

View File

@ -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

View File

@ -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();

View File

@ -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 &);

View File

@ -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;
}

View File

@ -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.

View File

@ -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;
}

View File

@ -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

View File

@ -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];

View File

@ -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 {

View File

@ -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);
}

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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();
}

View File

@ -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();

View File

@ -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;
}

View File

@ -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

View File

@ -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)

View File

@ -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;
/*!

View File

@ -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();
}
}
}

View File

@ -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:

View File

@ -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();
}
/*!

View File

@ -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");
}

View File

@ -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());
}
};

View File

@ -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);

View File

@ -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()

View File

@ -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

View File

@ -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()

View File

@ -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);

View File

@ -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);