diff --git a/CMakeLists.txt b/CMakeLists.txt index fe5f2855..3904dad1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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() diff --git a/ConfigureChecks.cmake b/ConfigureChecks.cmake index 60e6d090..452ce84a 100644 --- a/ConfigureChecks.cmake +++ b/ConfigureChecks.cmake @@ -142,57 +142,69 @@ endif() # Determine which kind of byte swap functions your compiler supports. check_cxx_source_compiles(" + #include int main() { - __builtin_bswap16(0); - __builtin_bswap32(0); - __builtin_bswap64(0); + boost::endian::endian_reverse(static_cast(1)); + boost::endian::endian_reverse(static_cast(1)); + boost::endian::endian_reverse(static_cast(1)); return 0; } -" HAVE_GCC_BYTESWAP) +" HAVE_BOOST_BYTESWAP) -if(NOT HAVE_GCC_BYTESWAP) +if(NOT HAVE_BOOST_BYTESWAP) check_cxx_source_compiles(" - #include 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 + #include 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 + #include 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 + #include 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 + 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 diff --git a/NEWS b/NEWS index 5db9da59..b5a27901 100644 --- a/NEWS +++ b/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. diff --git a/bindings/c/taglib_c.pc.cmake b/bindings/c/taglib_c.pc.cmake index 61764fc3..232f4f78 100644 --- a/bindings/c/taglib_c.pc.cmake +++ b/bindings/c/taglib_c.pc.cmake @@ -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 diff --git a/config.h.cmake b/config.h.cmake index b48f8e6b..482673db 100644 --- a/config.h.cmake +++ b/config.h.cmake @@ -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 diff --git a/examples/tagwriter.cpp b/examples/tagwriter.cpp index f2896d76..ed8b0d7a 100644 --- a/examples/tagwriter.cpp +++ b/examples/tagwriter.cpp @@ -23,6 +23,7 @@ */ #include +#include #include #include @@ -34,6 +35,7 @@ #include #include #include +#include using namespace std; @@ -65,11 +67,32 @@ void usage() cout << " -g " << endl; cout << " -y " << endl; cout << " -T " << endl; + cout << " -R " << endl; + cout << " -I " << endl; + cout << " -D " << 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 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; diff --git a/taglib-config.cmake b/taglib-config.cmake index 2c2d2dbc..2bc2811a 100644 --- a/taglib-config.cmake +++ b/taglib-config.cmake @@ -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 diff --git a/taglib-config.cmd.cmake b/taglib-config.cmd.cmake index 3e2b1cde..fd96791c 100644 --- a/taglib-config.cmd.cmake +++ b/taglib-config.cmd.cmake @@ -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 - diff --git a/taglib.pc.cmake b/taglib.pc.cmake index 909b8fcf..5ee50aa5 100644 --- a/taglib.pc.cmake +++ b/taglib.pc.cmake @@ -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 diff --git a/taglib/CMakeLists.txt b/taglib/CMakeLists.txt index 5eb9ecac..29624f2e 100644 --- a/taglib/CMakeLists.txt +++ b/taglib/CMakeLists.txt @@ -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 diff --git a/taglib/ape/apeitem.cpp b/taglib/ape/apeitem.cpp index db729cdf..534fb102 100644 --- a/taglib/ape/apeitem.cpp +++ b/taglib/ape/apeitem.cpp @@ -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; diff --git a/taglib/ape/apetag.cpp b/taglib/ape/apetag.cpp index 7a034129..baaad7c2 100644 --- a/taglib/ape/apetag.cpp +++ b/taglib/ape/apetag.cpp @@ -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(); diff --git a/taglib/asf/asffile.cpp b/taglib/asf/asffile.cpp index 0e98a948..af975209 100644 --- a/taglib/asf/asffile.cpp +++ b/taglib/asf/asffile.cpp @@ -196,7 +196,7 @@ void ASF::File::FilePrivate::BaseObject::parse(ASF::File *file, unsigned int siz if(size > 24 && static_cast(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); } diff --git a/taglib/asf/asfpicture.cpp b/taglib/asf/asfpicture.cpp index e3fa8928..d25b9a4d 100644 --- a/taglib/asf/asfpicture.cpp +++ b/taglib/asf/asfpicture.cpp @@ -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()) + diff --git a/taglib/mp4/mp4atom.cpp b/taglib/mp4/mp4atom.cpp index e0d74304..79f25dcf 100644 --- a/taglib/mp4/mp4atom.cpp +++ b/taglib/mp4/mp4atom.cpp @@ -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; } - diff --git a/taglib/mp4/mp4atom.h b/taglib/mp4/mp4atom.h index 81ac35db..53bc16a6 100644 --- a/taglib/mp4/mp4atom.h +++ b/taglib/mp4/mp4atom.h @@ -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; }; } diff --git a/taglib/mp4/mp4coverart.cpp b/taglib/mp4/mp4coverart.cpp index b5ff8b89..fb9c2ed7 100644 --- a/taglib/mp4/mp4coverart.cpp +++ b/taglib/mp4/mp4coverart.cpp @@ -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)) { } diff --git a/taglib/mp4/mp4file.cpp b/taglib/mp4/mp4file.cpp index c19fee1c..0ea68ff6 100644 --- a/taglib/mp4/mp4file.cpp +++ b/taglib/mp4/mp4file.cpp @@ -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; +} diff --git a/taglib/mp4/mp4file.h b/taglib/mp4/mp4file.h index 2d39df40..f9a8502f 100644 --- a/taglib/mp4/mp4file.h +++ b/taglib/mp4/mp4file.h @@ -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); diff --git a/taglib/mp4/mp4item.cpp b/taglib/mp4/mp4item.cpp index 8f6ef0fa..db27d06e 100644 --- a/taglib/mp4/mp4item.cpp +++ b/taglib/mp4/mp4item.cpp @@ -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)) { } diff --git a/taglib/mp4/mp4tag.cpp b/taglib/mp4/mp4tag.cpp index 71f6b66a..d0ec43d4 100644 --- a/taglib/mp4/mp4tag.cpp +++ b/taglib/mp4/mp4tag.cpp @@ -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(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(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); } diff --git a/taglib/mpeg/id3v2/frames/podcastframe.cpp b/taglib/mpeg/id3v2/frames/podcastframe.cpp new file mode 100644 index 00000000..5115a7dd --- /dev/null +++ b/taglib/mpeg/id3v2/frames/podcastframe.cpp @@ -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)); +} diff --git a/taglib/mpeg/id3v2/frames/podcastframe.h b/taglib/mpeg/id3v2/frames/podcastframe.h new file mode 100644 index 00000000..7bbc2138 --- /dev/null +++ b/taglib/mpeg/id3v2/frames/podcastframe.h @@ -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 diff --git a/taglib/mpeg/id3v2/frames/textidentificationframe.cpp b/taglib/mpeg/id3v2/frames/textidentificationframe.cpp index 9bdfecc2..13059732 100644 --- a/taglib/mpeg/id3v2/frames/textidentificationframe.cpp +++ b/taglib/mpeg/id3v2/frames/textidentificationframe.cpp @@ -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; } diff --git a/taglib/mpeg/id3v2/frames/urllinkframe.cpp b/taglib/mpeg/id3v2/frames/urllinkframe.cpp index a8169ec5..173ca34d 100644 --- a/taglib/mpeg/id3v2/frames/urllinkframe.cpp +++ b/taglib/mpeg/id3v2/frames/urllinkframe.cpp @@ -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 diff --git a/taglib/mpeg/id3v2/id3v2frame.cpp b/taglib/mpeg/id3v2/id3v2frame.cpp index 2f625c46..b3ec3407 100644 --- a/taglib/mpeg/id3v2/id3v2frame.cpp +++ b/taglib/mpeg/id3v2/id3v2frame.cpp @@ -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 &idMap() - { - static Map m; - if(m.isEmpty()) - for(size_t i = 0; i < frameTranslationSize; ++i) - m[frameTranslation[i][0]] = frameTranslation[i][1]; - return m; - } - - Map &txxxMap() - { - static Map 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 &deprecationMap() - { - static Map 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 &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 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 &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 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(); diff --git a/taglib/mpeg/id3v2/id3v2frame.h b/taglib/mpeg/id3v2/id3v2frame.h index a2ac1891..2c063336 100644 --- a/taglib/mpeg/id3v2/id3v2frame.h +++ b/taglib/mpeg/id3v2/id3v2frame.h @@ -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 &); diff --git a/taglib/mpeg/id3v2/id3v2framefactory.cpp b/taglib/mpeg/id3v2/id3v2framefactory.cpp index 35f50cd4..3426f30a 100644 --- a/taglib/mpeg/id3v2/id3v2framefactory.cpp +++ b/taglib/mpeg/id3v2/id3v2framefactory.cpp @@ -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; } diff --git a/taglib/mpeg/id3v2/id3v2header.cpp b/taglib/mpeg/id3v2/id3v2header.cpp index 10381053..6e567193 100644 --- a/taglib/mpeg/id3v2/id3v2header.cpp +++ b/taglib/mpeg/id3v2/id3v2header.cpp @@ -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. diff --git a/taglib/mpeg/id3v2/id3v2tag.cpp b/taglib/mpeg/id3v2/id3v2tag.cpp index ca26dd20..b4a4bcf9 100644 --- a/taglib/mpeg/id3v2/id3v2tag.cpp +++ b/taglib/mpeg/id3v2/id3v2tag.cpp @@ -27,6 +27,8 @@ #include "config.h" #endif +#include + #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(tagData.size() + paddingSize), '\0'); // Set the version and data size. d->header.setMajorVersion(version); - d->header.setTagSize(static_cast(tagData.size())); + d->header.setTagSize(static_cast(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(frameDataLength - frameDataPosition); break; } diff --git a/taglib/mpeg/mpegfile.cpp b/taglib/mpeg/mpegfile.cpp index 0c2c849a..8ede1d67 100644 --- a/taglib/mpeg/mpegfile.cpp +++ b/taglib/mpeg/mpegfile.cpp @@ -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 diff --git a/taglib/ogg/oggfile.cpp b/taglib/ogg/oggfile.cpp index 2dff9592..dc6f4def 100644 --- a/taglib/ogg/oggfile.cpp +++ b/taglib/ogg/oggfile.cpp @@ -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]; diff --git a/taglib/ogg/xiphcomment.cpp b/taglib/ogg/xiphcomment.cpp index e124bd8a..5fb90c13 100644 --- a/taglib/ogg/xiphcomment.cpp +++ b/taglib/ogg/xiphcomment.cpp @@ -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 { diff --git a/taglib/riff/aiff/aiffproperties.cpp b/taglib/riff/aiff/aiffproperties.cpp index 8f6f998e..c965d4b0 100644 --- a/taglib/riff/aiff/aiffproperties.cpp +++ b/taglib/riff/aiff/aiffproperties.cpp @@ -177,7 +177,7 @@ void RIFF::AIFF::AudioProperties::read(File *file) d->sampleRate = static_cast(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(length + 0.5); d->bitrate = static_cast(streamLength * 8.0 / length + 0.5); } diff --git a/taglib/riff/rifffile.cpp b/taglib/riff/rifffile.cpp index 0fb0306a..872d5d3a 100644 --- a/taglib/riff/rifffile.cpp +++ b/taglib/riff/rifffile.cpp @@ -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); diff --git a/taglib/toolkit/tbytevector.cpp b/taglib/toolkit/tbytevector.cpp index 761a0468..2172a0fa 100644 --- a/taglib/toolkit/tbytevector.cpp +++ b/taglib/toolkit/tbytevector.cpp @@ -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::iterator and std::vector::reverse_iterator. - */ template 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::iterator and std::vector::reverse_iterator. - */ template 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(*(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(*it)]; - if(dataEnd - step <= it) - break; - - it += step; } return ByteVector::npos; @@ -263,6 +240,8 @@ ByteVector fromFloat(TFloat value) template 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 &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 &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::const_iterator begin = d->data->begin() + d->offset; - std::vector::const_iterator end = begin + d->length; - d->data.reset(new std::vector(begin, end)); - d->offset = 0; + if(!isEmpty()) + ByteVector(&d->data->front() + d->offset, d->length).swap(*this); + else + ByteVector().swap(*this); } } diff --git a/taglib/toolkit/tbytevector.h b/taglib/toolkit/tbytevector.h index e56ac67d..3948ea2d 100644 --- a/taglib/toolkit/tbytevector.h +++ b/taglib/toolkit/tbytevector.h @@ -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; diff --git a/taglib/toolkit/tbytevectorlist.cpp b/taglib/toolkit/tbytevectorlist.cpp index 6504711e..5aa47576 100644 --- a/taglib/toolkit/tbytevectorlist.cpp +++ b/taglib/toolkit/tbytevectorlist.cpp @@ -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(); } diff --git a/taglib/toolkit/tbytevectorstream.cpp b/taglib/toolkit/tbytevectorstream.cpp index 6ee3f3e0..c1aded9d 100644 --- a/taglib/toolkit/tbytevectorstream.cpp +++ b/taglib/toolkit/tbytevectorstream.cpp @@ -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(d->position), length); d->position += v.size(); diff --git a/taglib/toolkit/tfile.cpp b/taglib/toolkit/tfile.cpp index c07d2946..858bbc2e 100644 --- a/taglib/toolkit/tfile.cpp +++ b/taglib/toolkit/tfile.cpp @@ -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; } diff --git a/taglib/toolkit/tfile.h b/taglib/toolkit/tfile.h index 08de59eb..093bce48 100644 --- a/taglib/toolkit/tfile.h +++ b/taglib/toolkit/tfile.h @@ -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 diff --git a/taglib/toolkit/tfilestream.cpp b/taglib/toolkit/tfilestream.cpp index 882b3cdc..5075ce63 100644 --- a/taglib/toolkit/tfilestream.cpp +++ b/taglib/toolkit/tfilestream.cpp @@ -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(length) > streamLength) diff --git a/taglib/toolkit/tlist.h b/taglib/toolkit/tlist.h index ee40f3a9..3b445d89 100644 --- a/taglib/toolkit/tlist.h +++ b/taglib/toolkit/tlist.h @@ -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; /*! diff --git a/taglib/toolkit/tstring.cpp b/taglib/toolkit/tstring.cpp index 22a90187..f9aea6f2 100644 --- a/taglib/toolkit/tstring.cpp +++ b/taglib/toolkit/tstring.cpp @@ -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(); } } } diff --git a/taglib/toolkit/tstring.h b/taglib/toolkit/tstring.h index 2a95aaef..6a8f0f69 100644 --- a/taglib/toolkit/tstring.h +++ b/taglib/toolkit/tstring.h @@ -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: diff --git a/taglib/toolkit/tutils.h b/taglib/toolkit/tutils.h index 409af82b..146743a4 100644 --- a/taglib/toolkit/tutils.h +++ b/taglib/toolkit/tutils.h @@ -34,7 +34,9 @@ # include #endif -#if defined(HAVE_MSC_BYTESWAP) +#if defined(HAVE_BOOST_BYTESWAP) +# include +#elif defined(HAVE_MSC_BYTESWAP) # include #elif defined(HAVE_GLIBC_BYTESWAP) # include @@ -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(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(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(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(); } /*! diff --git a/taglib/xm/xmfile.cpp b/taglib/xm/xmfile.cpp index 031f8a1e..7888f872 100644 --- a/taglib/xm/xmfile.cpp +++ b/taglib/xm/xmfile.cpp @@ -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"); } diff --git a/tests/test_apetag.cpp b/tests/test_apetag.cpp index 17a7c4d9..72893c5b 100644 --- a/tests/test_apetag.cpp +++ b/tests/test_apetag.cpp @@ -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()); } }; diff --git a/tests/test_bytevector.cpp b/tests/test_bytevector.cpp index 5bfbadb9..4b0755be 100644 --- a/tests/test_bytevector.cpp +++ b/tests/test_bytevector.cpp @@ -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); diff --git a/tests/test_bytevectorstream.cpp b/tests/test_bytevectorstream.cpp index b0fec90b..7f6df152 100644 --- a/tests/test_bytevectorstream.cpp +++ b/tests/test_bytevectorstream.cpp @@ -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() diff --git a/tests/test_id3v2.cpp b/tests/test_id3v2.cpp index ed96499d..f7a91751 100644 --- a/tests/test_id3v2.cpp +++ b/tests/test_id3v2.cpp @@ -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 diff --git a/tests/test_mp4.cpp b/tests/test_mp4.cpp index c557e337..7eaea895 100644 --- a/tests/test_mp4.cpp +++ b/tests/test_mp4.cpp @@ -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() diff --git a/tests/test_ogg.cpp b/tests/test_ogg.cpp index b59b6aa0..f3abf793 100644 --- a/tests/test_ogg.cpp +++ b/tests/test_ogg.cpp @@ -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); diff --git a/tests/test_string.cpp b/tests/test_string.cpp index f0038826..29923a35 100644 --- a/tests/test_string.cpp +++ b/tests/test_string.cpp @@ -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);