diff --git a/.gitignore b/.gitignore index 5f49ed13..70912fde 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ cmake_install.cmake +cmake_uninstall.cmake Makefile +CTestTestfile.cmake CMakeFiles/ *.so *.so.* @@ -16,6 +18,7 @@ CMakeFiles/ /config.h /taglib.pc /tests/test_runner +/tests/Testing /taglib_config.h /taglib-config /bindings/c/taglib_c.pc diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..76e58336 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,7 @@ +language: cpp +compiler: + - gcc + - clang +install: sudo apt-get install libcppunit-dev zlib1g-dev +script: cmake -DBUILD_TESTS=ON -DBUILD_EXAMPLES=ON . && make && make check + diff --git a/CMakeLists.txt b/CMakeLists.txt old mode 100644 new mode 100755 index f4baae46..1f29e463 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -56,9 +56,9 @@ set(TAGLIB_LIB_VERSION_STRING "${TAGLIB_LIB_MAJOR_VERSION}.${TAGLIB_LIB_MINOR_VE # 2. If any interfaces have been added, removed, or changed since the last update, increment current, and set revision to 0. # 3. If any interfaces have been added since the last public release, then increment age. # 4. If any interfaces have been removed since the last public release, then set age to 0. -set(TAGLIB_SOVERSION_CURRENT 12) +set(TAGLIB_SOVERSION_CURRENT 13) set(TAGLIB_SOVERSION_REVISION 0) -set(TAGLIB_SOVERSION_AGE 11) +set(TAGLIB_SOVERSION_AGE 12) math(EXPR TAGLIB_SOVERSION_MAJOR "${TAGLIB_SOVERSION_CURRENT} - ${TAGLIB_SOVERSION_AGE}") math(EXPR TAGLIB_SOVERSION_MINOR "${TAGLIB_SOVERSION_AGE}") @@ -66,8 +66,15 @@ math(EXPR TAGLIB_SOVERSION_PATCH "${TAGLIB_SOVERSION_REVISION}") include(ConfigureChecks.cmake) -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/taglib-config.cmake ${CMAKE_CURRENT_BINARY_DIR}/taglib-config ) -install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/taglib-config DESTINATION ${BIN_INSTALL_DIR}) +if(NOT WIN32) + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/taglib-config.cmake ${CMAKE_CURRENT_BINARY_DIR}/taglib-config ) + install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/taglib-config DESTINATION ${BIN_INSTALL_DIR}) +endif() + +if(WIN32) + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/taglib-config.cmd.cmake ${CMAKE_CURRENT_BINARY_DIR}/taglib-config.cmd ) + install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/taglib-config.cmd DESTINATION ${BIN_INSTALL_DIR}) +endif() if(NOT WIN32 AND NOT BUILD_FRAMEWORK) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/taglib.pc.cmake ${CMAKE_CURRENT_BINARY_DIR}/taglib.pc ) diff --git a/NEWS b/NEWS index 6c407a48..24d396cd 100644 --- a/NEWS +++ b/NEWS @@ -1,5 +1,34 @@ -TagLib 1.8 BETA (Jul 14, 2012) -============================== +TagLib 1.9 (In Development) +========================== + + * Added support for the Ogg Opus file format. + * Added support for INFO tags in WAV files. + * Changed FileStream to use Windows file API. + * Included taglib-config.cmd script for Windows. + * New ID3v1::Tag methods for working directly with genre numbers. + * New MPEG::File methods for checking which tags are saved in the file. + * Better parsing of corrupted FLAC files. + * Fixed saving of PropertyMap comments without description into ID3v2 tags. + * Fixed crash when parsing certain XM files. + * Fixed compilation of unit test with clang. + +TagLib 1.8 (Sep 6, 2012) +======================== + +1.8: + + * Added support for OWNE ID3 frames. + * Changed key validation in the new PropertyMap API. + * ID3v1::Tag::setStringHandler will no londer delete the previous handler, + the caller is responsible for this. + * File objects will also no longer delete the passed IOStream objects. It + should be done in the caller code after the File object is no longer + used. + * Added ID3v2::Tag::setLatin1StringHandler for custom handling of + latin1-encoded text in ID3v2 frames. + * Fixed validation of ID3v2 frame IDs (IDs with '0' were ignored). + +1.8 BETA: * New API for accessing tags by name. * New abstract I/O stream layer to allow custom I/O handlers. diff --git a/examples/tagreader.cpp b/examples/tagreader.cpp index 76fe0d1a..cc978eba 100644 --- a/examples/tagreader.cpp +++ b/examples/tagreader.cpp @@ -23,10 +23,12 @@ */ #include +#include #include #include #include +#include using namespace std; @@ -49,7 +51,7 @@ int main(int argc, char *argv[]) TagLib::Tag *tag = f.tag(); - cout << "-- TAG --" << endl; + cout << "-- TAG (basic) --" << endl; cout << "title - \"" << tag->title() << "\"" << endl; cout << "artist - \"" << tag->artist() << "\"" << endl; cout << "album - \"" << tag->album() << "\"" << endl; @@ -57,6 +59,23 @@ int main(int argc, char *argv[]) cout << "comment - \"" << tag->comment() << "\"" << endl; cout << "track - \"" << tag->track() << "\"" << endl; cout << "genre - \"" << tag->genre() << "\"" << endl; + + TagLib::PropertyMap tags = f.file()->properties(); + + 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 << "-- TAG (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; + } + } + } if(!f.isNull() && f.audioProperties()) { diff --git a/taglib-config.cmd.cmake b/taglib-config.cmd.cmake new file mode 100755 index 00000000..bef752ed --- /dev/null +++ b/taglib-config.cmd.cmake @@ -0,0 +1,36 @@ +@echo off +goto beginning + * + * It is what it is, you can do with it as you please. + * + * Just don't blame me if it teaches your computer to smoke! + * + * -Enjoy + * fh :)_~ + * +:beginning +if /i "%1#" == "--libs#" goto doit +if /i "%1#" == "--cflags#" goto doit +if /i "%1#" == "--version#" goto doit +if /i "%1#" == "--prefix#" goto doit + +echo "usage: %0 [OPTIONS]" +echo [--libs] +echo [--cflags] +echo [--version] +echo [--prefix] +goto theend + + * + * NOTE: Windows does not assume libraries are prefixed with 'lib'. + * NOTE: If '-llibtag' is the last element, it is easily appended in the users installation/makefile process + * to allow for static, shared or debug builds. + * It would be preferable if the top level CMakeLists.txt provided the library name during config. ?? +: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#" == "--prefix#" echo ${CMAKE_INSTALL_PREFIX} + +:theend + diff --git a/taglib/CMakeLists.txt b/taglib/CMakeLists.txt index ca62f3a4..72712ca2 100644 --- a/taglib/CMakeLists.txt +++ b/taglib/CMakeLists.txt @@ -10,6 +10,7 @@ include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/mp4 ${CMAKE_CURRENT_SOURCE_DIR}/ogg/vorbis ${CMAKE_CURRENT_SOURCE_DIR}/ogg/speex + ${CMAKE_CURRENT_SOURCE_DIR}/ogg/opus ${CMAKE_CURRENT_SOURCE_DIR}/mpeg/id3v2 ${CMAKE_CURRENT_SOURCE_DIR}/mpeg/id3v2/frames ${CMAKE_CURRENT_SOURCE_DIR}/mpeg/id3v1 @@ -65,6 +66,7 @@ set(tag_HDRS mpeg/id3v2/frames/attachedpictureframe.h mpeg/id3v2/frames/commentsframe.h mpeg/id3v2/frames/generalencapsulatedobjectframe.h + mpeg/id3v2/frames/ownershipframe.h mpeg/id3v2/frames/popularimeterframe.h mpeg/id3v2/frames/privateframe.h mpeg/id3v2/frames/relativevolumeframe.h @@ -82,6 +84,8 @@ set(tag_HDRS ogg/flac/oggflacfile.h ogg/speex/speexfile.h ogg/speex/speexproperties.h + ogg/opus/opusfile.h + ogg/opus/opusproperties.h flac/flacfile.h flac/flacpicture.h flac/flacproperties.h @@ -102,6 +106,7 @@ set(tag_HDRS riff/aiff/aiffproperties.h riff/wav/wavfile.h riff/wav/wavproperties.h + riff/wav/infotag.h asf/asffile.h asf/asfproperties.h asf/asftag.h @@ -151,6 +156,7 @@ set(frames_SRCS mpeg/id3v2/frames/attachedpictureframe.cpp mpeg/id3v2/frames/commentsframe.cpp mpeg/id3v2/frames/generalencapsulatedobjectframe.cpp + mpeg/id3v2/frames/ownershipframe.cpp mpeg/id3v2/frames/popularimeterframe.cpp mpeg/id3v2/frames/privateframe.cpp mpeg/id3v2/frames/relativevolumeframe.cpp @@ -217,6 +223,11 @@ set(speex_SRCS ogg/speex/speexproperties.cpp ) +set(opus_SRCS + ogg/opus/opusfile.cpp + ogg/opus/opusproperties.cpp +) + set(trueaudio_SRCS trueaudio/trueaudiofile.cpp trueaudio/trueaudioproperties.cpp @@ -242,6 +253,7 @@ set(aiff_SRCS set(wav_SRCS riff/wav/wavfile.cpp riff/wav/wavproperties.cpp + riff/wav/infotag.cpp ) set(mod_SRCS @@ -284,7 +296,7 @@ set(tag_LIB_SRCS ${mpeg_SRCS} ${id3v1_SRCS} ${id3v2_SRCS} ${frames_SRCS} ${ogg_SRCS} ${vorbis_SRCS} ${oggflacs_SRCS} ${mpc_SRCS} ${ape_SRCS} ${toolkit_SRCS} ${flacs_SRCS} ${wavpack_SRCS} ${speex_SRCS} ${trueaudio_SRCS} ${riff_SRCS} ${aiff_SRCS} ${wav_SRCS} - ${asf_SRCS} ${mp4_SRCS} ${mod_SRCS} ${s3m_SRCS} ${it_SRCS} ${xm_SRCS} + ${asf_SRCS} ${mp4_SRCS} ${mod_SRCS} ${s3m_SRCS} ${it_SRCS} ${xm_SRCS} ${opus_SRCS} tag.cpp tagunion.cpp fileref.cpp diff --git a/taglib/ape/apefile.cpp b/taglib/ape/apefile.cpp index cb652242..2572a2e9 100644 --- a/taglib/ape/apefile.cpp +++ b/taglib/ape/apefile.cpp @@ -90,14 +90,16 @@ APE::File::File(FileName file, bool readProperties, Properties::ReadStyle propertiesStyle) : TagLib::File(file) { d = new FilePrivate; - read(readProperties, propertiesStyle); + if(isOpen()) + read(readProperties, propertiesStyle); } APE::File::File(IOStream *stream, bool readProperties, Properties::ReadStyle propertiesStyle) : TagLib::File(stream) { d = new FilePrivate; - read(readProperties, propertiesStyle); + if(isOpen()) + read(readProperties, propertiesStyle); } APE::File::~File() diff --git a/taglib/ape/apefile.h b/taglib/ape/apefile.h index 0bdbd422..7be0f21d 100644 --- a/taglib/ape/apefile.h +++ b/taglib/ape/apefile.h @@ -95,6 +95,9 @@ namespace TagLib { * Contructs an WavPack file from \a file. If \a readProperties is true the * file's audio properties will also be read using \a propertiesStyle. If * false, \a propertiesStyle is ignored. + * + * \note TagLib will *not* take ownership of the stream, the caller is + * responsible for deleting it after the File object. */ File(IOStream *stream, bool readProperties = true, Properties::ReadStyle propertiesStyle = Properties::Average); @@ -129,6 +132,7 @@ namespace TagLib { * has no tag at all, APE will be created. */ PropertyMap setProperties(const PropertyMap &); + /*! * Returns the APE::Properties for this file. If no audio properties * were read then this will return a null pointer. diff --git a/taglib/ape/apetag.cpp b/taglib/ape/apetag.cpp index f1af0028..664ee3a8 100644 --- a/taglib/ape/apetag.cpp +++ b/taglib/ape/apetag.cpp @@ -260,7 +260,7 @@ PropertyMap APE::Tag::setProperties(const PropertyMap &origProps) bool APE::Tag::checkKey(const String &key) { - if(key.size() < 2 or key.size() > 16) + if(key.size() < 2 || key.size() > 16) return false; for(String::ConstIterator it = key.begin(); it != key.end(); it++) // only allow printable ASCII including space (32..127) diff --git a/taglib/asf/asffile.cpp b/taglib/asf/asffile.cpp index e2823c7e..eb000924 100644 --- a/taglib/asf/asffile.cpp +++ b/taglib/asf/asffile.cpp @@ -29,6 +29,7 @@ #include #include +#include #include #include "asffile.h" #include "asftag.h" @@ -348,7 +349,7 @@ void ASF::File::HeaderExtensionObject::parse(ASF::File *file, uint /*size*/) else { obj = new UnknownObject(guid); } - obj->parse(file, size); + obj->parse(file, (unsigned int)size); objects.append(obj); dataPos += size; } @@ -372,14 +373,16 @@ ASF::File::File(FileName file, bool readProperties, Properties::ReadStyle proper : TagLib::File(file) { d = new FilePrivate; - read(readProperties, propertiesStyle); + if(isOpen()) + read(readProperties, propertiesStyle); } ASF::File::File(IOStream *stream, bool readProperties, Properties::ReadStyle propertiesStyle) : TagLib::File(stream) { d = new FilePrivate; - read(readProperties, propertiesStyle); + if(isOpen()) + read(readProperties, propertiesStyle); } ASF::File::~File() @@ -401,6 +404,21 @@ ASF::Tag *ASF::File::tag() const return d->tag; } +PropertyMap ASF::File::properties() const +{ + return d->tag->properties(); +} + +void ASF::File::removeUnsupportedProperties(const StringList &properties) +{ + d->tag->removeUnsupportedProperties(properties); +} + +PropertyMap ASF::File::setProperties(const PropertyMap &properties) +{ + return d->tag->setProperties(properties); +} + ASF::Properties *ASF::File::audioProperties() const { return d->properties; @@ -535,7 +553,7 @@ bool ASF::File::save() data.append(d->objects[i]->render(this)); } data = headerGuid + ByteVector::fromLongLong(data.size() + 30, false) + ByteVector::fromUInt(d->objects.size(), false) + ByteVector("\x01\x02", 2) + data; - insert(data, 0, d->size); + insert(data, 0, (TagLib::ulong)d->size); return true; } diff --git a/taglib/asf/asffile.h b/taglib/asf/asffile.h index f0acd728..7d0e3bc7 100644 --- a/taglib/asf/asffile.h +++ b/taglib/asf/asffile.h @@ -54,6 +54,9 @@ namespace TagLib { * * \note In the current implementation, both \a readProperties and * \a propertiesStyle are ignored. + * + * \note TagLib will *not* take ownership of the stream, the caller is + * responsible for deleting it after the File object. */ File(FileName file, bool readProperties = true, Properties::ReadStyle propertiesStyle = Properties::Average); @@ -64,6 +67,9 @@ namespace TagLib { * * \note In the current implementation, both \a readProperties and * \a propertiesStyle are ignored. + * + * \note TagLib will *not* take ownership of the stream, the caller is + * responsible for deleting it after the File object. */ File(IOStream *stream, bool readProperties = true, Properties::ReadStyle propertiesStyle = Properties::Average); @@ -84,6 +90,22 @@ namespace TagLib { */ virtual Tag *tag() const; + /*! + * Implements the unified property interface -- export function. + */ + PropertyMap properties() const; + + /*! + * Removes unsupported properties. Forwards to the actual Tag's + * removeUnsupportedProperties() function. + */ + void removeUnsupportedProperties(const StringList &properties); + + /*! + * Implements the unified property interface -- import function. + */ + PropertyMap setProperties(const PropertyMap &); + /*! * Returns the ASF audio properties for this file. */ diff --git a/taglib/asf/asftag.cpp b/taglib/asf/asftag.cpp index b7b541fc..1cbd16eb 100644 --- a/taglib/asf/asftag.cpp +++ b/taglib/asf/asftag.cpp @@ -27,6 +27,7 @@ #include #endif +#include #include "asftag.h" using namespace TagLib; @@ -196,3 +197,162 @@ bool ASF::Tag::isEmpty() const d->attributeListMap.isEmpty(); } +static const char *keyTranslation[][2] = { + { "WM/AlbumTitle", "ALBUM" }, + { "WM/Composer", "COMPOSER" }, + { "WM/Writer", "WRITER" }, + { "WM/Conductor", "CONDUCTOR" }, + { "WM/ModifiedBy", "REMIXER" }, + { "WM/Year", "DATE" }, + { "WM/OriginalReleaseYear", "ORIGINALDATE" }, + { "WM/Producer", "PRODUCER" }, + { "WM/ContentGroupDescription", "GROUPING" }, + { "WM/SubTitle", "SUBTITLE" }, + { "WM/SetSubTitle", "DISCSUBTITLE" }, + { "WM/TrackNumber", "TRACKNUMBER" }, + { "WM/PartOfSet", "DISCNUMBER" }, + { "WM/Genre", "GENRE" }, + { "WM/BeatsPerMinute", "BPM" }, + { "WM/Mood", "MOOD" }, + { "WM/ISRC", "ISRC" }, + { "WM/Lyrics", "LYRICS" }, + { "WM/Media", "MEDIA" }, + { "WM/Publisher", "LABEL" }, + { "WM/CatalogNo", "CATALOGNUMBER" }, + { "WM/Barcode", "BARCODE" }, + { "WM/EncodedBy", "ENCODEDBY" }, + { "WM/AlbumSortOrder", "ALBUMSORT" }, + { "WM/AlbumArtistSortOrder", "ALBUMARTISTSORT" }, + { "WM/ArtistSortOrder", "ARTISTSORT" }, + { "WM/TitleSortOrder", "TITLESORT" }, + { "WM/Script", "SCRIPT" }, + { "WM/Language", "LANGUAGE" }, + { "MusicBrainz/Track Id", "MUSICBRAINZ_TRACKID" }, + { "MusicBrainz/Artist Id", "MUSICBRAINZ_ARTISTID" }, + { "MusicBrainz/Album Id", "MUSICBRAINZ_ALBUMID" }, + { "MusicBrainz/Album Artist Id", "MUSICBRAINZ_ALBUMARTISTID" }, + { "MusicBrainz/Release Group Id", "MUSICBRAINZ_RELEASEGROUPID" }, + { "MusicBrainz/Work Id", "MUSICBRAINZ_WORKID" }, + { "MusicIP/PUID", "MUSICIP_PUID" }, + { "Acoustid/Id", "ACOUSTID_ID" }, + { "Acoustid/Fingerprint", "ACOUSTID_FINGERPRINT" }, +}; + +PropertyMap ASF::Tag::properties() const +{ + static Map keyMap; + if(keyMap.isEmpty()) { + int numKeys = sizeof(keyTranslation) / sizeof(keyTranslation[0]); + for(int i = 0; i < numKeys; i++) { + keyMap[keyTranslation[i][0]] = keyTranslation[i][1]; + } + } + + PropertyMap props; + + if(!d->title.isEmpty()) { + props["TITLE"] = d->title; + } + if(!d->artist.isEmpty()) { + props["ARTIST"] = d->artist; + } + if(!d->copyright.isEmpty()) { + props["COPYRIGHT"] = d->copyright; + } + if(!d->comment.isEmpty()) { + props["COMMENT"] = d->comment; + } + + ASF::AttributeListMap::ConstIterator it = d->attributeListMap.begin(); + for(; it != d->attributeListMap.end(); ++it) { + if(keyMap.contains(it->first)) { + String key = keyMap[it->first]; + AttributeList::ConstIterator it2 = it->second.begin(); + for(; it2 != it->second.end(); ++it2) { + if(key == "TRACKNUMBER") { + if(it2->type() == ASF::Attribute::DWordType) + props.insert(key, String::number(it2->toUInt())); + else + props.insert(key, it2->toString()); + } + else { + props.insert(key, it2->toString()); + } + } + } + else { + props.unsupportedData().append(it->first); + } + } + return props; +} + +void ASF::Tag::removeUnsupportedProperties(const StringList &props) +{ + StringList::ConstIterator it = props.begin(); + for(; it != props.end(); ++it) + d->attributeListMap.erase(*it); +} + +PropertyMap ASF::Tag::setProperties(const PropertyMap &props) +{ + static Map reverseKeyMap; + if(reverseKeyMap.isEmpty()) { + int numKeys = sizeof(keyTranslation) / sizeof(keyTranslation[0]); + for(int i = 0; i < numKeys; i++) { + reverseKeyMap[keyTranslation[i][1]] = keyTranslation[i][0]; + } + } + + PropertyMap origProps = properties(); + PropertyMap::ConstIterator it = origProps.begin(); + for(; it != origProps.end(); ++it) { + if(!props.contains(it->first) || props[it->first].isEmpty()) { + if(it->first == "TITLE") { + d->title = String::null; + } + else if(it->first == "ARTIST") { + d->artist = String::null; + } + else if(it->first == "COMMENT") { + d->comment = String::null; + } + else if(it->first == "COPYRIGHT") { + d->copyright = String::null; + } + else { + d->attributeListMap.erase(reverseKeyMap[it->first]); + } + } + } + + PropertyMap ignoredProps; + it = props.begin(); + for(; it != props.end(); ++it) { + if(reverseKeyMap.contains(it->first)) { + String name = reverseKeyMap[it->first]; + removeItem(name); + StringList::ConstIterator it2 = it->second.begin(); + for(; it2 != it->second.end(); ++it2) { + addAttribute(name, *it2); + } + } + else if(it->first == "TITLE") { + d->title = it->second.toString(); + } + else if(it->first == "ARTIST") { + d->artist = it->second.toString(); + } + else if(it->first == "COMMENT") { + d->comment = it->second.toString(); + } + else if(it->first == "COPYRIGHT") { + d->copyright = it->second.toString(); + } + else { + ignoredProps.insert(it->first, it->second); + } + } + + return ignoredProps; +} diff --git a/taglib/asf/asftag.h b/taglib/asf/asftag.h index 6b2d2bf3..f68579d8 100644 --- a/taglib/asf/asftag.h +++ b/taglib/asf/asftag.h @@ -176,6 +176,10 @@ namespace TagLib { */ void addAttribute(const String &name, const Attribute &attribute); + PropertyMap properties() const; + void removeUnsupportedProperties(const StringList& properties); + PropertyMap setProperties(const PropertyMap &properties); + private: class TagPrivate; diff --git a/taglib/fileref.cpp b/taglib/fileref.cpp index 5c42ed4c..859f3155 100644 --- a/taglib/fileref.cpp +++ b/taglib/fileref.cpp @@ -45,6 +45,7 @@ #include "mp4file.h" #include "wavpackfile.h" #include "speexfile.h" +#include "opusfile.h" #include "trueaudiofile.h" #include "aifffile.h" #include "wavfile.h" @@ -252,6 +253,8 @@ File *FileRef::create(FileName fileName, bool readAudioProperties, return new WavPack::File(fileName, readAudioProperties, audioPropertiesStyle); if(ext == "SPX") return new Ogg::Speex::File(fileName, readAudioProperties, audioPropertiesStyle); + if(ext == "OPUS") + return new Ogg::Opus::File(fileName, readAudioProperties, audioPropertiesStyle); if(ext == "TTA") return new TrueAudio::File(fileName, readAudioProperties, audioPropertiesStyle); if(ext == "M4A" || ext == "M4R" || ext == "M4B" || ext == "M4P" || ext == "MP4" || ext == "3G2") diff --git a/taglib/flac/flacfile.cpp b/taglib/flac/flacfile.cpp index 79acb539..0bfe84c3 100644 --- a/taglib/flac/flacfile.cpp +++ b/taglib/flac/flacfile.cpp @@ -70,7 +70,8 @@ public: ~FilePrivate() { - for(uint i = 0; i < blocks.size(); i++) { + uint size = blocks.size(); + for(uint i = 0; i < size; i++) { delete blocks[i]; } delete properties; @@ -108,7 +109,8 @@ FLAC::File::File(FileName file, bool readProperties, TagLib::File(file) { d = new FilePrivate; - read(readProperties, propertiesStyle); + if(isOpen()) + read(readProperties, propertiesStyle); } FLAC::File::File(FileName file, ID3v2::FrameFactory *frameFactory, @@ -117,7 +119,8 @@ FLAC::File::File(FileName file, ID3v2::FrameFactory *frameFactory, { d = new FilePrivate; d->ID3v2FrameFactory = frameFactory; - read(readProperties, propertiesStyle); + if(isOpen()) + read(readProperties, propertiesStyle); } FLAC::File::File(IOStream *stream, ID3v2::FrameFactory *frameFactory, @@ -126,7 +129,8 @@ FLAC::File::File(IOStream *stream, ID3v2::FrameFactory *frameFactory, { d = new FilePrivate; d->ID3v2FrameFactory = frameFactory; - read(readProperties, propertiesStyle); + if(isOpen()) + read(readProperties, propertiesStyle); } FLAC::File::~File() @@ -243,7 +247,7 @@ bool FLAC::File::save() } ByteVector padding = ByteVector::fromUInt(paddingLength); padding.resize(paddingLength + 4); - padding[0] = FLAC::MetadataBlock::Padding | LastBlockFlag; + padding[0] = (char)(FLAC::MetadataBlock::Padding | LastBlockFlag); data.append(padding); // Write the data to the file @@ -425,7 +429,7 @@ void FLAC::File::scan() length = header.mid(1, 3).toUInt(); ByteVector data = readBlock(length); - if(data.size() != length) { + if(data.size() != length || length == 0) { debug("FLAC::File::scan() -- FLAC stream corrupted"); setValid(false); return; diff --git a/taglib/flac/flacfile.h b/taglib/flac/flacfile.h index 31dfebd7..716d4478 100644 --- a/taglib/flac/flacfile.h +++ b/taglib/flac/flacfile.h @@ -97,6 +97,9 @@ namespace TagLib { * * If this file contains and ID3v2 tag the frames will be created using * \a frameFactory. + * + * \note TagLib will *not* take ownership of the stream, the caller is + * responsible for deleting it after the File object. */ // BIC: merge with the above constructor File(IOStream *stream, ID3v2::FrameFactory *frameFactory, diff --git a/taglib/it/itfile.cpp b/taglib/it/itfile.cpp index 51b57b0c..ad5cf0b8 100644 --- a/taglib/it/itfile.cpp +++ b/taglib/it/itfile.cpp @@ -45,7 +45,8 @@ IT::File::File(FileName file, bool readProperties, Mod::FileBase(file), d(new FilePrivate(propertiesStyle)) { - read(readProperties); + if(isOpen()) + read(readProperties); } IT::File::File(IOStream *stream, bool readProperties, @@ -53,7 +54,8 @@ IT::File::File(IOStream *stream, bool readProperties, Mod::FileBase(stream), d(new FilePrivate(propertiesStyle)) { - read(readProperties); + if(isOpen()) + read(readProperties); } IT::File::~File() @@ -128,7 +130,7 @@ bool IT::File::save() seek(sampleOffset + 20); - if((i + instrumentCount) < lines.size()) + if((TagLib::uint)(i + instrumentCount) < lines.size()) writeString(lines[i + instrumentCount], 25); else writeString(String::null, 25); diff --git a/taglib/it/itfile.h b/taglib/it/itfile.h index 9c507742..5584b7cd 100644 --- a/taglib/it/itfile.h +++ b/taglib/it/itfile.h @@ -48,6 +48,9 @@ namespace TagLib { * Contructs a Impulse Tracker file from \a stream. If \a readProperties * is true the file's audio properties will also be read using * \a propertiesStyle. If false, \a propertiesStyle is ignored. + * + * \note TagLib will *not* take ownership of the stream, the caller is + * responsible for deleting it after the File object. */ File(IOStream *stram, bool readProperties = true, AudioProperties::ReadStyle propertiesStyle = diff --git a/taglib/mod/modfile.cpp b/taglib/mod/modfile.cpp index 25fc8715..ce974c16 100644 --- a/taglib/mod/modfile.cpp +++ b/taglib/mod/modfile.cpp @@ -45,7 +45,8 @@ Mod::File::File(FileName file, bool readProperties, Mod::FileBase(file), d(new FilePrivate(propertiesStyle)) { - read(readProperties); + if(isOpen()) + read(readProperties); } Mod::File::File(IOStream *stream, bool readProperties, @@ -53,7 +54,8 @@ Mod::File::File(IOStream *stream, bool readProperties, Mod::FileBase(stream), d(new FilePrivate(propertiesStyle)) { - read(readProperties); + if(isOpen()) + read(readProperties); } Mod::File::~File() diff --git a/taglib/mod/modfile.h b/taglib/mod/modfile.h index 9e79659c..ad1e43b8 100644 --- a/taglib/mod/modfile.h +++ b/taglib/mod/modfile.h @@ -49,6 +49,9 @@ namespace TagLib { * Contructs a Protracker file from \a stream. If \a readProperties * is true the file's audio properties will also be read using * \a propertiesStyle. If false, \a propertiesStyle is ignored. + * + * \note TagLib will *not* take ownership of the stream, the caller is + * responsible for deleting it after the File object. */ File(IOStream *stream, bool readProperties = true, AudioProperties::ReadStyle propertiesStyle = diff --git a/taglib/mp4/mp4coverart.h b/taglib/mp4/mp4coverart.h index 804ab785..64115b45 100644 --- a/taglib/mp4/mp4coverart.h +++ b/taglib/mp4/mp4coverart.h @@ -42,10 +42,11 @@ namespace TagLib { * This describes the image type. */ enum Format { - JPEG = TypeJPEG, - PNG = TypePNG, - BMP = TypeBMP, - GIF = TypeGIF + JPEG = TypeJPEG, + PNG = TypePNG, + BMP = TypeBMP, + GIF = TypeGIF, + Unknown = TypeImplicit, }; CoverArt(Format format, const ByteVector &data); diff --git a/taglib/mp4/mp4file.cpp b/taglib/mp4/mp4file.cpp index 02185575..f41ee888 100644 --- a/taglib/mp4/mp4file.cpp +++ b/taglib/mp4/mp4file.cpp @@ -29,6 +29,7 @@ #include #include +#include #include "mp4atom.h" #include "mp4tag.h" #include "mp4file.h" @@ -67,14 +68,16 @@ MP4::File::File(FileName file, bool readProperties, AudioProperties::ReadStyle a : TagLib::File(file) { d = new FilePrivate; - read(readProperties, audioPropertiesStyle); + if(isOpen()) + read(readProperties, audioPropertiesStyle); } MP4::File::File(IOStream *stream, bool readProperties, AudioProperties::ReadStyle audioPropertiesStyle) : TagLib::File(stream) { d = new FilePrivate; - read(readProperties, audioPropertiesStyle); + if(isOpen()) + read(readProperties, audioPropertiesStyle); } MP4::File::~File() @@ -88,6 +91,21 @@ MP4::File::tag() const return d->tag; } +PropertyMap MP4::File::properties() const +{ + return d->tag->properties(); +} + +void MP4::File::removeUnsupportedProperties(const StringList &properties) +{ + d->tag->removeUnsupportedProperties(properties); +} + +PropertyMap MP4::File::setProperties(const PropertyMap &properties) +{ + return d->tag->setProperties(properties); +} + MP4::Properties * MP4::File::audioProperties() const { diff --git a/taglib/mp4/mp4file.h b/taglib/mp4/mp4file.h index c3613f76..17fd5a95 100644 --- a/taglib/mp4/mp4file.h +++ b/taglib/mp4/mp4file.h @@ -65,6 +65,9 @@ namespace TagLib { * * \note In the current implementation, both \a readProperties and * \a propertiesStyle are ignored. + * + * \note TagLib will *not* take ownership of the stream, the caller is + * responsible for deleting it after the File object. */ File(IOStream *stream, bool readProperties = true, Properties::ReadStyle audioPropertiesStyle = Properties::Average); @@ -85,6 +88,22 @@ namespace TagLib { */ Tag *tag() const; + /*! + * Implements the unified property interface -- export function. + */ + PropertyMap properties() const; + + /*! + * Removes unsupported properties. Forwards to the actual Tag's + * removeUnsupportedProperties() function. + */ + void removeUnsupportedProperties(const StringList &properties); + + /*! + * Implements the unified property interface -- import function. + */ + PropertyMap setProperties(const PropertyMap &); + /*! * Returns the MP4 audio properties for this file. */ diff --git a/taglib/mp4/mp4tag.cpp b/taglib/mp4/mp4tag.cpp index fc75fb45..3c22f062 100644 --- a/taglib/mp4/mp4tag.cpp +++ b/taglib/mp4/mp4tag.cpp @@ -29,6 +29,7 @@ #include #include +#include #include "mp4atom.h" #include "mp4tag.h" #include "id3v1genres.h" @@ -283,10 +284,13 @@ MP4::Tag::parseCovr(MP4::Atom *atom, TagLib::File *file) debug("MP4: Unexpected atom \"" + name + "\", expecting \"data\""); break; } - if(flags == TypeJPEG || flags == TypePNG || flags == TypeBMP || flags == TypeGIF) { + if(flags == TypeJPEG || flags == TypePNG || flags == TypeBMP || flags == TypeGIF || flags == TypeImplicit) { value.append(MP4::CoverArt(MP4::CoverArt::Format(flags), data.mid(pos + 16, length - 16))); } + else { + debug("MP4: Unknown covr format " + String::number(flags)); + } pos += length; } if(value.size() > 0) @@ -570,7 +574,7 @@ MP4::Tag::updateOffsets(long delta, long offset) atom->offset += delta; } d->file->seek(atom->offset + 9); - ByteVector data = d->file->readBlock(atom->offset - 9); + ByteVector data = d->file->readBlock(atom->length - 9); unsigned int flags = (ByteVector(1, '\0') + data.mid(0, 3)).toUInt(); if(flags & 1) { long long o = data.mid(7, 8).toLongLong(); @@ -756,3 +760,153 @@ MP4::Tag::itemListMap() return d->items; } +static const char *keyTranslation[][2] = { + { "\251nam", "TITLE" }, + { "\251ART", "ARTIST" }, + { "\251alb", "ALBUM" }, + { "\251cmt", "COMMENT" }, + { "\251gen", "GENRE" }, + { "\251day", "DATE" }, + { "\251wrt", "COMPOSER" }, + { "\251grp", "GROUPING" }, + { "trkn", "TRACKNUMBER" }, + { "disk", "DISCNUMBER" }, + { "cpil", "COMPILATION" }, + { "tmpo", "BPM" }, + { "cprt", "COPYRIGHT" }, + { "\251lyr", "LYRICS" }, + { "\251too", "ENCODEDBY" }, + { "soal", "ALBUMSORT" }, + { "soaa", "ALBUMARTISTSORT" }, + { "soar", "ARTISTSORT" }, + { "sonm", "TITLESORT" }, + { "soco", "COMPOSERSORT" }, + { "sosn", "SHOWSORT" }, + { "----:com.apple.iTunes:MusicBrainz Track Id", "MUSICBRAINZ_TRACKID" }, + { "----:com.apple.iTunes:MusicBrainz Artist Id", "MUSICBRAINZ_ARTISTID" }, + { "----:com.apple.iTunes:MusicBrainz Album Id", "MUSICBRAINZ_ALBUMID" }, + { "----:com.apple.iTunes:MusicBrainz Album Artist Id", "MUSICBRAINZ_ALBUMARTISTID" }, + { "----:com.apple.iTunes:MusicBrainz Release Group Id", "MUSICBRAINZ_RELEASEGROUPID" }, + { "----:com.apple.iTunes:MusicBrainz Work Id", "MUSICBRAINZ_WORKID" }, + { "----:com.apple.iTunes:ASIN", "ASIN" }, + { "----:com.apple.iTunes:LABEL", "LABEL" }, + { "----:com.apple.iTunes:LYRICIST", "LYRICIST" }, + { "----:com.apple.iTunes:CONDUCTOR", "CONDUCTOR" }, + { "----:com.apple.iTunes:REMIXER", "REMIXER" }, + { "----:com.apple.iTunes:ENGINEER", "ENGINEER" }, + { "----:com.apple.iTunes:PRODUCER", "PRODUCER" }, + { "----:com.apple.iTunes:DJMIXER", "DJMIXER" }, + { "----:com.apple.iTunes:MIXER", "MIXER" }, + { "----:com.apple.iTunes:SUBTITLE", "SUBTITLE" }, + { "----:com.apple.iTunes:DISCSUBTITLE", "DISCSUBTITLE" }, + { "----:com.apple.iTunes:MOOD", "MOOD" }, + { "----:com.apple.iTunes:ISRC", "ISRC" }, + { "----:com.apple.iTunes:CATALOGNUMBER", "CATALOGNUMBER" }, + { "----:com.apple.iTunes:BARCODE", "BARCODE" }, + { "----:com.apple.iTunes:SCRIPT", "SCRIPT" }, + { "----:com.apple.iTunes:LANGUAGE", "LANGUAGE" }, + { "----:com.apple.iTunes:LICENSE", "LICENSE" }, + { "----:com.apple.iTunes:MEDIA", "MEDIA" }, +}; + +PropertyMap MP4::Tag::properties() const +{ + static Map keyMap; + if(keyMap.isEmpty()) { + int numKeys = sizeof(keyTranslation) / sizeof(keyTranslation[0]); + for(int i = 0; i < numKeys; i++) { + keyMap[keyTranslation[i][0]] = keyTranslation[i][1]; + } + } + + PropertyMap props; + MP4::ItemListMap::ConstIterator it = d->items.begin(); + for(; it != d->items.end(); ++it) { + if(keyMap.contains(it->first)) { + String key = keyMap[it->first]; + if(key == "TRACKNUMBER" || key == "DISCNUMBER") { + MP4::Item::IntPair ip = it->second.toIntPair(); + String value = String::number(ip.first); + if(ip.second) { + value += "/" + String::number(ip.second); + } + props[key] = value; + } + else if(key == "BPM") { + props[key] = String::number(it->second.toInt()); + } + else if(key == "COMPILATION") { + props[key] = String::number(it->second.toBool()); + } + else { + props[key] = it->second.toStringList(); + } + } + else { + props.unsupportedData().append(it->first); + } + } + return props; +} + +void MP4::Tag::removeUnsupportedProperties(const StringList &props) +{ + StringList::ConstIterator it = props.begin(); + for(; it != props.end(); ++it) + d->items.erase(*it); +} + +PropertyMap MP4::Tag::setProperties(const PropertyMap &props) +{ + static Map reverseKeyMap; + if(reverseKeyMap.isEmpty()) { + int numKeys = sizeof(keyTranslation) / sizeof(keyTranslation[0]); + for(int i = 0; i < numKeys; i++) { + reverseKeyMap[keyTranslation[i][1]] = keyTranslation[i][0]; + } + } + + PropertyMap origProps = properties(); + PropertyMap::ConstIterator it = origProps.begin(); + for(; 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) { + if(reverseKeyMap.contains(it->first)) { + String name = reverseKeyMap[it->first]; + if(it->first == "TRACKNUMBER" || it->first == "DISCNUMBER") { + int first = 0, second = 0; + StringList parts = StringList::split(it->second.front(), "/"); + if(parts.size() > 0) { + first = parts[0].toInt(); + if(parts.size() > 1) { + second = parts[1].toInt(); + } + d->items[name] = MP4::Item(first, second); + } + } + else if(it->first == "BPM") { + int value = it->second.front().toInt(); + d->items[name] = MP4::Item(value); + } + else if(it->first == "COMPILATION") { + bool value = it->second.front().toInt(); + d->items[name] = MP4::Item(value > 0); + } + else { + d->items[name] = it->second; + } + } + else { + ignoredProps.insert(it->first, it->second); + } + } + + return ignoredProps; +} + diff --git a/taglib/mp4/mp4tag.h b/taglib/mp4/mp4tag.h index b5ea6ebb..0e1d0676 100644 --- a/taglib/mp4/mp4tag.h +++ b/taglib/mp4/mp4tag.h @@ -67,6 +67,10 @@ namespace TagLib { ItemListMap &itemListMap(); + PropertyMap properties() const; + void removeUnsupportedProperties(const StringList& properties); + PropertyMap setProperties(const PropertyMap &properties); + private: AtomDataList parseData2(Atom *atom, TagLib::File *file, int expectedFlags = -1, bool freeForm = false); TagLib::ByteVectorList parseData(Atom *atom, TagLib::File *file, int expectedFlags = -1, bool freeForm = false); diff --git a/taglib/mpc/mpcfile.cpp b/taglib/mpc/mpcfile.cpp index 519a0467..18f533f8 100644 --- a/taglib/mpc/mpcfile.cpp +++ b/taglib/mpc/mpcfile.cpp @@ -94,14 +94,16 @@ MPC::File::File(FileName file, bool readProperties, Properties::ReadStyle propertiesStyle) : TagLib::File(file) { d = new FilePrivate; - read(readProperties, propertiesStyle); + if(isOpen()) + read(readProperties, propertiesStyle); } MPC::File::File(IOStream *stream, bool readProperties, Properties::ReadStyle propertiesStyle) : TagLib::File(stream) { d = new FilePrivate; - read(readProperties, propertiesStyle); + if(isOpen()) + read(readProperties, propertiesStyle); } MPC::File::~File() diff --git a/taglib/mpc/mpcfile.h b/taglib/mpc/mpcfile.h index 61ac6d67..167b768e 100644 --- a/taglib/mpc/mpcfile.h +++ b/taglib/mpc/mpcfile.h @@ -95,6 +95,9 @@ namespace TagLib { * Contructs an MPC file from \a file. If \a readProperties is true the * file's audio properties will also be read using \a propertiesStyle. If * false, \a propertiesStyle is ignored. + * + * \note TagLib will *not* take ownership of the stream, the caller is + * responsible for deleting it after the File object. */ File(IOStream *stream, bool readProperties = true, Properties::ReadStyle propertiesStyle = Properties::Average); diff --git a/taglib/mpc/mpcproperties.cpp b/taglib/mpc/mpcproperties.cpp index 7046db99..23d41987 100644 --- a/taglib/mpc/mpcproperties.cpp +++ b/taglib/mpc/mpcproperties.cpp @@ -214,7 +214,7 @@ void MPC::Properties::readSV8(File *file) d->channels = flags[7] * 8 + flags[6] * 4 + flags[5] * 2 + flags[4] + 1; if((d->sampleFrames - begSilence) != 0) - d->bitrate = d->streamLength * 8.0 * d->sampleRate / (d->sampleFrames - begSilence); + d->bitrate = (int)(d->streamLength * 8.0 * d->sampleRate / (d->sampleFrames - begSilence)); d->bitrate = d->bitrate / 1000; d->length = (d->sampleFrames - begSilence) / d->sampleRate; @@ -279,10 +279,10 @@ void MPC::Properties::readSV7(const ByteVector &data) } if (d->trackPeak != 0) - d->trackPeak = (int)(log10(d->trackPeak) * 20 * 256 + .5); + d->trackPeak = (int)(log10((double)d->trackPeak) * 20 * 256 + .5); if (d->albumPeak != 0) - d->albumPeak = (int)(log10(d->albumPeak) * 20 * 256 + .5); + d->albumPeak = (int)(log10((double)d->albumPeak) * 20 * 256 + .5); bool trueGapless = (gapless >> 31) & 0x0001; if(trueGapless) { diff --git a/taglib/mpeg/id3v1/id3v1tag.cpp b/taglib/mpeg/id3v1/id3v1tag.cpp index 94d533ae..9fc8cfd7 100644 --- a/taglib/mpeg/id3v1/id3v1tag.cpp +++ b/taglib/mpeg/id3v1/id3v1tag.cpp @@ -182,22 +182,32 @@ void ID3v1::Tag::setGenre(const String &s) d->genre = ID3v1::genreIndex(s); } -void ID3v1::Tag::setYear(uint i) +void ID3v1::Tag::setYear(TagLib::uint i) { d->year = i > 0 ? String::number(i) : String::null; } -void ID3v1::Tag::setTrack(uint i) +void ID3v1::Tag::setTrack(TagLib::uint i) { d->track = i < 256 ? i : 0; } +TagLib::uint ID3v1::Tag::genreNumber() const +{ + return d->genre; +} + +void ID3v1::Tag::setGenreNumber(TagLib::uint i) +{ + d->genre = i < 256 ? i : 255; +} + void ID3v1::Tag::setStringHandler(const StringHandler *handler) { - if(TagPrivate::stringHandler != &defaultStringHandler) - delete TagPrivate::stringHandler; - - TagPrivate::stringHandler = handler; + if (handler) + TagPrivate::stringHandler = handler; + else + TagPrivate::stringHandler = &defaultStringHandler; } //////////////////////////////////////////////////////////////////////////////// diff --git a/taglib/mpeg/id3v1/id3v1tag.h b/taglib/mpeg/id3v1/id3v1tag.h index a90ac531..7b26d023 100644 --- a/taglib/mpeg/id3v1/id3v1tag.h +++ b/taglib/mpeg/id3v1/id3v1tag.h @@ -140,20 +140,40 @@ namespace TagLib { virtual String album() const; virtual String comment() const; virtual String genre() const; - virtual uint year() const; - virtual uint track() const; + virtual TagLib::uint year() const; + virtual TagLib::uint track() const; virtual void setTitle(const String &s); virtual void setArtist(const String &s); virtual void setAlbum(const String &s); virtual void setComment(const String &s); virtual void setGenre(const String &s); - virtual void setYear(uint i); - virtual void setTrack(uint i); + virtual void setYear(TagLib::uint i); + virtual void setTrack(TagLib::uint i); + + /*! + * Returns the genre in number. + * + * /note Normally 255 indicates that this tag contains no genre. + */ + TagLib::uint genreNumber() const; + + /*! + * Sets the genre in number to \a i. + * + * /note Valid value is from 0 up to 255. Normally 255 indicates that + * this tag contains no genre. + */ + void setGenreNumber(TagLib::uint i); /*! * Sets the string handler that decides how the ID3v1 data will be * converted to and from binary data. + * If the parameter \a handler is null, the previous handler is + * released and default ISO-8859-1 handler is restored. + * + * \note The caller is responsible for deleting the previous handler + * as needed after it is released. * * \see StringHandler */ diff --git a/taglib/mpeg/id3v2/frames/commentsframe.cpp b/taglib/mpeg/id3v2/frames/commentsframe.cpp index 18195531..deaa9d9a 100644 --- a/taglib/mpeg/id3v2/frames/commentsframe.cpp +++ b/taglib/mpeg/id3v2/frames/commentsframe.cpp @@ -158,8 +158,13 @@ void CommentsFrame::parseFields(const ByteVector &data) ByteVectorList l = ByteVectorList::split(data.mid(4), textDelimiter(d->textEncoding), byteAlign, 2); if(l.size() == 2) { - d->description = String(l.front(), d->textEncoding); - d->text = String(l.back(), d->textEncoding); + if(d->textEncoding == String::Latin1) { + d->description = Tag::latin1StringHandler()->parse(l.front()); + d->text = Tag::latin1StringHandler()->parse(l.back()); + } else { + d->description = String(l.front(), d->textEncoding); + d->text = String(l.back(), d->textEncoding); + } } } diff --git a/taglib/mpeg/id3v2/frames/ownershipframe.cpp b/taglib/mpeg/id3v2/frames/ownershipframe.cpp new file mode 100644 index 00000000..9451c4c4 --- /dev/null +++ b/taglib/mpeg/id3v2/frames/ownershipframe.cpp @@ -0,0 +1,162 @@ +/*************************************************************************** + copyright : (C) 2012 by Rupert Daniel + email : rupert@cancelmonday.com + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include + +#include "ownershipframe.h" +#include + +using namespace TagLib; +using namespace ID3v2; + +class OwnershipFrame::OwnershipFramePrivate +{ +public: + String pricePaid; + String datePurchased; + String seller; + String::Type textEncoding; +}; + +//////////////////////////////////////////////////////////////////////////////// +// public members +//////////////////////////////////////////////////////////////////////////////// + +OwnershipFrame::OwnershipFrame(String::Type encoding) : Frame("OWNE") +{ + d = new OwnershipFramePrivate; + d->textEncoding = encoding; +} + +OwnershipFrame::OwnershipFrame(const ByteVector &data) : Frame(data) +{ + d = new OwnershipFramePrivate; + setData(data); +} + +OwnershipFrame::~OwnershipFrame() +{ + delete d; +} + +String OwnershipFrame::toString() const +{ + return "pricePaid=" + d->pricePaid + " datePurchased=" + d->datePurchased + " seller=" + d->seller; +} + +String OwnershipFrame::pricePaid() const +{ + return d->pricePaid; +} + +void OwnershipFrame::setPricePaid(const String &s) +{ + d->pricePaid = s; +} + +String OwnershipFrame::datePurchased() const +{ + return d->datePurchased; +} + +void OwnershipFrame::setDatePurchased(const String &s) +{ + d->datePurchased = s; +} + +String OwnershipFrame::seller() const +{ + return d->seller; +} + +void OwnershipFrame::setSeller(const String &s) +{ + d->seller = s; +} + +String::Type OwnershipFrame::textEncoding() const +{ + return d->textEncoding; +} + +void OwnershipFrame::setTextEncoding(String::Type encoding) +{ + d->textEncoding = encoding; +} + +//////////////////////////////////////////////////////////////////////////////// +// protected members +//////////////////////////////////////////////////////////////////////////////// + +void OwnershipFrame::parseFields(const ByteVector &data) +{ + int pos = 0; + + // Get the text encoding + d->textEncoding = String::Type(data[0]); + pos += 1; + + // Read the price paid this is a null terminate string + d->pricePaid = readStringField(data, String::Latin1, &pos); + + // If we don't have at least 8 bytes left then don't parse the rest of the + // data + if(data.size() - pos < 8) { + return; + } + + // Read the date purchased YYYYMMDD + d->datePurchased = String(data.mid(pos, 8)); + pos += 8; + + // Read the seller + if(d->textEncoding == String::Latin1) + d->seller = Tag::latin1StringHandler()->parse(data.mid(pos)); + else + d->seller = String(data.mid(pos), d->textEncoding); +} + +ByteVector OwnershipFrame::renderFields() const +{ + ByteVector v; + + v.append(char(d->textEncoding)); + v.append(d->pricePaid.data(String::Latin1)); + v.append(textDelimiter(String::Latin1)); + v.append(d->datePurchased.data(String::Latin1)); + v.append(d->seller.data(d->textEncoding)); + + return v; +} + +//////////////////////////////////////////////////////////////////////////////// +// private members +//////////////////////////////////////////////////////////////////////////////// + +OwnershipFrame::OwnershipFrame(const ByteVector &data, Header *h) : Frame(h) +{ + d = new OwnershipFramePrivate; + parseFields(fieldData(data)); +} diff --git a/taglib/mpeg/id3v2/frames/ownershipframe.h b/taglib/mpeg/id3v2/frames/ownershipframe.h new file mode 100644 index 00000000..34fc9129 --- /dev/null +++ b/taglib/mpeg/id3v2/frames/ownershipframe.h @@ -0,0 +1,151 @@ +/*************************************************************************** + copyright : (C) 2012 by Rupert Daniel + email : rupert@cancelmonday.com + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_OWNERSHIPFRAME_H +#define TAGLIB_OWNERSHIPFRAME_H + +#include "id3v2frame.h" +#include "taglib_export.h" + +namespace TagLib { + + namespace ID3v2 { + + //! An implementation of ID3v2 "ownership" + + /*! + * This implements the ID3v2 ownership (OWNE frame). It consists of + * a price paid, a date purchased (YYYYMMDD) and the name of the seller. + */ + + class TAGLIB_EXPORT OwnershipFrame : public Frame + { + friend class FrameFactory; + + public: + /*! + * Construct an empty ownership frame. + */ + explicit OwnershipFrame(String::Type encoding = String::Latin1); + + /*! + * Construct a ownership based on the data in \a data. + */ + explicit OwnershipFrame(const ByteVector &data); + + /*! + * Destroys this OwnershipFrame instance. + */ + virtual ~OwnershipFrame(); + + /*! + * Returns the text of this popularimeter. + * + * \see text() + */ + virtual String toString() const; + + /*! + * Returns the date purchased. + * + * \see setDatePurchased() + */ + String datePurchased() const; + + /*! + * Set the date purchased. + * + * \see datePurchased() + */ + void setDatePurchased(const String &datePurchased); + + /*! + * Returns the price paid. + * + * \see setPricePaid() + */ + String pricePaid() const; + + /*! + * Set the price paid. + * + * \see pricePaid() + */ + void setPricePaid(const String &pricePaid); + + /*! + * Returns the seller. + * + * \see setSeller() + */ + String seller() const; + + /*! + * Set the seller. + * + * \see seller() + */ + void setSeller(const String &seller); + + /*! + * Returns the text encoding that will be used in rendering this frame. + * This defaults to the type that was either specified in the constructor + * or read from the frame when parsed. + * + * \see setTextEncoding() + * \see render() + */ + String::Type textEncoding() const; + + /*! + * Sets the text encoding to be used when rendering this frame to + * \a encoding. + * + * \see textEncoding() + * \see render() + */ + void setTextEncoding(String::Type encoding); + + protected: + // Reimplementations. + + virtual void parseFields(const ByteVector &data); + virtual ByteVector renderFields() const; + + private: + /*! + * The constructor used by the FrameFactory. + */ + OwnershipFrame(const ByteVector &data, Header *h); + OwnershipFrame(const OwnershipFrame &); + OwnershipFrame &operator=(const OwnershipFrame &); + + class OwnershipFramePrivate; + OwnershipFramePrivate *d; + }; + + } +} +#endif diff --git a/taglib/mpeg/id3v2/frames/textidentificationframe.cpp b/taglib/mpeg/id3v2/frames/textidentificationframe.cpp index b01f5129..70ea50f8 100644 --- a/taglib/mpeg/id3v2/frames/textidentificationframe.cpp +++ b/taglib/mpeg/id3v2/frames/textidentificationframe.cpp @@ -213,8 +213,10 @@ void TextIdentificationFrame::parseFields(const ByteVector &data) for(ByteVectorList::Iterator it = l.begin(); it != l.end(); it++) { if(!(*it).isEmpty()) { - String s(*it, d->textEncoding); - d->fieldList.append(s); + if(d->textEncoding == String::Latin1) + d->fieldList.append(Tag::latin1StringHandler()->parse(*it)); + else + d->fieldList.append(String(*it, d->textEncoding)); } } } @@ -379,18 +381,12 @@ void UserTextIdentificationFrame::setDescription(const String &s) PropertyMap UserTextIdentificationFrame::asProperties() const { - String tagName = description(); - PropertyMap map; - String key = tagName.upper(); - if(key.isNull()) // this frame's description is not a valid PropertyMap key -> add to unsupported list - map.unsupportedData().append(L"TXXX/" + description()); - else { - StringList v = fieldList(); - for(StringList::ConstIterator it = v.begin(); it != v.end(); ++it) - if(*it != description()) - map.insert(key, *it); - } + String tagName = txxxToKey(description()); + StringList v = fieldList(); + for(StringList::ConstIterator it = v.begin(); it != v.end(); ++it) + if(it != v.begin()) + map.insert(tagName, *it); return map; } diff --git a/taglib/mpeg/id3v2/frames/uniquefileidentifierframe.cpp b/taglib/mpeg/id3v2/frames/uniquefileidentifierframe.cpp index e12583ad..a0e842e0 100644 --- a/taglib/mpeg/id3v2/frames/uniquefileidentifierframe.cpp +++ b/taglib/mpeg/id3v2/frames/uniquefileidentifierframe.cpp @@ -24,8 +24,10 @@ ***************************************************************************/ #include +#include #include +#include "id3v2tag.h" #include "uniquefileidentifierframe.h" using namespace TagLib; @@ -87,6 +89,34 @@ String UniqueFileIdentifierFrame::toString() const return String::null; } +PropertyMap UniqueFileIdentifierFrame::asProperties() const +{ + PropertyMap map; + if(d->owner == "http://musicbrainz.org") { + map.insert("MUSICBRAINZ_TRACKID", String(d->identifier)); + } + else { + map.unsupportedData().append(frameID() + String("/") + d->owner); + } + return map; +} + +UniqueFileIdentifierFrame *UniqueFileIdentifierFrame::findByOwner(const ID3v2::Tag *tag, const String &o) // static +{ + ID3v2::FrameList comments = tag->frameList("UFID"); + + for(ID3v2::FrameList::ConstIterator it = comments.begin(); + it != comments.end(); + ++it) + { + UniqueFileIdentifierFrame *frame = dynamic_cast(*it); + if(frame && frame->owner() == o) + return frame; + } + + return 0; +} + void UniqueFileIdentifierFrame::parseFields(const ByteVector &data) { if(data.size() < 1) { diff --git a/taglib/mpeg/id3v2/frames/uniquefileidentifierframe.h b/taglib/mpeg/id3v2/frames/uniquefileidentifierframe.h index 0cf4b8f6..add5a2e0 100644 --- a/taglib/mpeg/id3v2/frames/uniquefileidentifierframe.h +++ b/taglib/mpeg/id3v2/frames/uniquefileidentifierframe.h @@ -94,6 +94,16 @@ namespace TagLib { virtual String toString() const; + PropertyMap asProperties() const; + + /*! + * UFID frames each have a unique owner. This searches for a UFID + * frame with the owner \a o and returns a pointer to it. + * + * \see owner() + */ + static UniqueFileIdentifierFrame *findByOwner(const Tag *tag, const String &o); + protected: virtual void parseFields(const ByteVector &data); virtual ByteVector renderFields() const; diff --git a/taglib/mpeg/id3v2/frames/unsynchronizedlyricsframe.cpp b/taglib/mpeg/id3v2/frames/unsynchronizedlyricsframe.cpp index 3f4d6a79..8f96cb8d 100644 --- a/taglib/mpeg/id3v2/frames/unsynchronizedlyricsframe.cpp +++ b/taglib/mpeg/id3v2/frames/unsynchronizedlyricsframe.cpp @@ -158,8 +158,13 @@ void UnsynchronizedLyricsFrame::parseFields(const ByteVector &data) ByteVectorList::split(data.mid(4), textDelimiter(d->textEncoding), byteAlign, 2); if(l.size() == 2) { - d->description = String(l.front(), d->textEncoding); - d->text = String(l.back(), d->textEncoding); + if(d->textEncoding == String::Latin1) { + d->description = Tag::latin1StringHandler()->parse(l.front()); + d->text = Tag::latin1StringHandler()->parse(l.back()); + } else { + d->description = String(l.front(), d->textEncoding); + d->text = String(l.back(), d->textEncoding); + } } } diff --git a/taglib/mpeg/id3v2/id3v2frame.cpp b/taglib/mpeg/id3v2/id3v2frame.cpp index 3a5463f5..e2b84311 100644 --- a/taglib/mpeg/id3v2/id3v2frame.cpp +++ b/taglib/mpeg/id3v2/id3v2frame.cpp @@ -36,6 +36,7 @@ #include #include +#include "id3v2tag.h" #include "id3v2frame.h" #include "id3v2synchdata.h" #include "tpropertymap.h" @@ -43,6 +44,7 @@ #include "frames/urllinkframe.h" #include "frames/unsynchronizedlyricsframe.h" #include "frames/commentsframe.h" +#include "frames/uniquefileidentifierframe.h" #include "frames/unknownframe.h" using namespace TagLib; @@ -71,7 +73,7 @@ namespace return false; for(ByteVector::ConstIterator it = frameID.begin(); it != frameID.end(); it++) { - if( (*it < 'A' || *it > 'Z') && (*it < '1' || *it > '9') ) { + if( (*it < 'A' || *it > 'Z') && (*it < '0' || *it > '9') ) { return false; } } @@ -119,12 +121,16 @@ Frame *Frame::createTextualFrame(const String &key, const StringList &values) // TextIdentificationFrame *frame = new TextIdentificationFrame(frameID, String::UTF8); frame->setText(values); return frame; - } else if(values.size() == 1){ // URL frame (not WXXX); support only one value + } else if((frameID[0] == 'W') && (values.size() == 1)){ // URL frame (not WXXX); support only one value UrlLinkFrame* frame = new UrlLinkFrame(frameID); frame->setUrl(values.front()); return frame; } } + if(key == "MUSICBRAINZ_TRACKID" && values.size() == 1) { + UniqueFileIdentifierFrame *frame = new UniqueFileIdentifierFrame("http://musicbrainz.org", values.front().data(String::UTF8)); + return frame; + } // now we check if it's one of the "special" cases: // -LYRICS: depending on the number of values, use USLT or TXXX (with description=LYRICS) if((key == "LYRICS" || key.startsWith(lyricsPrefix)) && values.size() == 1){ @@ -143,12 +149,14 @@ Frame *Frame::createTextualFrame(const String &key, const StringList &values) // // -COMMENT: depending on the number of values, use COMM or TXXX (with description=COMMENT) if((key == "COMMENT" || key.startsWith(commentPrefix)) && values.size() == 1){ CommentsFrame *frame = new CommentsFrame(String::UTF8); - frame->setDescription(key == "COMMENT" ? key : key.substr(commentPrefix.size())); + if (key != "COMMENT"){ + frame->setDescription(key.substr(commentPrefix.size())); + } frame->setText(values.front()); return frame; } // if non of the above cases apply, we use a TXXX frame with the key as description - return new UserTextIdentificationFrame(key, values, String::UTF8); + return new UserTextIdentificationFrame(keyToTXXX(key), values, String::UTF8); } Frame::~Frame() @@ -273,7 +281,11 @@ String Frame::readStringField(const ByteVector &data, String::Type encoding, int if(end < *position) return String::null; - String str = String(data.mid(*position, end - *position), encoding); + String str; + if(encoding == String::Latin1) + str = Tag::latin1StringHandler()->parse(data.mid(*position, end - *position)); + else + str = String(data.mid(*position, end - *position), encoding); *position = end + delimiter.size(); @@ -343,7 +355,7 @@ static const char *frameTranslation[][2] = { { "TLAN", "LANGUAGE" }, { "TLEN", "LENGTH" }, //{ "TMCL", "MUSICIANCREDITS" }, handled separately - { "TMED", "MEDIATYPE" }, + { "TMED", "MEDIA" }, { "TMOO", "MOOD" }, { "TOAL", "ORIGINALALBUM" }, { "TOFN", "ORIGINALFILENAME" }, @@ -356,7 +368,7 @@ static const char *frameTranslation[][2] = { { "TPE4", "REMIXER" }, // could also be ARRANGER { "TPOS", "DISCNUMBER" }, { "TPRO", "PRODUCEDNOTICE" }, - { "TPUB", "PUBLISHER" }, + { "TPUB", "LABEL" }, { "TRCK", "TRACKNUMBER" }, { "TRSN", "RADIOSTATION" }, { "TRSO", "RADIOSTATIONOWNER" }, @@ -380,6 +392,18 @@ static const char *frameTranslation[][2] = { //{ "USLT", "LYRICS" }, handled specially }; +static const TagLib::uint txxxFrameTranslationSize = 7; +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; @@ -389,6 +413,18 @@ Map &idMap() 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] = { @@ -428,6 +464,26 @@ ByteVector Frame::keyToFrameID(const String &s) return ByteVector::null; } +String Frame::txxxToKey(const String &description) +{ + Map &m = txxxMap(); + String d = description.upper(); + if(m.contains(d)) + return m[d]; + 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]; + return s; +} + PropertyMap Frame::asProperties() const { if(dynamic_cast< const UnknownFrame *>(this)) { @@ -449,6 +505,8 @@ PropertyMap Frame::asProperties() const return dynamic_cast< const CommentsFrame* >(this)->asProperties(); else if(id == "USLT") return dynamic_cast< const UnsynchronizedLyricsFrame* >(this)->asProperties(); + else if(id == "UFID") + return dynamic_cast< const UniqueFileIdentifierFrame* >(this)->asProperties(); PropertyMap m; m.unsupportedData().append(id); return m; diff --git a/taglib/mpeg/id3v2/id3v2frame.h b/taglib/mpeg/id3v2/id3v2frame.h index 95c4070b..3e257d4f 100644 --- a/taglib/mpeg/id3v2/id3v2frame.h +++ b/taglib/mpeg/id3v2/id3v2frame.h @@ -274,6 +274,15 @@ namespace TagLib { */ static String frameIDToKey(const ByteVector &); + /*! + * Returns an appropriate TXXX frame description for the given free-form tag key. + */ + static String keyToTXXX(const String &); + + /*! + * Returns a free-form tag name for the given ID3 frame description. + */ + static String txxxToKey(const String &); /*! * This helper function splits the PropertyMap \a original into three ProperytMaps diff --git a/taglib/mpeg/id3v2/id3v2framefactory.cpp b/taglib/mpeg/id3v2/id3v2framefactory.cpp index 38f46c56..c7d74214 100644 --- a/taglib/mpeg/id3v2/id3v2framefactory.cpp +++ b/taglib/mpeg/id3v2/id3v2framefactory.cpp @@ -44,6 +44,7 @@ #include "frames/unsynchronizedlyricsframe.h" #include "frames/popularimeterframe.h" #include "frames/privateframe.h" +#include "frames/ownershipframe.h" using namespace TagLib; using namespace ID3v2; @@ -98,7 +99,7 @@ Frame *FrameFactory::createFrame(const ByteVector &origData, Header *tagHeader) // A quick sanity check -- make sure that the frameID is 4 uppercase Latin1 // characters. Also make sure that there is data in the frame. - if(!frameID.size() == (version < 3 ? 3 : 4) || + if(frameID.size() != (version < 3 ? 3 : 4) || header->frameSize() <= uint(header->dataLengthIndicator() ? 4 : 0) || header->frameSize() > data.size()) { @@ -106,8 +107,19 @@ Frame *FrameFactory::createFrame(const ByteVector &origData, Header *tagHeader) return 0; } +#ifndef NO_ITUNES_HACKS + if(version == 3 && frameID.size() == 4 && frameID[3] == '\0') { + // iTunes v2.3 tags store v2.2 frames - convert now + frameID = frameID.mid(0, 3); + header->setFrameID(frameID); + header->setVersion(2); + updateFrame(header); + header->setVersion(3); + } +#endif + for(ByteVector::ConstIterator it = frameID.begin(); it != frameID.end(); it++) { - if( (*it < 'A' || *it > 'Z') && (*it < '1' || *it > '9') ) { + if( (*it < 'A' || *it > 'Z') && (*it < '0' || *it > '9') ) { delete header; return 0; } @@ -238,6 +250,14 @@ Frame *FrameFactory::createFrame(const ByteVector &origData, Header *tagHeader) if(frameID == "PRIV") return new PrivateFrame(data, header); + + // Ownership (frames 4.22) + + if(frameID == "OWNE") { + OwnershipFrame *f = new OwnershipFrame(data, header); + d->setTextEncoding(f); + return f; + } return new UnknownFrame(data, header); } diff --git a/taglib/mpeg/id3v2/id3v2framefactory.h b/taglib/mpeg/id3v2/id3v2framefactory.h index 16419c76..762d7eb3 100644 --- a/taglib/mpeg/id3v2/id3v2framefactory.h +++ b/taglib/mpeg/id3v2/id3v2framefactory.h @@ -123,8 +123,7 @@ namespace TagLib { FrameFactory(); /*! - * Destroys the frame factory. In most cases this will never be called (as - * is typical of singletons). + * Destroys the frame factory. */ virtual ~FrameFactory(); diff --git a/taglib/mpeg/id3v2/id3v2tag.cpp b/taglib/mpeg/id3v2/id3v2tag.cpp index 662fd159..a1b169e1 100644 --- a/taglib/mpeg/id3v2/id3v2tag.cpp +++ b/taglib/mpeg/id3v2/id3v2tag.cpp @@ -70,8 +70,30 @@ public: FrameListMap frameListMap; FrameList frameList; + + static const Latin1StringHandler *stringHandler; }; +static const Latin1StringHandler defaultStringHandler; +const ID3v2::Latin1StringHandler *ID3v2::Tag::TagPrivate::stringHandler = &defaultStringHandler; + +//////////////////////////////////////////////////////////////////////////////// +// StringHandler implementation +//////////////////////////////////////////////////////////////////////////////// + +Latin1StringHandler::Latin1StringHandler() +{ +} + +Latin1StringHandler::~Latin1StringHandler() +{ +} + +String Latin1StringHandler::parse(const ByteVector &data) const +{ + return String(data, String::Latin1); +} + //////////////////////////////////////////////////////////////////////////////// // public members //////////////////////////////////////////////////////////////////////////////// @@ -357,10 +379,12 @@ void ID3v2::Tag::removeUnsupportedProperties(const StringList &properties) for(FrameList::ConstIterator fit = l.begin(); fit != l.end(); fit++) if (dynamic_cast(*fit) != 0) removeFrame(*fit); - } else if(it->size() == 4){ + } + else if(it->size() == 4){ ByteVector id = it->data(String::Latin1); removeFrames(id); - } else { + } + else { ByteVector id = it->substr(0,4).data(String::Latin1); if(it->size() <= 5) continue; // invalid specification @@ -374,6 +398,8 @@ void ID3v2::Tag::removeUnsupportedProperties(const StringList &properties) frame = CommentsFrame::findByDescription(this, description); else if(id == "USLT") frame = UnsynchronizedLyricsFrame::findByDescription(this, description); + else if(id == "UFID") + frame = UniqueFileIdentifierFrame::findByOwner(this, description); if(frame) removeFrame(frame); } @@ -584,6 +610,19 @@ ByteVector ID3v2::Tag::render(int version) const return d->header.render() + tagData; } +Latin1StringHandler const *ID3v2::Tag::latin1StringHandler() +{ + return TagPrivate::stringHandler; +} + +void ID3v2::Tag::setLatin1StringHandler(const Latin1StringHandler *handler) +{ + if(handler) + TagPrivate::stringHandler = handler; + else + TagPrivate::stringHandler = &defaultStringHandler; +} + //////////////////////////////////////////////////////////////////////////////// // protected members //////////////////////////////////////////////////////////////////////////////// @@ -645,8 +684,9 @@ void ID3v2::Tag::parse(const ByteVector &origData) // portion of the frame data. if(data.at(frameDataPosition) == 0) { - if(d->header.footerPresent()) + if(d->header.footerPresent()) { debug("Padding *and* a footer found. This is not allowed by the spec."); + } d->paddingSize = frameDataLength - frameDataPosition; return; diff --git a/taglib/mpeg/id3v2/id3v2tag.h b/taglib/mpeg/id3v2/id3v2tag.h index 94784e76..5fd5c1f1 100644 --- a/taglib/mpeg/id3v2/id3v2tag.h +++ b/taglib/mpeg/id3v2/id3v2tag.h @@ -57,6 +57,36 @@ namespace TagLib { typedef List FrameList; typedef Map FrameListMap; + //! An abstraction for the ISO-8859-1 string to data encoding in ID3v2 tags. + + /*! + * ID3v2 tag can store strings in ISO-8859-1 (Latin1), and TagLib only + * supports genuine ISO-8859-1 by default. However, in practice, non + * ISO-8859-1 encodings are often used instead of ISO-8859-1, such as + * Windows-1252 for western languages, Shift_JIS for Japanese and so on. + * + * Here is an option to read such tags by subclassing this class, + * reimplementing parse() and setting your reimplementation as the default + * with ID3v2::Tag::setStringHandler(). + * + * \note Writing non-ISO-8859-1 tags is not implemented intentionally. + * Use UTF-16 or UTF-8 instead. + * + * \see ID3v2::Tag::setStringHandler() + */ + class TAGLIB_EXPORT Latin1StringHandler + { + public: + Latin1StringHandler(); + virtual ~Latin1StringHandler(); + + /*! + * Decode a string from \a data. The default implementation assumes that + * \a data is an ISO-8859-1 (Latin1) character array. + */ + virtual String parse(const ByteVector &data) const; + }; + //! The main class in the ID3v2 implementation /*! @@ -323,6 +353,27 @@ namespace TagLib { */ // BIC: combine with the above method ByteVector render(int version) const; + + /*! + * Gets the current string handler that decides how the "Latin-1" data + * will be converted to and from binary data. + * + * \see Latin1StringHandler + */ + static Latin1StringHandler const *latin1StringHandler(); + + /*! + * Sets the string handler that decides how the "Latin-1" data will be + * converted to and from binary data. + * If the parameter \a handler is null, the previous handler is + * released and default ISO-8859-1 handler is restored. + * + * \note The caller is responsible for deleting the previous handler + * as needed after it is released. + * + * \see Latin1StringHandler + */ + static void setLatin1StringHandler(const Latin1StringHandler *handler); protected: /*! diff --git a/taglib/mpeg/mpegfile.cpp b/taglib/mpeg/mpegfile.cpp index 6ebff897..ec094cdc 100644 --- a/taglib/mpeg/mpegfile.cpp +++ b/taglib/mpeg/mpegfile.cpp @@ -437,6 +437,21 @@ long MPEG::File::lastFrameOffset() return previousFrameOffset(ID3v1Tag() ? d->ID3v1Location - 1 : length()); } +bool MPEG::File::hasID3v1Tag() const +{ + return d->hasID3v1; +} + +bool MPEG::File::hasID3v2Tag() const +{ + return d->hasID3v2; +} + +bool MPEG::File::hasAPETag() const +{ + return d->hasAPE; +} + //////////////////////////////////////////////////////////////////////////////// // private members //////////////////////////////////////////////////////////////////////////////// diff --git a/taglib/mpeg/mpegfile.h b/taglib/mpeg/mpegfile.h index 185fced6..f7b98364 100644 --- a/taglib/mpeg/mpegfile.h +++ b/taglib/mpeg/mpegfile.h @@ -99,6 +99,9 @@ namespace TagLib { * file's audio properties will also be read using \a propertiesStyle. If * false, \a propertiesStyle is ignored. The frames will be created using * \a frameFactory. + * + * \note TagLib will *not* take ownership of the stream, the caller is + * responsible for deleting it after the File object. */ // BIC: merge with the above constructor File(IOStream *stream, ID3v2::FrameFactory *frameFactory, @@ -298,6 +301,21 @@ namespace TagLib { */ long lastFrameOffset(); + /*! + * Returns whether or not the file on disk contains ID3v1 tag. + */ + bool hasID3v1Tag() const; + + /*! + * Returns whether or not the file on disk contains ID3v2 tag. + */ + bool hasID3v2Tag() const; + + /*! + * Returns whether or not the file on disk contains APE tag. + */ + bool hasAPETag() const; + private: File(const File &); File &operator=(const File &); diff --git a/taglib/mpeg/mpegproperties.cpp b/taglib/mpeg/mpegproperties.cpp index 028ee061..3af95664 100644 --- a/taglib/mpeg/mpegproperties.cpp +++ b/taglib/mpeg/mpegproperties.cpp @@ -221,7 +221,7 @@ void MPEG::Properties::read() double length = timePerFrame * d->xingHeader->totalFrames(); d->length = int(length); - d->bitrate = d->length > 0 ? d->xingHeader->totalSize() * 8 / length / 1000 : 0; + d->bitrate = d->length > 0 ? (int)(d->xingHeader->totalSize() * 8 / length / 1000) : 0; } else { // Since there was no valid Xing header found, we hope that we're in a constant diff --git a/taglib/ogg/flac/oggflacfile.cpp b/taglib/ogg/flac/oggflacfile.cpp index 3addbffa..b73c5f57 100644 --- a/taglib/ogg/flac/oggflacfile.cpp +++ b/taglib/ogg/flac/oggflacfile.cpp @@ -72,14 +72,16 @@ Ogg::FLAC::File::File(FileName file, bool readProperties, Properties::ReadStyle propertiesStyle) : Ogg::File(file) { d = new FilePrivate; - read(readProperties, propertiesStyle); + if(isOpen()) + read(readProperties, propertiesStyle); } Ogg::FLAC::File::File(IOStream *stream, bool readProperties, Properties::ReadStyle propertiesStyle) : Ogg::File(stream) { d = new FilePrivate; - read(readProperties, propertiesStyle); + if(isOpen()) + read(readProperties, propertiesStyle); } Ogg::FLAC::File::~File() @@ -263,9 +265,9 @@ void Ogg::FLAC::File::scan() d->hasXiphComment = true; d->commentPacket = ipacket; } - else if(blockType > 5) + else if(blockType > 5) { debug("Ogg::FLAC::File::scan() -- Unknown metadata block"); - + } } // End of metadata, now comes the datastream diff --git a/taglib/ogg/flac/oggflacfile.h b/taglib/ogg/flac/oggflacfile.h index d4373795..8558cfdf 100644 --- a/taglib/ogg/flac/oggflacfile.h +++ b/taglib/ogg/flac/oggflacfile.h @@ -75,6 +75,9 @@ namespace TagLib { * Contructs an Ogg/FLAC file from \a file. If \a readProperties is true * the file's audio properties will also be read using \a propertiesStyle. * If false, \a propertiesStyle is ignored. + * + * \note TagLib will *not* take ownership of the stream, the caller is + * responsible for deleting it after the File object. */ File(IOStream *stream, bool readProperties = true, Properties::ReadStyle propertiesStyle = Properties::Average); diff --git a/taglib/ogg/oggfile.h b/taglib/ogg/oggfile.h index 1ecf2b9b..b36daecb 100644 --- a/taglib/ogg/oggfile.h +++ b/taglib/ogg/oggfile.h @@ -100,6 +100,9 @@ namespace TagLib { * \note This constructor is protected since Ogg::File shouldn't be * instantiated directly but rather should be used through the codec * specific subclasses. + * + * \note TagLib will *not* take ownership of the stream, the caller is + * responsible for deleting it after the File object. */ File(IOStream *stream); diff --git a/taglib/ogg/opus/opusfile.cpp b/taglib/ogg/opus/opusfile.cpp new file mode 100644 index 00000000..ecbc218d --- /dev/null +++ b/taglib/ogg/opus/opusfile.cpp @@ -0,0 +1,126 @@ +/*************************************************************************** + copyright : (C) 2012 by Lukáš Lalinský + email : lalinsky@gmail.com + + copyright : (C) 2002 - 2008 by Scott Wheeler + email : wheeler@kde.org + (original Vorbis implementation) + ***************************************************************************/ + +/*************************************************************************** + * 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 + +#include +#include + +#include "opusfile.h" + +using namespace TagLib; +using namespace TagLib::Ogg; + +class Opus::File::FilePrivate +{ +public: + FilePrivate() : + comment(0), + properties(0) {} + + ~FilePrivate() + { + delete comment; + delete properties; + } + + Ogg::XiphComment *comment; + Properties *properties; +}; + +//////////////////////////////////////////////////////////////////////////////// +// public members +//////////////////////////////////////////////////////////////////////////////// + +Opus::File::File(FileName file, bool readProperties, + Properties::ReadStyle propertiesStyle) : Ogg::File(file) +{ + d = new FilePrivate; + read(readProperties, propertiesStyle); +} + +Opus::File::File(IOStream *stream, bool readProperties, + Properties::ReadStyle propertiesStyle) : Ogg::File(stream) +{ + d = new FilePrivate; + read(readProperties, propertiesStyle); +} + +Opus::File::~File() +{ + delete d; +} + +Ogg::XiphComment *Opus::File::tag() const +{ + return d->comment; +} + +Opus::Properties *Opus::File::audioProperties() const +{ + return d->properties; +} + +bool Opus::File::save() +{ + if(!d->comment) + d->comment = new Ogg::XiphComment; + + setPacket(1, ByteVector("OpusTags", 8) + d->comment->render(false)); + + return Ogg::File::save(); +} + +//////////////////////////////////////////////////////////////////////////////// +// private members +//////////////////////////////////////////////////////////////////////////////// + +void Opus::File::read(bool readProperties, Properties::ReadStyle propertiesStyle) +{ + ByteVector opusHeaderData = packet(0); + + if(!opusHeaderData.startsWith("OpusHead")) { + setValid(false); + debug("Opus::File::read() -- invalid Opus identification header"); + return; + } + + ByteVector commentHeaderData = packet(1); + + if(!commentHeaderData.startsWith("OpusTags")) { + setValid(false); + debug("Opus::File::read() -- invalid Opus tags header"); + return; + } + + d->comment = new Ogg::XiphComment(commentHeaderData.mid(8)); + + if(readProperties) + d->properties = new Properties(this, propertiesStyle); +} diff --git a/taglib/ogg/opus/opusfile.h b/taglib/ogg/opus/opusfile.h new file mode 100644 index 00000000..73375af8 --- /dev/null +++ b/taglib/ogg/opus/opusfile.h @@ -0,0 +1,110 @@ +/*************************************************************************** + copyright : (C) 2012 by Lukáš Lalinský + email : lalinsky@gmail.com + + copyright : (C) 2002 - 2008 by Scott Wheeler + email : wheeler@kde.org + (original Vorbis implementation) +***************************************************************************/ + +/*************************************************************************** + * 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_OPUSFILE_H +#define TAGLIB_OPUSFILE_H + +#include "oggfile.h" +#include "xiphcomment.h" + +#include "opusproperties.h" + +namespace TagLib { + + namespace Ogg { + + //! A namespace containing classes for Opus metadata + + namespace Opus { + + //! An implementation of Ogg::File with Opus specific methods + + /*! + * This is the central class in the Ogg Opus metadata processing collection + * of classes. It's built upon Ogg::File which handles processing of the Ogg + * logical bitstream and breaking it down into pages which are handled by + * the codec implementations, in this case Opus specifically. + */ + + class TAGLIB_EXPORT File : public Ogg::File + { + public: + /*! + * Contructs a Opus file from \a file. If \a readProperties is true the + * file's audio properties will also be read using \a propertiesStyle. If + * false, \a propertiesStyle is ignored. + */ + File(FileName file, bool readProperties = true, + Properties::ReadStyle propertiesStyle = Properties::Average); + + /*! + * Contructs a Opus file from \a file. If \a readProperties is true the + * file's audio properties will also be read using \a propertiesStyle. If + * false, \a propertiesStyle is ignored. + * + * \note TagLib will *not* take ownership of the stream, the caller is + * responsible for deleting it after the File object. + */ + File(IOStream *stream, bool readProperties = true, + Properties::ReadStyle propertiesStyle = Properties::Average); + + /*! + * Destroys this instance of the File. + */ + virtual ~File(); + + /*! + * Returns the XiphComment for this file. XiphComment implements the tag + * interface, so this serves as the reimplementation of + * TagLib::File::tag(). + */ + virtual Ogg::XiphComment *tag() const; + + /*! + * Returns the Opus::Properties for this file. If no audio properties + * were read then this will return a null pointer. + */ + virtual Properties *audioProperties() const; + + virtual bool save(); + + private: + File(const File &); + File &operator=(const File &); + + void read(bool readProperties, Properties::ReadStyle propertiesStyle); + + class FilePrivate; + FilePrivate *d; + }; + } + } +} + +#endif diff --git a/taglib/ogg/opus/opusproperties.cpp b/taglib/ogg/opus/opusproperties.cpp new file mode 100644 index 00000000..70679d4c --- /dev/null +++ b/taglib/ogg/opus/opusproperties.cpp @@ -0,0 +1,161 @@ +/*************************************************************************** + copyright : (C) 2012 by Lukáš Lalinský + email : lalinsky@gmail.com + + copyright : (C) 2002 - 2008 by Scott Wheeler + email : wheeler@kde.org + (original Vorbis implementation) + ***************************************************************************/ + +/*************************************************************************** + * 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 +#include + +#include + +#include "opusproperties.h" +#include "opusfile.h" + +using namespace TagLib; +using namespace TagLib::Ogg; + +class Opus::Properties::PropertiesPrivate +{ +public: + PropertiesPrivate(File *f, ReadStyle s) : + file(f), + style(s), + length(0), + inputSampleRate(0), + channels(0), + opusVersion(0) {} + + File *file; + ReadStyle style; + int length; + int inputSampleRate; + int channels; + int opusVersion; +}; + +//////////////////////////////////////////////////////////////////////////////// +// public members +//////////////////////////////////////////////////////////////////////////////// + +Opus::Properties::Properties(File *file, ReadStyle style) : AudioProperties(style) +{ + d = new PropertiesPrivate(file, style); + read(); +} + +Opus::Properties::~Properties() +{ + delete d; +} + +int Opus::Properties::length() const +{ + return d->length; +} + +int Opus::Properties::bitrate() const +{ + return 0; +} + +int Opus::Properties::sampleRate() const +{ + // Opus can decode any stream at a sample rate of 8, 12, 16, 24, or 48 kHz, + // so there is no single sample rate. Let's assume it's the highest + // possible. + return 48000; +} + +int Opus::Properties::channels() const +{ + return d->channels; +} + +int Opus::Properties::inputSampleRate() const +{ + return d->inputSampleRate; +} + +int Opus::Properties::opusVersion() const +{ + return d->opusVersion; +} + +//////////////////////////////////////////////////////////////////////////////// +// private members +//////////////////////////////////////////////////////////////////////////////// + +void Opus::Properties::read() +{ + // Get the identification header from the Ogg implementation. + + // http://tools.ietf.org/html/draft-terriberry-oggopus-01#section-5.1 + + ByteVector data = d->file->packet(0); + + // *Magic Signature* + int pos = 8; + + // *Version* (8 bits, unsigned) + d->opusVersion = uchar(data.at(pos)); + pos += 1; + + // *Output Channel Count* 'C' (8 bits, unsigned) + d->channels = uchar(data.at(pos)); + pos += 1; + + // *Pre-skip* (16 bits, unsigned, little endian) + ushort preSkip = data.mid(pos, 2).toUShort(false); + pos += 2; + + // *Input Sample Rate* (32 bits, unsigned, little endian) + d->inputSampleRate = data.mid(pos, 4).toUInt(false); + pos += 4; + + // *Output Gain* (16 bits, signed, little endian) + pos += 2; + + // *Channel Mapping Family* (8 bits, unsigned) + pos += 1; + + const Ogg::PageHeader *first = d->file->firstPageHeader(); + const Ogg::PageHeader *last = d->file->lastPageHeader(); + + if(first && last) { + long long start = first->absoluteGranularPosition(); + long long end = last->absoluteGranularPosition(); + + if(start >= 0 && end >= 0) + d->length = (int) ((end - start - preSkip) / 48000); + else { + debug("Opus::Properties::read() -- The PCM values for the start or " + "end of this file was incorrect."); + } + } + else + debug("Opus::Properties::read() -- Could not find valid first and last Ogg pages."); +} diff --git a/taglib/ogg/opus/opusproperties.h b/taglib/ogg/opus/opusproperties.h new file mode 100644 index 00000000..946f1675 --- /dev/null +++ b/taglib/ogg/opus/opusproperties.h @@ -0,0 +1,96 @@ +/*************************************************************************** + copyright : (C) 2012 by Lukáš Lalinský + email : lalinsky@gmail.com + + copyright : (C) 2002 - 2008 by Scott Wheeler + email : wheeler@kde.org + (original Vorbis implementation) +***************************************************************************/ + +/*************************************************************************** + * 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_OPUSPROPERTIES_H +#define TAGLIB_OPUSPROPERTIES_H + +#include "audioproperties.h" + +namespace TagLib { + + namespace Ogg { + + namespace Opus { + + class File; + + //! An implementation of audio property reading for Ogg Opus + + /*! + * This reads the data from an Ogg Opus stream found in the AudioProperties + * API. + */ + + class TAGLIB_EXPORT Properties : public AudioProperties + { + public: + /*! + * Create an instance of Opus::Properties with the data read from the + * Opus::File \a file. + */ + Properties(File *file, ReadStyle style = Average); + + /*! + * Destroys this Opus::Properties instance. + */ + virtual ~Properties(); + + // Reimplementations. + + virtual int length() const; + virtual int bitrate() const; + virtual int sampleRate() const; + virtual int channels() const; + + /*! + * The Opus codec supports decoding at multiple sample rates, there is no + * single sample rate of the encoded stream. This returns the sample rate + * of the original audio stream. + */ + int inputSampleRate() const; + + /*! + * Returns the Opus version, currently "0" (as specified by the spec). + */ + int opusVersion() const; + + private: + Properties(const Properties &); + Properties &operator=(const Properties &); + + void read(); + + class PropertiesPrivate; + PropertiesPrivate *d; + }; + } + } +} + +#endif diff --git a/taglib/ogg/speex/speexfile.cpp b/taglib/ogg/speex/speexfile.cpp index 3a4940a2..8ac86e69 100644 --- a/taglib/ogg/speex/speexfile.cpp +++ b/taglib/ogg/speex/speexfile.cpp @@ -62,14 +62,16 @@ Speex::File::File(FileName file, bool readProperties, Properties::ReadStyle propertiesStyle) : Ogg::File(file) { d = new FilePrivate; - read(readProperties, propertiesStyle); + if(isOpen()) + read(readProperties, propertiesStyle); } Speex::File::File(IOStream *stream, bool readProperties, Properties::ReadStyle propertiesStyle) : Ogg::File(stream) { d = new FilePrivate; - read(readProperties, propertiesStyle); + if(isOpen()) + read(readProperties, propertiesStyle); } Speex::File::~File() diff --git a/taglib/ogg/speex/speexfile.h b/taglib/ogg/speex/speexfile.h index c14cf2aa..dfe51ec4 100644 --- a/taglib/ogg/speex/speexfile.h +++ b/taglib/ogg/speex/speexfile.h @@ -67,6 +67,9 @@ namespace TagLib { * Contructs a Speex file from \a file. If \a readProperties is true the * file's audio properties will also be read using \a propertiesStyle. If * false, \a propertiesStyle is ignored. + * + * \note TagLib will *not* take ownership of the stream, the caller is + * responsible for deleting it after the File object. */ File(IOStream *stream, bool readProperties = true, Properties::ReadStyle propertiesStyle = Properties::Average); diff --git a/taglib/ogg/vorbis/vorbisfile.cpp b/taglib/ogg/vorbis/vorbisfile.cpp index e2eed9e2..82984536 100644 --- a/taglib/ogg/vorbis/vorbisfile.cpp +++ b/taglib/ogg/vorbis/vorbisfile.cpp @@ -67,14 +67,16 @@ Vorbis::File::File(FileName file, bool readProperties, Properties::ReadStyle propertiesStyle) : Ogg::File(file) { d = new FilePrivate; - read(readProperties, propertiesStyle); + if(isOpen()) + read(readProperties, propertiesStyle); } Vorbis::File::File(IOStream *stream, bool readProperties, Properties::ReadStyle propertiesStyle) : Ogg::File(stream) { d = new FilePrivate; - read(readProperties, propertiesStyle); + if(isOpen()) + read(readProperties, propertiesStyle); } Vorbis::File::~File() diff --git a/taglib/ogg/vorbis/vorbisfile.h b/taglib/ogg/vorbis/vorbisfile.h index 15c29d99..6e4d4fc4 100644 --- a/taglib/ogg/vorbis/vorbisfile.h +++ b/taglib/ogg/vorbis/vorbisfile.h @@ -74,6 +74,9 @@ namespace TagLib { * Contructs a Vorbis file from \a file. If \a readProperties is true the * file's audio properties will also be read using \a propertiesStyle. If * false, \a propertiesStyle is ignored. + * + * \note TagLib will *not* take ownership of the stream, the caller is + * responsible for deleting it after the File object. */ File(IOStream *stream, bool readProperties = true, Properties::ReadStyle propertiesStyle = Properties::Average); diff --git a/taglib/ogg/vorbis/vorbisproperties.cpp b/taglib/ogg/vorbis/vorbisproperties.cpp index 6de6a599..c67e1677 100644 --- a/taglib/ogg/vorbis/vorbisproperties.cpp +++ b/taglib/ogg/vorbis/vorbisproperties.cpp @@ -173,7 +173,7 @@ void Vorbis::Properties::read() long long end = last->absoluteGranularPosition(); if(start >= 0 && end >= 0 && d->sampleRate > 0) - d->length = (end - start) / (long long) d->sampleRate; + d->length = (int)((end - start) / (long long) d->sampleRate); else debug("Vorbis::Properties::read() -- Either the PCM values for the start or " "end of this file was incorrect or the sample rate is zero."); diff --git a/taglib/riff/aiff/aifffile.h b/taglib/riff/aiff/aifffile.h index a50c8ecb..e1284db0 100644 --- a/taglib/riff/aiff/aifffile.h +++ b/taglib/riff/aiff/aifffile.h @@ -69,6 +69,9 @@ namespace TagLib { * Contructs an AIFF file from \a file. If \a readProperties is true the * file's audio properties will also be read using \a propertiesStyle. If * false, \a propertiesStyle is ignored. + * + * \note TagLib will *not* take ownership of the stream, the caller is + * responsible for deleting it after the File object. */ File(IOStream *stream, bool readProperties = true, Properties::ReadStyle propertiesStyle = Properties::Average); diff --git a/taglib/riff/aiff/aiffproperties.cpp b/taglib/riff/aiff/aiffproperties.cpp index c78f0cba..3ea27582 100644 --- a/taglib/riff/aiff/aiffproperties.cpp +++ b/taglib/riff/aiff/aiffproperties.cpp @@ -154,7 +154,7 @@ void RIFF::AIFF::Properties::read(const ByteVector &data) d->sampleFrames = data.mid(2, 4).toUInt(); d->sampleWidth = data.mid(6, 2).toShort(); double sampleRate = ConvertFromIeeeExtended(reinterpret_cast(data.mid(8, 10).data())); - d->sampleRate = sampleRate; - d->bitrate = (sampleRate * d->sampleWidth * d->channels) / 1000.0; + d->sampleRate = (int)sampleRate; + d->bitrate = (int)((sampleRate * d->sampleWidth * d->channels) / 1000.0); d->length = d->sampleRate > 0 ? d->sampleFrames / d->sampleRate : 0; } diff --git a/taglib/riff/rifffile.cpp b/taglib/riff/rifffile.cpp index df3b3663..a38e1c40 100644 --- a/taglib/riff/rifffile.cpp +++ b/taglib/riff/rifffile.cpp @@ -138,34 +138,44 @@ ByteVector RIFF::File::chunkData(uint i) return readBlock(d->chunks[i].size); } -void RIFF::File::setChunkData(const ByteVector &name, const ByteVector &data) +void RIFF::File::setChunkData(uint i, const ByteVector &data) +{ + // First we update the global size + + d->size += ((data.size() + 1) & ~1) - (d->chunks[i].size + d->chunks[i].padding); + insert(ByteVector::fromUInt(d->size, d->endianness == BigEndian), 4, 4); + + // Now update the specific chunk + + writeChunk(chunkName(i), data, d->chunks[i].offset - 8, d->chunks[i].size + d->chunks[i].padding + 8); + + d->chunks[i].size = data.size(); + d->chunks[i].padding = (data.size() & 0x01) ? 1 : 0; + + // Now update the internal offsets + + for(i++; i < d->chunks.size(); i++) + d->chunks[i].offset = d->chunks[i-1].offset + 8 + d->chunks[i-1].size + d->chunks[i-1].padding; +} + +void RIFF::File::setChunkData(const ByteVector &name, const ByteVector &data, bool alwaysCreate) { if(d->chunks.size() == 0) { debug("RIFF::File::setChunkData - No valid chunks found."); return; } - for(uint i = 0; i < d->chunks.size(); i++) { - if(d->chunks[i].name == name) { + if(alwaysCreate && name != "LIST") { + debug("RIFF::File::setChunkData - alwaysCreate should be used for only \"LIST\" chunks."); + return; + } - // First we update the global size - - d->size += ((data.size() + 1) & ~1) - (d->chunks[i].size + d->chunks[i].padding); - insert(ByteVector::fromUInt(d->size, d->endianness == BigEndian), 4, 4); - - // Now update the specific chunk - - writeChunk(name, data, d->chunks[i].offset - 8, d->chunks[i].size + d->chunks[i].padding + 8); - - d->chunks[i].size = data.size(); - d->chunks[i].padding = (data.size() & 0x01) ? 1 : 0; - - // Now update the internal offsets - - for(i++; i < d->chunks.size(); i++) - d->chunks[i].offset = d->chunks[i-1].offset + 8 + d->chunks[i-1].size + d->chunks[i-1].padding; - - return; + if(!alwaysCreate) { + for(uint i = 0; i < d->chunks.size(); i++) { + if(d->chunks[i].name == name) { + setChunkData(i, data); + return; + } } } @@ -181,7 +191,7 @@ void RIFF::File::setChunkData(const ByteVector &name, const ByteVector &data) // Now add the chunk to the file - writeChunk(name, data, offset, std::max(ulong(0), length() - offset), (offset & 1) ? 1 : 0); + writeChunk(name, data, offset, std::max(0, length() - offset), (offset & 1) ? 1 : 0); // And update our internal structure @@ -199,6 +209,28 @@ void RIFF::File::setChunkData(const ByteVector &name, const ByteVector &data) d->chunks.push_back(chunk); } +void RIFF::File::removeChunk(uint i) +{ + if(i >= d->chunks.size()) + return; + + removeBlock(d->chunks[i].offset - 8, d->chunks[i].size + 8); + d->chunks.erase(d->chunks.begin() + i); +} + +void RIFF::File::removeChunk(const ByteVector &name) +{ + std::vector newChunks; + for(size_t i = 0; i < d->chunks.size(); ++i) { + if(d->chunks[i].name == name) + removeBlock(d->chunks[i].offset - 8, d->chunks[i].size + 8); + else + newChunks.push_back(d->chunks[i]); + } + + d->chunks.swap(newChunks); +} + //////////////////////////////////////////////////////////////////////////////// // private members //////////////////////////////////////////////////////////////////////////////// diff --git a/taglib/riff/rifffile.h b/taglib/riff/rifffile.h index e418dbb6..274549ae 100644 --- a/taglib/riff/rifffile.h +++ b/taglib/riff/rifffile.h @@ -95,14 +95,39 @@ namespace TagLib { */ ByteVector chunkData(uint i); + /*! + * Sets the data for the the specified chunk to \a data. + * + * \warning This will update the file immediately. + */ + void setChunkData(uint i, const ByteVector &data); + /*! * Sets the data for the chunk \a name to \a data. If a chunk with the * given name already exists it will be overwritten, otherwise it will be * created after the existing chunks. * + * \note If \a alwaysCreate is true, a new chunk is created regardless of + * existence of chunk \a name. It should be used for only "LIST" chunks. + * * \warning This will update the file immediately. */ - void setChunkData(const ByteVector &name, const ByteVector &data); + void setChunkData(const ByteVector &name, const ByteVector &data, bool alwaysCreate = false); + + /*! + * Removes the specified chunk. + * + * \warning This will update the file immediately. + */ + void removeChunk(uint i); + + /*! + * Removes the chunk \a name. + * + * \warning This will update the file immediately. + * \warning This removes all the chunks with the given name. + */ + void removeChunk(const ByteVector &name); private: File(const File &); diff --git a/taglib/riff/wav/infotag.cpp b/taglib/riff/wav/infotag.cpp new file mode 100644 index 00000000..df1d2f81 --- /dev/null +++ b/taglib/riff/wav/infotag.cpp @@ -0,0 +1,258 @@ +/*************************************************************************** + copyright : (C) 2002 - 2008 by Scott Wheeler + email : wheeler@kde.org + ***************************************************************************/ + +/*************************************************************************** + * 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 +#include + +#include "infotag.h" + +using namespace TagLib; +using namespace RIFF::Info; + +namespace { + static bool isValidChunkID(const ByteVector &name) + { + if(name.size() != 4) + return false; + + for(int i = 0; i < 4; i++) { + if(name[i] < 32 || name[i] > 127) + return false; + } + + return true; + } +} + +class RIFF::Info::Tag::TagPrivate +{ +public: + TagPrivate() + {} + + FieldListMap fieldListMap; + + static const StringHandler *stringHandler; +}; + +//////////////////////////////////////////////////////////////////////////////// +// StringHandler implementation +//////////////////////////////////////////////////////////////////////////////// + +StringHandler::StringHandler() +{ +} + +StringHandler::~StringHandler() +{ +} + +String RIFF::Info::StringHandler::parse(const ByteVector &data) const +{ + return String(data, String::UTF8); +} + +ByteVector RIFF::Info::StringHandler::render(const String &s) const +{ + return s.data(String::UTF8); +} + +//////////////////////////////////////////////////////////////////////////////// +// public members +//////////////////////////////////////////////////////////////////////////////// + +static const StringHandler defaultStringHandler; +const RIFF::Info::StringHandler *RIFF::Info::Tag::TagPrivate::stringHandler = &defaultStringHandler; + +RIFF::Info::Tag::Tag(const ByteVector &data) : TagLib::Tag() +{ + d = new TagPrivate; + parse(data); +} + +RIFF::Info::Tag::Tag() : TagLib::Tag() +{ + d = new TagPrivate; +} + +RIFF::Info::Tag::~Tag() +{ +} + +String RIFF::Info::Tag::title() const +{ + return fieldText("INAM"); +} + +String RIFF::Info::Tag::artist() const +{ + return fieldText("IART"); +} + +String RIFF::Info::Tag::album() const +{ + return fieldText("IPRD"); +} + +String RIFF::Info::Tag::comment() const +{ + return fieldText("ICMT"); +} + +String RIFF::Info::Tag::genre() const +{ + return fieldText("IGNR"); +} + +TagLib::uint RIFF::Info::Tag::year() const +{ + return fieldText("ICRD").substr(0, 4).toInt(); +} + +TagLib::uint RIFF::Info::Tag::track() const +{ + return fieldText("IPRT").toInt(); +} + +void RIFF::Info::Tag::setTitle(const String &s) +{ + setFieldText("INAM", s); +} + +void RIFF::Info::Tag::setArtist(const String &s) +{ + setFieldText("IART", s); +} + +void RIFF::Info::Tag::setAlbum(const String &s) +{ + setFieldText("IPRD", s); +} + +void RIFF::Info::Tag::setComment(const String &s) +{ + setFieldText("ICMT", s); +} + +void RIFF::Info::Tag::setGenre(const String &s) +{ + setFieldText("IGNR", s); +} + +void RIFF::Info::Tag::setYear(uint i) +{ + if(i != 0) + setFieldText("ICRD", String::number(i)); + else + d->fieldListMap.erase("ICRD"); +} + +void RIFF::Info::Tag::setTrack(uint i) +{ + if(i != 0) + setFieldText("IPRT", String::number(i)); + else + d->fieldListMap.erase("IPRT"); +} + +bool RIFF::Info::Tag::isEmpty() const +{ + return d->fieldListMap.isEmpty(); +} + +String RIFF::Info::Tag::fieldText(const ByteVector &id) const +{ + if(d->fieldListMap.contains(id)) + return String(d->fieldListMap[id]); + else + return String(); +} + +void RIFF::Info::Tag::setFieldText(const ByteVector &id, const String &s) +{ + // id must be four-byte long pure ascii string. + if(!isValidChunkID(id)) + return; + + if(!s.isEmpty()) + d->fieldListMap[id] = s; + else + removeField(id); +} + +void RIFF::Info::Tag::removeField(const ByteVector &id) +{ + if(d->fieldListMap.contains(id)) + d->fieldListMap.erase(id); +} + +ByteVector RIFF::Info::Tag::render() const +{ + ByteVector data("INFO"); + + FieldListMap::ConstIterator it = d->fieldListMap.begin(); + for(; it != d->fieldListMap.end(); ++it) { + ByteVector text = TagPrivate::stringHandler->render(it->second); + if(text.isEmpty()) + continue; + + data.append(it->first); + data.append(ByteVector::fromUInt(text.size() + 1, false)); + data.append(text); + + do { + data.append('\0'); + } while(data.size() & 1); + } + + if(data.size() == 4) + return ByteVector(); + else + return data; +} + +void RIFF::Info::Tag::setStringHandler(const StringHandler *handler) +{ + if(handler) + TagPrivate::stringHandler = handler; + else + TagPrivate::stringHandler = &defaultStringHandler; +} + +//////////////////////////////////////////////////////////////////////////////// +// protected members +//////////////////////////////////////////////////////////////////////////////// + +void RIFF::Info::Tag::parse(const ByteVector &data) +{ + uint p = 4; + while(p < data.size()) { + uint size = data.mid(p + 4, 4).toUInt(false); + d->fieldListMap[data.mid(p, 4)] = TagPrivate::stringHandler->parse(data.mid(p + 8, size)); + + p += ((size + 1) & ~1) + 8; + } +} + diff --git a/taglib/riff/wav/infotag.h b/taglib/riff/wav/infotag.h new file mode 100644 index 00000000..72414841 --- /dev/null +++ b/taglib/riff/wav/infotag.h @@ -0,0 +1,179 @@ +/*************************************************************************** + copyright : (C) 2002 - 2008 by Scott Wheeler + email : wheeler@kde.org + ***************************************************************************/ + +/*************************************************************************** + * 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_INFOTAG_H +#define TAGLIB_INFOTAG_H + +#include "tag.h" +#include "tmap.h" +#include "tstring.h" +#include "tstringlist.h" +#include "tbytevector.h" +#include "taglib_export.h" + +namespace TagLib { + + class File; + + //! A RIFF Info tag implementation. + namespace RIFF { + namespace Info { + + typedef Map FieldListMap; + + //! A abstraction for the string to data encoding in Info tags. + + /*! + * RIFF Info tag has no clear definitions about character encodings. + * In practice, local encoding of each system is largely used and UTF-8 is + * popular too. + * + * Here is an option to read and write tags in your preferrd encoding + * by subclassing this class, reimplementing parse() and render() and setting + * your reimplementation as the default with Info::Tag::setStringHandler(). + * + * \see ID3v1::Tag::setStringHandler() + */ + + class TAGLIB_EXPORT StringHandler + { + public: + StringHandler(); + ~StringHandler(); + + /*! + * Decode a string from \a data. The default implementation assumes that + * \a data is an UTF-8 character array. + */ + virtual String parse(const ByteVector &data) const; + + /*! + * Encode a ByteVector with the data from \a s. The default implementation + * assumes that \a s is an UTF-8 string. + */ + virtual ByteVector render(const String &s) const; + }; + + //! The main class in the ID3v2 implementation + + /*! + * This is the main class in the INFO tag implementation. RIFF INFO tag is a + * metadata format found in WAV audio and AVI video files. Though it is a part + * of Microsoft/IBM's RIFF specification, the author could not find the official + * documents about it. So, this implementation is refering to unofficial documents + * online and some applications' behaviors especially Windows Explorer. + */ + class TAGLIB_EXPORT Tag : public TagLib::Tag + { + public: + /*! + * Constructs an empty Info tag. + */ + Tag(); + + /*! + * Constructs an Info tag read from \a data which is contents of "LIST" chunk. + */ + Tag(const ByteVector &data); + + virtual ~Tag(); + + // Reimplementations + + virtual String title() const; + virtual String artist() const; + virtual String album() const; + virtual String comment() const; + virtual String genre() const; + virtual uint year() const; + virtual uint track() const; + + virtual void setTitle(const String &s); + virtual void setArtist(const String &s); + virtual void setAlbum(const String &s); + virtual void setComment(const String &s); + virtual void setGenre(const String &s); + virtual void setYear(uint i); + virtual void setTrack(uint i); + + virtual bool isEmpty() const; + /* + * Gets the value of the field with the ID \a id. + */ + String fieldText(const ByteVector &id) const; + + /* + * Sets the value of the field with the ID \a id to \a s. + * If the field does not exist, it is created. + * If \s is empty, the field is removed. + * + * \note fieldId must be four-byte long pure ascii string. This function + * performs nothing if fieldId is invalid. + */ + void setFieldText(const ByteVector &id, const String &s); + + /* + * Removes the field with the ID \a id. + */ + void removeField(const ByteVector &id); + + /*! + * Render the tag back to binary data, suitable to be written to disk. + * + * \note Returns empty ByteVector is the tag contains no fields. + */ + ByteVector render() const; + + /*! + * Sets the string handler that decides how the text data will be + * converted to and from binary data. + * If the parameter \a handler is null, the previous handler is + * released and default UTF-8 handler is restored. + * + * \note The caller is responsible for deleting the previous handler + * as needed after it is released. + * + * \see StringHandler + */ + static void setStringHandler(const StringHandler *handler); + + protected: + /*! + * Pareses the body of the tag in \a data. + */ + void parse(const ByteVector &data); + + + private: + Tag(const Tag &); + Tag &operator=(const Tag &); + + class TagPrivate; + TagPrivate *d; + }; + }} +} + +#endif diff --git a/taglib/riff/wav/wavfile.cpp b/taglib/riff/wav/wavfile.cpp index 613db4ef..0ba40665 100644 --- a/taglib/riff/wav/wavfile.cpp +++ b/taglib/riff/wav/wavfile.cpp @@ -23,36 +23,42 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ -#include -#include -#include -#include -#include +#include "tbytevector.h" +#include "tdebug.h" +#include "tstringlist.h" +#include "tpropertymap.h" #include "wavfile.h" +#include "id3v2tag.h" +#include "infotag.h" +#include "tagunion.h" using namespace TagLib; +namespace +{ + enum { ID3v2Index = 0, InfoIndex = 1 }; +} + class RIFF::WAV::File::FilePrivate { public: FilePrivate() : properties(0), - tag(0), tagChunkID("ID3 ") { - } ~FilePrivate() { delete properties; - delete tag; } Properties *properties; - ID3v2::Tag *tag; + ByteVector tagChunkID; + + TagUnion tag; }; //////////////////////////////////////////////////////////////////////////////// @@ -82,26 +88,40 @@ RIFF::WAV::File::~File() ID3v2::Tag *RIFF::WAV::File::tag() const { - return d->tag; + return ID3v2Tag(); +} + +ID3v2::Tag *RIFF::WAV::File::ID3v2Tag() const +{ + return d->tag.access(ID3v2Index, false); +} + +RIFF::Info::Tag *RIFF::WAV::File::InfoTag() const +{ + return d->tag.access(InfoIndex, false); } PropertyMap RIFF::WAV::File::properties() const { - return d->tag->properties(); + return d->tag.properties(); } PropertyMap RIFF::WAV::File::setProperties(const PropertyMap &properties) { - return d->tag->setProperties(properties); + return d->tag.setProperties(properties); } - RIFF::WAV::Properties *RIFF::WAV::File::audioProperties() const { return d->properties; } bool RIFF::WAV::File::save() +{ + return RIFF::WAV::File::save(AllTags); +} + +bool RIFF::WAV::File::save(TagTypes tags, bool stripOthers, int id3v2Version) { if(readOnly()) { debug("RIFF::WAV::File::save() -- File is read only."); @@ -113,7 +133,25 @@ bool RIFF::WAV::File::save() return false; } - setChunkData(d->tagChunkID, d->tag->render()); + if(stripOthers) + strip(static_cast(AllTags & ~tags)); + + ID3v2::Tag *id3v2tag = d->tag.access(ID3v2Index, false); + if(!id3v2tag->isEmpty()) { + if(tags & ID3v2) + setChunkData(d->tagChunkID, id3v2tag->render(id3v2Version)); + } + + Info::Tag *infotag = d->tag.access(InfoIndex, false); + if(!infotag->isEmpty()) { + if(tags & Info) { + int chunkId = findInfoTagChunk(); + if(chunkId != -1) + setChunkData(chunkId, infotag->render()); + else + setChunkData("LIST", infotag->render(), true); + } + } return true; } @@ -127,19 +165,53 @@ void RIFF::WAV::File::read(bool readProperties, Properties::ReadStyle properties ByteVector formatData; uint streamLength = 0; for(uint i = 0; i < chunkCount(); i++) { - if(chunkName(i) == "ID3 " || chunkName(i) == "id3 ") { + String name = chunkName(i); + if(name == "ID3 " || name == "id3 ") { d->tagChunkID = chunkName(i); - d->tag = new ID3v2::Tag(this, chunkOffset(i)); + d->tag.set(ID3v2Index, new ID3v2::Tag(this, chunkOffset(i))); } - else if(chunkName(i) == "fmt " && readProperties) + else if(name == "fmt " && readProperties) formatData = chunkData(i); - else if(chunkName(i) == "data" && readProperties) + else if(name == "data" && readProperties) streamLength = chunkDataSize(i); + else if(name == "LIST") { + ByteVector data = chunkData(i); + ByteVector type = data.mid(0, 4); + + if(type == "INFO") + d->tag.set(InfoIndex, new RIFF::Info::Tag(data)); + } } + if (!d->tag[ID3v2Index]) + d->tag.set(ID3v2Index, new ID3v2::Tag); + + if (!d->tag[InfoIndex]) + d->tag.set(InfoIndex, new RIFF::Info::Tag); + if(!formatData.isEmpty()) d->properties = new Properties(formatData, streamLength, propertiesStyle); - - if(!d->tag) - d->tag = new ID3v2::Tag; +} + +void RIFF::WAV::File::strip(TagTypes tags) +{ + if(tags & ID3v2) + removeChunk(d->tagChunkID); + + if(tags & Info){ + TagLib::uint chunkId = findInfoTagChunk(); + if(chunkId != TagLib::uint(-1)) + removeChunk(chunkId); + } +} + +TagLib::uint RIFF::WAV::File::findInfoTagChunk() +{ + for(uint i = 0; i < chunkCount(); ++i) { + if(chunkName(i) == "LIST" && chunkData(i).mid(0, 4) == "INFO") { + return i; + } + } + + return TagLib::uint(-1); } diff --git a/taglib/riff/wav/wavfile.h b/taglib/riff/wav/wavfile.h index 861f3f77..18ce2336 100644 --- a/taglib/riff/wav/wavfile.h +++ b/taglib/riff/wav/wavfile.h @@ -28,6 +28,7 @@ #include "rifffile.h" #include "id3v2tag.h" +#include "infotag.h" #include "wavproperties.h" namespace TagLib { @@ -57,6 +58,17 @@ namespace TagLib { class TAGLIB_EXPORT File : public TagLib::RIFF::File { public: + enum TagTypes { + //! Empty set. Matches no tag types. + NoTags = 0x0000, + //! Matches ID3v2 tags. + ID3v2 = 0x0001, + //! Matches Info tags. + Info = 0x0002, + //! Matches all tag types. + AllTags = 0xffff + }; + /*! * Contructs an WAV file from \a file. If \a readProperties is true the * file's audio properties will also be read using \a propertiesStyle. If @@ -69,6 +81,9 @@ namespace TagLib { * Contructs an WAV file from \a file. If \a readProperties is true the * file's audio properties will also be read using \a propertiesStyle. If * false, \a propertiesStyle is ignored. + * + * \note TagLib will *not* take ownership of the stream, the caller is + * responsible for deleting it after the File object. */ File(IOStream *stream, bool readProperties = true, Properties::ReadStyle propertiesStyle = Properties::Average); @@ -79,9 +94,22 @@ namespace TagLib { virtual ~File(); /*! - * Returns the Tag for this file. + * Returns the ID3v2 Tag for this file. + * + * \note This method does not return all the tags for this file for + * backward compatibility. Will be fixed in TagLib 2.0. */ - virtual ID3v2::Tag *tag() const; + ID3v2::Tag *tag() const; + + /*! + * Returns the ID3v2 Tag for this file. + */ + ID3v2::Tag *ID3v2Tag() const; + + /*! + * Returns the RIFF INFO Tag for this file. + */ + Info::Tag *InfoTag() const; /*! * Implements the unified property interface -- export function. @@ -106,12 +134,21 @@ namespace TagLib { */ virtual bool save(); + bool save(TagTypes tags, bool stripOthers = true, int id3v2Version = 4); + private: File(const File &); File &operator=(const File &); void read(bool readProperties, Properties::ReadStyle propertiesStyle); + void strip(TagTypes tags); + + /*! + * Returns the index of the chunk that its name is "LIST" and list type is "INFO". + */ + uint findInfoTagChunk(); + class FilePrivate; FilePrivate *d; }; diff --git a/taglib/s3m/s3mfile.cpp b/taglib/s3m/s3mfile.cpp index 7ffdf910..371340a5 100644 --- a/taglib/s3m/s3mfile.cpp +++ b/taglib/s3m/s3mfile.cpp @@ -47,7 +47,8 @@ S3M::File::File(FileName file, bool readProperties, Mod::FileBase(file), d(new FilePrivate(propertiesStyle)) { - read(readProperties); + if(isOpen()) + read(readProperties); } S3M::File::File(IOStream *stream, bool readProperties, @@ -55,7 +56,8 @@ S3M::File::File(IOStream *stream, bool readProperties, Mod::FileBase(stream), d(new FilePrivate(propertiesStyle)) { - read(readProperties); + if(isOpen()) + read(readProperties); } S3M::File::~File() diff --git a/taglib/s3m/s3mfile.h b/taglib/s3m/s3mfile.h index 0605b2bf..c862108e 100644 --- a/taglib/s3m/s3mfile.h +++ b/taglib/s3m/s3mfile.h @@ -48,6 +48,9 @@ namespace TagLib { * Contructs a ScreamTracker III file from \a stream. If \a readProperties * is true the file's audio properties will also be read using * \a propertiesStyle. If false, \a propertiesStyle is ignored. + * + * \note TagLib will *not* take ownership of the stream, the caller is + * responsible for deleting it after the File object. */ File(IOStream *stream, bool readProperties = true, AudioProperties::ReadStyle propertiesStyle = diff --git a/taglib/toolkit/taglib.h b/taglib/toolkit/taglib.h old mode 100644 new mode 100755 index dda9c83c..e941ca78 --- a/taglib/toolkit/taglib.h +++ b/taglib/toolkit/taglib.h @@ -27,7 +27,7 @@ #define TAGLIB_H #define TAGLIB_MAJOR_VERSION 1 -#define TAGLIB_MINOR_VERSION 7 +#define TAGLIB_MINOR_VERSION 8 #define TAGLIB_PATCH_VERSION 0 #if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 1)) @@ -48,7 +48,9 @@ # include # define TAGLIB_ATOMIC_MAC #elif defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__CYGWIN__) -# define NOMINMAX +# if !defined(NOMINMAX) +# define NOMINMAX +# endif # include # define TAGLIB_ATOMIC_WIN #elif defined (__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__ >= 401) \ @@ -143,7 +145,7 @@ namespace TagLib { * - A clean, high level, C++ API to handling audio meta data. * - Format specific APIs for advanced API users. * - ID3v1, ID3v2, APE, FLAC, Xiph, iTunes-style MP4 and WMA tag formats. - * - MP3, MPC, FLAC, MP4, ASF, AIFF, WAV, TrueAudio, WavPack, Ogg FLAC, Ogg Vorbis and Speex file formats. + * - MP3, MPC, FLAC, MP4, ASF, AIFF, WAV, TrueAudio, WavPack, Ogg FLAC, Ogg Vorbis, Speex and Opus file formats. * - Basic audio file properties such as length, sample rate, etc. * - Long term binary and source compatibility. * - Extensible design, notably the ability to add other formats or extend current formats as a library user. diff --git a/taglib/toolkit/tfile.cpp b/taglib/toolkit/tfile.cpp index 09b8fdbc..7251f3f7 100644 --- a/taglib/toolkit/tfile.cpp +++ b/taglib/toolkit/tfile.cpp @@ -60,6 +60,7 @@ #include "mp4file.h" #include "wavpackfile.h" #include "speexfile.h" +#include "opusfile.h" #include "trueaudiofile.h" #include "aifffile.h" #include "wavfile.h" @@ -68,21 +69,24 @@ #include "s3mfile.h" #include "itfile.h" #include "xmfile.h" +#include "mp4file.h" using namespace TagLib; class File::FilePrivate { public: - FilePrivate(IOStream *stream); + FilePrivate(IOStream *stream, bool owner); IOStream *stream; + bool streamOwner; bool valid; static const uint bufferSize = 1024; }; -File::FilePrivate::FilePrivate(IOStream *stream) : +File::FilePrivate::FilePrivate(IOStream *stream, bool owner) : stream(stream), + streamOwner(owner), valid(true) { } @@ -94,17 +98,17 @@ File::FilePrivate::FilePrivate(IOStream *stream) : File::File(FileName fileName) { IOStream *stream = new FileStream(fileName); - d = new FilePrivate(stream); + d = new FilePrivate(stream, true); } File::File(IOStream *stream) { - d = new FilePrivate(stream); + d = new FilePrivate(stream, false); } File::~File() { - if(d->stream) + if(d->stream && d->streamOwner) delete d->stream; delete d; } @@ -133,6 +137,8 @@ PropertyMap File::properties() const return dynamic_cast(this)->properties(); if(dynamic_cast(this)) return dynamic_cast(this)->properties(); + if(dynamic_cast(this)) + return dynamic_cast(this)->properties(); if(dynamic_cast(this)) return dynamic_cast(this)->properties(); if(dynamic_cast(this)) @@ -147,12 +153,10 @@ PropertyMap File::properties() const return dynamic_cast(this)->properties(); if(dynamic_cast(this)) return dynamic_cast(this)->properties(); - // no specialized implementation available -> use generic one - // - ASF: ugly format, largely undocumented, not worth implementing - // dict interface ... - // - MP4: taglib's MP4::Tag does not really support anything beyond - // the basic implementation, therefor we use just the default Tag - // interface + if(dynamic_cast(this)) + return dynamic_cast(this)->properties(); + if(dynamic_cast(this)) + return dynamic_cast(this)->properties(); return tag()->properties(); } @@ -172,6 +176,8 @@ void File::removeUnsupportedProperties(const StringList &properties) dynamic_cast(this)->removeUnsupportedProperties(properties); else if(dynamic_cast(this)) dynamic_cast(this)->removeUnsupportedProperties(properties); + else if(dynamic_cast(this)) + dynamic_cast(this)->removeUnsupportedProperties(properties); else if(dynamic_cast(this)) dynamic_cast(this)->removeUnsupportedProperties(properties); else if(dynamic_cast(this)) @@ -186,6 +192,10 @@ void File::removeUnsupportedProperties(const StringList &properties) dynamic_cast(this)->removeUnsupportedProperties(properties); else if(dynamic_cast(this)) dynamic_cast(this)->removeUnsupportedProperties(properties); + else if(dynamic_cast(this)) + dynamic_cast(this)->removeUnsupportedProperties(properties); + else if(dynamic_cast(this)) + dynamic_cast(this)->removeUnsupportedProperties(properties); else tag()->removeUnsupportedProperties(properties); } @@ -208,6 +218,8 @@ PropertyMap File::setProperties(const PropertyMap &properties) return dynamic_cast(this)->setProperties(properties); else if(dynamic_cast(this)) return dynamic_cast(this)->setProperties(properties); + else if(dynamic_cast(this)) + return dynamic_cast(this)->setProperties(properties); else if(dynamic_cast(this)) return dynamic_cast(this)->setProperties(properties); else if(dynamic_cast(this)) @@ -222,6 +234,10 @@ PropertyMap File::setProperties(const PropertyMap &properties) return dynamic_cast(this)->setProperties(properties); else if(dynamic_cast(this)) return dynamic_cast(this)->setProperties(properties); + else if(dynamic_cast(this)) + return dynamic_cast(this)->setProperties(properties); + else if(dynamic_cast(this)) + return dynamic_cast(this)->setProperties(properties); else return tag()->setProperties(properties); } @@ -452,12 +468,32 @@ long File::length() bool File::isReadable(const char *file) { + +#if defined(_MSC_VER) && (_MSC_VER >= 1400) // VC++2005 or later + + return _access_s(file, R_OK) == 0; + +#else + return access(file, R_OK) == 0; + +#endif + } bool File::isWritable(const char *file) { + +#if defined(_MSC_VER) && (_MSC_VER >= 1400) // VC++2005 or later + + return _access_s(file, W_OK) == 0; + +#else + return access(file, W_OK) == 0; + +#endif + } //////////////////////////////////////////////////////////////////////////////// diff --git a/taglib/toolkit/tfile.h b/taglib/toolkit/tfile.h index 7df774a0..7e6f2b93 100644 --- a/taglib/toolkit/tfile.h +++ b/taglib/toolkit/tfile.h @@ -260,6 +260,9 @@ namespace TagLib { /*! * Construct a File object and use the \a stream instance. * + * \note TagLib will *not* take ownership of the stream, the caller is + * responsible for deleting it after the File object. + * * \note Constructor is protected since this class should only be * instantiated through subclasses. */ diff --git a/taglib/toolkit/tfilestream.cpp b/taglib/toolkit/tfilestream.cpp index e2aebae3..52252023 100644 --- a/taglib/toolkit/tfilestream.cpp +++ b/taglib/toolkit/tfilestream.cpp @@ -35,43 +35,86 @@ # include # include # include -# define ftruncate _chsize #else # include #endif #include -#ifndef R_OK -# define R_OK 4 -#endif -#ifndef W_OK -# define W_OK 2 -#endif - using namespace TagLib; +namespace { #ifdef _WIN32 -typedef FileName FileNameHandle; + // For Windows + + typedef FileName FileNameHandle; + + // Using native file handles instead of file descriptors for reducing the resource consumption. + + const HANDLE InvalidFile = INVALID_HANDLE_VALUE; + + HANDLE openFile(const FileName &path, bool readOnly) + { + DWORD access = readOnly ? GENERIC_READ : (GENERIC_READ | GENERIC_WRITE); + + if(wcslen(path) > 0) + return CreateFileW(path, access, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); + else + return CreateFileA(path, access, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); + } + + size_t fread(void *ptr, size_t size, size_t nmemb, HANDLE stream) + { + DWORD readLen; + ReadFile(stream, ptr, size * nmemb, &readLen, NULL); + + return (readLen / size); + } + + size_t fwrite(const void *ptr, size_t size, size_t nmemb, HANDLE stream) + { + DWORD writtenLen; + WriteFile(stream, ptr, size * nmemb, &writtenLen, NULL); + + return writtenLen; + } #else -struct FileNameHandle : public std::string -{ - FileNameHandle(FileName name) : std::string(name) {} - operator FileName () const { return c_str(); } -}; + // For non-Windows + + FILE *const InvalidFile = 0; + + struct FileNameHandle : public std::string + { + FileNameHandle(FileName name) : std::string(name) {} + operator FileName () const { return c_str(); } + }; + + FILE *openFile(const FileName &path, bool readOnly) + { + return fopen(path, readOnly ? "rb" : "rb+"); + } #endif +} class FileStream::FileStreamPrivate { public: FileStreamPrivate(FileName fileName, bool openReadOnly); +#ifdef _WIN32 + + HANDLE file; + +#else + FILE *file; +#endif + FileNameHandle name; bool readOnly; @@ -80,48 +123,22 @@ public: }; FileStream::FileStreamPrivate::FileStreamPrivate(FileName fileName, bool openReadOnly) : - file(0), + file(InvalidFile), name(fileName), readOnly(true), size(0) { // First try with read / write mode, if that fails, fall back to read only. -#ifdef _WIN32 + if(!openReadOnly) + file = openFile(name, false); - if(wcslen((const wchar_t *) fileName) > 0) { + if(file != InvalidFile) + readOnly = false; + else + file = openFile(name, true); - if(openReadOnly) - file = _wfopen(name, L"rb"); - else { - file = _wfopen(name, L"rb+"); - - if(file) - readOnly = false; - else - file = _wfopen(name, L"rb"); - } - - if(file) - return; - - } - -#endif - - if(openReadOnly) - file = fopen(name, "rb"); - else { - file = fopen(name, "rb+"); - - if(file) - readOnly = false; - else - file = fopen(name, "rb"); - } - - if(!file) - { + if(file == InvalidFile) { debug("Could not open file " + String((const char *) name)); } } @@ -137,8 +154,18 @@ FileStream::FileStream(FileName file, bool openReadOnly) FileStream::~FileStream() { +#ifdef _WIN32 + + if(d->file) + CloseHandle(d->file); + +#else + if(d->file) fclose(d->file); + +#endif + delete d; } @@ -329,27 +356,67 @@ void FileStream::seek(long offset, Position p) return; } +#ifdef _WIN32 + + DWORD whence; switch(p) { case Beginning: - fseek(d->file, offset, SEEK_SET); + whence = FILE_BEGIN; break; case Current: - fseek(d->file, offset, SEEK_CUR); + whence = FILE_CURRENT; break; case End: - fseek(d->file, offset, SEEK_END); + whence = FILE_END; break; } + + SetFilePointer(d->file, offset, NULL, whence); + +#else + + int whence; + switch(p) { + case Beginning: + whence = SEEK_SET; + break; + case Current: + whence = SEEK_CUR; + break; + case End: + whence = SEEK_END; + break; + } + + fseek(d->file, offset, whence); + +#endif } void FileStream::clear() { +#ifdef _WIN32 + + // NOP + +#else + clearerr(d->file); + +#endif } long FileStream::tell() const { +#ifdef _WIN32 + + return (long)SetFilePointer(d->file, 0, NULL, FILE_CURRENT); + +#else + return ftell(d->file); + +#endif } long FileStream::length() @@ -379,7 +446,20 @@ long FileStream::length() void FileStream::truncate(long length) { +#ifdef _WIN32 + + long currentPos = tell(); + + seek(length); + SetEndOfFile(d->file); + + seek(currentPos); + +#else + ftruncate(fileno(d->file), length); + +#endif } TagLib::uint FileStream::bufferSize() diff --git a/taglib/toolkit/tpropertymap.h b/taglib/toolkit/tpropertymap.h index 7f59b21e..2be49ddb 100644 --- a/taglib/toolkit/tpropertymap.h +++ b/taglib/toolkit/tpropertymap.h @@ -40,6 +40,65 @@ namespace TagLib { * Note that most metadata formats pose additional conditions on the tag keys. The * most popular ones (Vorbis, APE, ID3v2) should support all ASCII only words of * length between 2 and 16. + * + * This class can contain any tags, but here is a list of "well-known" tags that + * you might want to use: + * + * Basic tags: + * + * - TITLE + * - ALBUM + * - ARTIST + * - ALBUMARTIST + * - SUBTITLE + * - TRACKNUMBER + * - DISCNUMBER + * - DATE + * - ORIGINALDATE + * - GENRE + * - COMMENT + * + * Sort names: + * + * - TITLESORT + * - ALBUMSORT + * - ARTISTSORT + * - ALBUMARTISTSORT + * + * Credits: + * + * - COMPOSER + * - LYRICIST + * - CONDUCTOR + * - REMIXER + * - PERFORMER: + * + * Other tags: + * + * - ISRC + * - ASIN + * - BPM + * - COPYRIGHT + * - ENCODEDBY + * - MOOD + * - COMMENT + * - MEDIA + * - LABEL + * - CATALOGNUMBER + * - BARCODE + * + * MusicBrainz identifiers: + * + * - MUSICBRAINZ_TRACKID + * - MUSICBRAINZ_ALBUMID + * - MUSICBRAINZ_RELEASEGROUPID + * - MUSICBRAINZ_WORKID + * - MUSICBRAINZ_ARTISTID + * - MUSICBRAINZ_ALBUMARTISTID + * - ACOUSTID_ID + * - ACOUSTID_FINGERPRINT + * - MUSICIP_PUID + * */ class TAGLIB_EXPORT PropertyMap: public SimplePropertyMap diff --git a/taglib/toolkit/tstring.cpp b/taglib/toolkit/tstring.cpp index f99d1c37..292f3533 100644 --- a/taglib/toolkit/tstring.cpp +++ b/taglib/toolkit/tstring.cpp @@ -233,8 +233,9 @@ std::string String::to8Bit(bool unicode) const &target, targetBuffer + outputBufferSize, Unicode::lenientConversion); - if(result != Unicode::conversionOK) + if(result != Unicode::conversionOK) { debug("String::to8Bit() - Unicode conversion error."); + } int newSize = target - targetBuffer; s.resize(newSize); @@ -259,8 +260,17 @@ const char *String::toCString(bool unicode) const std::string buffer = to8Bit(unicode); d->CString = new char[buffer.size() + 1]; + +#if defined(_MSC_VER) && (_MSC_VER >= 1400) // VC++2005 or later + + strcpy_s(d->CString, buffer.size() + 1, buffer.c_str()); + +#else + strcpy(d->CString, buffer.c_str()); +#endif + return d->CString; } @@ -335,9 +345,6 @@ bool String::startsWith(const String &s) const String String::substr(uint position, uint n) const { - if(n > position + d->data.size()) - n = d->data.size() - position; - String s; s.d->data = d->data.substr(position, n); return s; @@ -779,9 +786,9 @@ void String::prepare(Type t) &target, targetBuffer + bufferSize, Unicode::lenientConversion); - if(result != Unicode::conversionOK) + if(result != Unicode::conversionOK) { debug("String::prepare() - Unicode conversion error."); - + } int newSize = target != targetBuffer ? target - targetBuffer - 1 : 0; d->data.resize(newSize); diff --git a/taglib/toolkit/unicode.cpp b/taglib/toolkit/unicode.cpp index b60264d9..1b26977e 100644 --- a/taglib/toolkit/unicode.cpp +++ b/taglib/toolkit/unicode.cpp @@ -155,7 +155,7 @@ ConversionResult ConvertUTF16toUTF8 ( case 4: *--target = (ch | byteMark) & byteMask; ch >>= 6; case 3: *--target = (ch | byteMark) & byteMask; ch >>= 6; case 2: *--target = (ch | byteMark) & byteMask; ch >>= 6; - case 1: *--target = ch | firstByteMark[bytesToWrite]; + case 1: *--target = (UTF8)(ch | firstByteMark[bytesToWrite]); } target += bytesToWrite; } @@ -253,7 +253,7 @@ ConversionResult ConvertUTF8toUTF16 ( result = sourceIllegal; break; } else { - *target++ = ch; /* normal case */ + *target++ = (UTF16)ch; /* normal case */ } } else if (ch > UNI_MAX_UTF16) { if (flags == strictConversion) { @@ -270,7 +270,7 @@ ConversionResult ConvertUTF8toUTF16 ( result = targetExhausted; break; } ch -= halfBase; - *target++ = (ch >> halfShift) + UNI_SUR_HIGH_START; + *target++ = (UTF16)((ch >> halfShift) + UNI_SUR_HIGH_START); *target++ = (ch & halfMask) + UNI_SUR_LOW_START; } } diff --git a/taglib/trueaudio/trueaudiofile.h b/taglib/trueaudio/trueaudiofile.h index 9b0378f7..e3e1fe62 100644 --- a/taglib/trueaudio/trueaudiofile.h +++ b/taglib/trueaudio/trueaudiofile.h @@ -100,6 +100,9 @@ namespace TagLib { * Contructs an TrueAudio file from \a file. If \a readProperties is true the * file's audio properties will also be read using \a propertiesStyle. If * false, \a propertiesStyle is ignored. + * + * \note TagLib will *not* take ownership of the stream, the caller is + * responsible for deleting it after the File object. */ File(IOStream *stream, bool readProperties = true, Properties::ReadStyle propertiesStyle = Properties::Average); @@ -109,6 +112,9 @@ namespace TagLib { * file's audio properties will also be read using \a propertiesStyle. If * false, \a propertiesStyle is ignored. The frames will be created using * \a frameFactory. + * + * \note TagLib will *not* take ownership of the stream, the caller is + * responsible for deleting it after the File object. */ File(IOStream *stream, ID3v2::FrameFactory *frameFactory, bool readProperties = true, diff --git a/taglib/wavpack/wavpackfile.cpp b/taglib/wavpack/wavpackfile.cpp index 49f7923e..2d1f8cd9 100644 --- a/taglib/wavpack/wavpackfile.cpp +++ b/taglib/wavpack/wavpackfile.cpp @@ -86,14 +86,16 @@ WavPack::File::File(FileName file, bool readProperties, Properties::ReadStyle propertiesStyle) : TagLib::File(file) { d = new FilePrivate; - read(readProperties, propertiesStyle); + if(isOpen()) + read(readProperties, propertiesStyle); } WavPack::File::File(IOStream *stream, bool readProperties, Properties::ReadStyle propertiesStyle) : TagLib::File(stream) { d = new FilePrivate; - read(readProperties, propertiesStyle); + if(isOpen()) + read(readProperties, propertiesStyle); } WavPack::File::~File() diff --git a/taglib/wavpack/wavpackfile.h b/taglib/wavpack/wavpackfile.h index 02bac023..5bbbc65a 100644 --- a/taglib/wavpack/wavpackfile.h +++ b/taglib/wavpack/wavpackfile.h @@ -91,6 +91,9 @@ namespace TagLib { * Contructs an WavPack file from \a file. If \a readProperties is true the * file's audio properties will also be read using \a propertiesStyle. If * false, \a propertiesStyle is ignored. + * + * \note TagLib will *not* take ownership of the stream, the caller is + * responsible for deleting it after the File object. */ File(IOStream *stream, bool readProperties = true, Properties::ReadStyle propertiesStyle = Properties::Average); diff --git a/taglib/xm/xmfile.cpp b/taglib/xm/xmfile.cpp index 272e5fe0..d8373a78 100644 --- a/taglib/xm/xmfile.cpp +++ b/taglib/xm/xmfile.cpp @@ -359,7 +359,8 @@ XM::File::File(FileName file, bool readProperties, Mod::FileBase(file), d(new FilePrivate(propertiesStyle)) { - read(readProperties); + if(isOpen()) + read(readProperties); } XM::File::File(IOStream *stream, bool readProperties, @@ -367,7 +368,8 @@ XM::File::File(IOStream *stream, bool readProperties, Mod::FileBase(stream), d(new FilePrivate(propertiesStyle)) { - read(readProperties); + if(isOpen()) + read(readProperties); } XM::File::~File() @@ -443,7 +445,7 @@ bool XM::File::save() return false; uint len = std::min(22UL, instrumentHeaderSize - 4U); - if(i > lines.size()) + if(i >= lines.size()) writeString(String::null, len); else writeString(lines[i], len); diff --git a/taglib/xm/xmfile.h b/taglib/xm/xmfile.h index 1b07010b..9d1bb71c 100644 --- a/taglib/xm/xmfile.h +++ b/taglib/xm/xmfile.h @@ -48,6 +48,9 @@ namespace TagLib { * Contructs a Extended Module file from \a stream. If \a readProperties * is true the file's audio properties will also be read using * \a propertiesStyle. If false, \a propertiesStyle is ignored. + * + * \note TagLib will *not* take ownership of the stream, the caller is + * responsible for deleting it after the File object. */ File(IOStream *stream, bool readProperties = true, AudioProperties::ReadStyle propertiesStyle = diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 38ba4d25..4e428af8 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -16,6 +16,8 @@ INCLUDE_DIRECTORIES( ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/ogg ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/ogg/vorbis ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/ogg/flac + ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/ogg/speex + ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/ogg/opus ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/flac ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/wavpack ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/mod @@ -60,8 +62,11 @@ SET(test_runner_SRCS test_it.cpp test_xm.cpp test_mpc.cpp + test_opus.cpp ) +INCLUDE_DIRECTORIES(${CPPUNIT_INCLUDE_DIR}) + ADD_EXECUTABLE(test_runner ${test_runner_SRCS}) TARGET_LINK_LIBRARIES(test_runner tag ${CPPUNIT_LIBRARIES}) diff --git a/tests/data/correctness_gain_silent_output.opus b/tests/data/correctness_gain_silent_output.opus new file mode 100644 index 00000000..00972c42 Binary files /dev/null and b/tests/data/correctness_gain_silent_output.opus differ diff --git a/tests/data/w000.mp3 b/tests/data/w000.mp3 new file mode 100644 index 00000000..f9c22617 Binary files /dev/null and b/tests/data/w000.mp3 differ diff --git a/tests/test_apetag.cpp b/tests/test_apetag.cpp index 6cda5e52..c93b2a7e 100644 --- a/tests/test_apetag.cpp +++ b/tests/test_apetag.cpp @@ -1,4 +1,3 @@ -#include #include #include #include @@ -7,6 +6,7 @@ #include #include #include +#include #include "utils.h" using namespace std; @@ -77,7 +77,7 @@ public: APE::Item item3 = APE::Item("TRACKNUMBER", "29"); tag.setItem("TRACKNUMBER", item3); properties = tag.properties(); - CPPUNIT_ASSERT_EQUAL(uint(2), properties["TRACKNUMBER"].size()); + CPPUNIT_ASSERT_EQUAL(TagLib::uint(2), properties["TRACKNUMBER"].size()); CPPUNIT_ASSERT_EQUAL(String("17"), properties["TRACKNUMBER"][0]); CPPUNIT_ASSERT_EQUAL(String("29"), properties["TRACKNUMBER"][1]); @@ -93,7 +93,7 @@ public: APE::Tag tag; PropertyMap unsuccessful = tag.setProperties(properties); - CPPUNIT_ASSERT_EQUAL(uint(2), unsuccessful.size()); + CPPUNIT_ASSERT_EQUAL(TagLib::uint(2), unsuccessful.size()); CPPUNIT_ASSERT(unsuccessful.contains("A")); CPPUNIT_ASSERT(unsuccessful.contains("MP+")); } diff --git a/tests/test_asf.cpp b/tests/test_asf.cpp index 51981342..8610c24b 100644 --- a/tests/test_asf.cpp +++ b/tests/test_asf.cpp @@ -1,10 +1,11 @@ -#include #include #include #include #include #include +#include #include +#include #include "utils.h" using namespace std; @@ -13,7 +14,7 @@ using namespace TagLib; class TestASF : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(TestASF); - CPPUNIT_TEST(testProperties); + CPPUNIT_TEST(testAudioProperties); CPPUNIT_TEST(testRead); CPPUNIT_TEST(testSaveMultipleValues); CPPUNIT_TEST(testSaveStream); @@ -22,11 +23,12 @@ class TestASF : public CppUnit::TestFixture CPPUNIT_TEST(testSaveLargeValue); CPPUNIT_TEST(testSavePicture); CPPUNIT_TEST(testSaveMultiplePictures); + CPPUNIT_TEST(testProperties); CPPUNIT_TEST_SUITE_END(); public: - void testProperties() + void testAudioProperties() { ASF::File f(TEST_FILE_PATH_C("silence-1.wma")); CPPUNIT_ASSERT_EQUAL(4, f.audioProperties()->length()); @@ -215,6 +217,39 @@ public: delete f; } + void testProperties() + { + ASF::File f(TEST_FILE_PATH_C("silence-1.wma")); + + PropertyMap tags = f.properties(); + + tags["TRACKNUMBER"] = StringList("2"); + tags["DISCNUMBER"] = StringList("3"); + tags["BPM"] = StringList("123"); + tags["ARTIST"] = StringList("Foo Bar"); + f.setProperties(tags); + + tags = f.properties(); + + CPPUNIT_ASSERT_EQUAL(String("Foo Bar"), f.tag()->artist()); + CPPUNIT_ASSERT_EQUAL(StringList("Foo Bar"), tags["ARTIST"]); + + CPPUNIT_ASSERT(f.tag()->attributeListMap().contains("WM/BeatsPerMinute")); + CPPUNIT_ASSERT_EQUAL(1u, f.tag()->attributeListMap()["WM/BeatsPerMinute"].size()); + CPPUNIT_ASSERT_EQUAL(String("123"), f.tag()->attributeListMap()["WM/BeatsPerMinute"].front().toString()); + CPPUNIT_ASSERT_EQUAL(StringList("123"), tags["BPM"]); + + CPPUNIT_ASSERT(f.tag()->attributeListMap().contains("WM/TrackNumber")); + CPPUNIT_ASSERT_EQUAL(1u, f.tag()->attributeListMap()["WM/TrackNumber"].size()); + CPPUNIT_ASSERT_EQUAL(String("2"), f.tag()->attributeListMap()["WM/TrackNumber"].front().toString()); + CPPUNIT_ASSERT_EQUAL(StringList("2"), tags["TRACKNUMBER"]); + + CPPUNIT_ASSERT(f.tag()->attributeListMap().contains("WM/PartOfSet")); + CPPUNIT_ASSERT_EQUAL(1u, f.tag()->attributeListMap()["WM/PartOfSet"].size()); + CPPUNIT_ASSERT_EQUAL(String("3"), f.tag()->attributeListMap()["WM/PartOfSet"].front().toString()); + CPPUNIT_ASSERT_EQUAL(StringList("3"), tags["DISCNUMBER"]); + } + }; CPPUNIT_TEST_SUITE_REGISTRATION(TestASF); diff --git a/tests/test_bytevector.cpp b/tests/test_bytevector.cpp index 2ac78b34..dd433456 100644 --- a/tests/test_bytevector.cpp +++ b/tests/test_bytevector.cpp @@ -22,9 +22,9 @@ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include #include #include +#include using namespace std; using namespace TagLib; diff --git a/tests/test_bytevectorlist.cpp b/tests/test_bytevectorlist.cpp index f090fcdc..7e4a9fd7 100644 --- a/tests/test_bytevectorlist.cpp +++ b/tests/test_bytevectorlist.cpp @@ -1,6 +1,6 @@ -#include #include #include +#include using namespace std; using namespace TagLib; diff --git a/tests/test_bytevectorstream.cpp b/tests/test_bytevectorstream.cpp index b5114679..b0fec90b 100644 --- a/tests/test_bytevectorstream.cpp +++ b/tests/test_bytevectorstream.cpp @@ -1,5 +1,5 @@ -#include #include +#include using namespace std; using namespace TagLib; diff --git a/tests/test_fileref.cpp b/tests/test_fileref.cpp index 3268fdb4..5500ae7f 100644 --- a/tests/test_fileref.cpp +++ b/tests/test_fileref.cpp @@ -1,10 +1,10 @@ -#include #include #include #include #include #include #include +#include #include "utils.h" #ifdef HAVE_CONFIG_H #include "config.h" @@ -29,6 +29,7 @@ class TestFileRef : public CppUnit::TestFixture CPPUNIT_TEST(testMP4_3); CPPUNIT_TEST(testTrueAudio); CPPUNIT_TEST(testAPE); + CPPUNIT_TEST(testWav); CPPUNIT_TEST_SUITE_END(); public: @@ -127,6 +128,11 @@ public: fileRefSave("no-tags", ".3g2"); } + void testWav() + { + fileRefSave("empty", ".wav"); + } + void testOGA_FLAC() { FileRef *f = new FileRef(TEST_FILE_PATH_C("empty_flac.oga")); @@ -143,7 +149,7 @@ public: void testAPE() { - fileRefSave("mac-399.ape", ".ape"); + fileRefSave("mac-399", ".ape"); } }; diff --git a/tests/test_flac.cpp b/tests/test_flac.cpp index f6a65e09..7f40a382 100644 --- a/tests/test_flac.cpp +++ b/tests/test_flac.cpp @@ -1,4 +1,3 @@ -#include #include #include #include @@ -7,6 +6,7 @@ #include #include #include +#include #include "utils.h" using namespace std; diff --git a/tests/test_flacpicture.cpp b/tests/test_flacpicture.cpp index 5ed13951..0ba9452f 100644 --- a/tests/test_flacpicture.cpp +++ b/tests/test_flacpicture.cpp @@ -1,4 +1,3 @@ -#include #include #include #include @@ -6,6 +5,7 @@ #include #include #include +#include #include "utils.h" using namespace std; diff --git a/tests/test_flacunknownmetadatablock.cpp b/tests/test_flacunknownmetadatablock.cpp index 2a99a1ee..881078dd 100644 --- a/tests/test_flacunknownmetadatablock.cpp +++ b/tests/test_flacunknownmetadatablock.cpp @@ -1,10 +1,10 @@ -#include #include #include #include #include #include #include +#include #include "utils.h" using namespace std; diff --git a/tests/test_id3v1.cpp b/tests/test_id3v1.cpp index 308225c2..1d0a4974 100644 --- a/tests/test_id3v1.cpp +++ b/tests/test_id3v1.cpp @@ -1,7 +1,7 @@ -#include #include #include #include +#include using namespace std; using namespace TagLib; diff --git a/tests/test_id3v2.cpp b/tests/test_id3v2.cpp index 0dd033d5..48faf306 100644 --- a/tests/test_id3v2.cpp +++ b/tests/test_id3v2.cpp @@ -1,4 +1,3 @@ -#include #include #include // so evil :( @@ -15,8 +14,10 @@ #include #include #include +#include #include #include +#include #include "utils.h" using namespace std; @@ -61,6 +62,8 @@ class TestID3v2 : public CppUnit::TestFixture CPPUNIT_TEST(testRenderUrlLinkFrame); CPPUNIT_TEST(testParseUserUrlLinkFrame); CPPUNIT_TEST(testRenderUserUrlLinkFrame); + CPPUNIT_TEST(testParseOwnershipFrame); + CPPUNIT_TEST(testRenderOwnershipFrame); CPPUNIT_TEST(testSaveUTF16Comment); CPPUNIT_TEST(testUpdateGenre23_1); CPPUNIT_TEST(testUpdateGenre23_2); @@ -69,6 +72,7 @@ class TestID3v2 : public CppUnit::TestFixture CPPUNIT_TEST(testDowngradeTo23); // CPPUNIT_TEST(testUpdateFullDate22); TODO TYE+TDA should be upgraded to TDRC together CPPUNIT_TEST(testCompressedFrameWithBrokenLength); + CPPUNIT_TEST(testW000); CPPUNIT_TEST(testPropertyInterface); CPPUNIT_TEST(testPropertyInterface2); CPPUNIT_TEST(testDeleteFrame); @@ -385,6 +389,38 @@ public: "http://example.com", 33), // URL f.render()); } + + void testParseOwnershipFrame() + { + ID3v2::OwnershipFrame f( + ByteVector("OWNE" // Frame ID + "\x00\x00\x00\x19" // Frame size + "\x00\x00" // Frame flags + "\x00" // Text encoding + "GBP1.99\x00" // Price paid + "20120905" // Date of purchase + "Beatport", 35)); // Seller + CPPUNIT_ASSERT_EQUAL(String("GBP1.99"), f.pricePaid()); + CPPUNIT_ASSERT_EQUAL(String("20120905"), f.datePurchased()); + CPPUNIT_ASSERT_EQUAL(String("Beatport"), f.seller()); + } + + void testRenderOwnershipFrame() + { + ID3v2::OwnershipFrame f; + f.setPricePaid("GBP1.99"); + f.setDatePurchased("20120905"); + f.setSeller("Beatport"); + CPPUNIT_ASSERT_EQUAL( + ByteVector("OWNE" // Frame ID + "\x00\x00\x00\x19" // Frame size + "\x00\x00" // Frame flags + "\x00" // Text encoding + "GBP1.99\x00" // Price paid + "20120905" // Date of purchase + "Beatport", 35), // URL + f.render()); + } void testItunes24FrameSize() { @@ -552,6 +588,16 @@ public: CPPUNIT_ASSERT_EQUAL(String(""), frame->description()); CPPUNIT_ASSERT_EQUAL(TagLib::uint(86414), frame->picture().size()); } + + void testW000() + { + MPEG::File f(TEST_FILE_PATH_C("w000.mp3"), false); + CPPUNIT_ASSERT(f.ID3v2Tag()->frameListMap().contains("W000")); + ID3v2::UrlLinkFrame *frame = + dynamic_cast(f.ID3v2Tag()->frameListMap()["W000"].front()); + CPPUNIT_ASSERT(frame); + CPPUNIT_ASSERT_EQUAL(String("lukas.lalinsky@example.com____"), frame->url()); + } void testPropertyInterface() { @@ -578,7 +624,7 @@ public: CPPUNIT_ASSERT_EQUAL(String("A COMMENT"), dict["COMMENT"].front()); CPPUNIT_ASSERT_EQUAL(1u, dict.unsupportedData().size()); - CPPUNIT_ASSERT_EQUAL(String("UFID"), dict.unsupportedData().front()); + CPPUNIT_ASSERT_EQUAL(String("UFID/supermihi@web.de"), dict.unsupportedData().front()); } void testPropertyInterface2() @@ -611,11 +657,23 @@ public: frame5->setText(tmclData); tag.addFrame(frame5); + ID3v2::UniqueFileIdentifierFrame *frame6 = new ID3v2::UniqueFileIdentifierFrame("http://musicbrainz.org", "152454b9-19ba-49f3-9fc9-8fc26545cf41"); + tag.addFrame(frame6); + + ID3v2::UniqueFileIdentifierFrame *frame7 = new ID3v2::UniqueFileIdentifierFrame("http://example.com", "123"); + tag.addFrame(frame7); + + ID3v2::UserTextIdentificationFrame *frame8 = new ID3v2::UserTextIdentificationFrame(); + frame8->setDescription("MusicBrainz Album Id"); + frame8->setText("95c454a5-d7e0-4d8f-9900-db04aca98ab3"); + tag.addFrame(frame8); + PropertyMap properties = tag.properties(); - CPPUNIT_ASSERT_EQUAL(2u, properties.unsupportedData().size()); + CPPUNIT_ASSERT_EQUAL(3u, properties.unsupportedData().size()); CPPUNIT_ASSERT(properties.unsupportedData().contains("TIPL")); CPPUNIT_ASSERT(properties.unsupportedData().contains("APIC")); + CPPUNIT_ASSERT(properties.unsupportedData().contains("UFID/http://example.com")); CPPUNIT_ASSERT(properties.contains("PERFORMER:VIOLIN")); CPPUNIT_ASSERT(properties.contains("PERFORMER:PIANO")); @@ -625,9 +683,17 @@ public: CPPUNIT_ASSERT(properties.contains("LYRICS")); CPPUNIT_ASSERT(properties.contains("LYRICS:TEST")); + CPPUNIT_ASSERT(properties.contains("MUSICBRAINZ_TRACKID")); + CPPUNIT_ASSERT_EQUAL(String("152454b9-19ba-49f3-9fc9-8fc26545cf41"), properties["MUSICBRAINZ_TRACKID"].front()); + + CPPUNIT_ASSERT(properties.contains("MUSICBRAINZ_ALBUMID")); + CPPUNIT_ASSERT_EQUAL(String("95c454a5-d7e0-4d8f-9900-db04aca98ab3"), properties["MUSICBRAINZ_ALBUMID"].front()); + tag.removeUnsupportedProperties(properties.unsupportedData()); CPPUNIT_ASSERT(tag.frameList("APIC").isEmpty()); CPPUNIT_ASSERT(tag.frameList("TIPL").isEmpty()); + CPPUNIT_ASSERT_EQUAL((ID3v2::UniqueFileIdentifierFrame *)0, ID3v2::UniqueFileIdentifierFrame::findByOwner(&tag, "http://example.com")); + CPPUNIT_ASSERT_EQUAL(frame6, ID3v2::UniqueFileIdentifierFrame::findByOwner(&tag, "http://musicbrainz.org")); } void testDeleteFrame() @@ -640,29 +706,30 @@ public: CPPUNIT_ASSERT_EQUAL(1u, t->frameList("TCON").size()); t->removeFrame(frame, true); f.save(MPEG::File::ID3v2); - + MPEG::File f2(newname.c_str()); t = f2.ID3v2Tag(); CPPUNIT_ASSERT(t->frameList("TCON").isEmpty()); } + void testSaveAndStripID3v1ShouldNotAddFrameFromID3v1ToId3v2() { ScopedFileCopy copy("xing", ".mp3"); string newname = copy.fileName(); - + { MPEG::File foo(newname.c_str()); foo.tag()->setArtist("Artist"); foo.save(MPEG::File::ID3v1 | MPEG::File::ID3v2); } - + { MPEG::File bar(newname.c_str()); bar.ID3v2Tag()->removeFrames("TPE1"); // Should strip ID3v1 here and not add old values to ID3v2 again bar.save(MPEG::File::ID3v2, true); } - + MPEG::File f(newname.c_str()); CPPUNIT_ASSERT(!f.ID3v2Tag()->frameListMap().contains("TPE1")); } diff --git a/tests/test_info.cpp b/tests/test_info.cpp new file mode 100644 index 00000000..f76fd67a --- /dev/null +++ b/tests/test_info.cpp @@ -0,0 +1,45 @@ +#include +#include +#include +#include +#include +#include +#include "utils.h" + +using namespace std; +using namespace TagLib; + +class TestInfoTag : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(TestInfoTag); + CPPUNIT_TEST(testTitle); + CPPUNIT_TEST(testNumericFields); + CPPUNIT_TEST_SUITE_END(); + +public: + void testTitle() + { + RIFF::Info::Tag tag; + + CPPUNIT_ASSERT_EQUAL(String(""), tag.title()); + tag.setTitle("Test title 1"); + CPPUNIT_ASSERT_EQUAL(String("Test title 1"), tag.title()); + } + + void testNumericFields() + { + RIFF::Info::Tag tag; + + CPPUNIT_ASSERT_EQUAL((uint)0, tag.track()); + tag.setTrack(1234); + CPPUNIT_ASSERT_EQUAL((uint)1234, tag.track()); + CPPUNIT_ASSERT_EQUAL(String("1234"), tag.fieldText("IPRT")); + + CPPUNIT_ASSERT_EQUAL((uint)0, tag.year()); + tag.setYear(1234); + CPPUNIT_ASSERT_EQUAL((uint)1234, tag.year()); + CPPUNIT_ASSERT_EQUAL(String("1234"), tag.fieldText("ICRD")); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestInfoTag); diff --git a/tests/test_it.cpp b/tests/test_it.cpp index 53d8ea0e..be5680ec 100644 --- a/tests/test_it.cpp +++ b/tests/test_it.cpp @@ -19,9 +19,9 @@ * MA 02110-1301 USA * ***************************************************************************/ -#include #include #include +#include #include "utils.h" using namespace std; diff --git a/tests/test_mod.cpp b/tests/test_mod.cpp index 05657c13..0a233c97 100644 --- a/tests/test_mod.cpp +++ b/tests/test_mod.cpp @@ -19,9 +19,9 @@ * MA 02110-1301 USA * ***************************************************************************/ -#include #include #include +#include #include "utils.h" using namespace std; diff --git a/tests/test_mp4.cpp b/tests/test_mp4.cpp index 14a4df8e..be7ad2c3 100644 --- a/tests/test_mp4.cpp +++ b/tests/test_mp4.cpp @@ -1,11 +1,12 @@ -#include #include #include #include #include #include +#include #include #include +#include #include "utils.h" using namespace std; @@ -25,6 +26,7 @@ class TestMP4 : public CppUnit::TestFixture CPPUNIT_TEST(testCovrRead); CPPUNIT_TEST(testCovrWrite); CPPUNIT_TEST(testCovrRead2); + CPPUNIT_TEST(testProperties); CPPUNIT_TEST_SUITE_END(); public: @@ -224,6 +226,55 @@ public: delete f; } + void testProperties() + { + MP4::File f(TEST_FILE_PATH_C("has-tags.m4a")); + + PropertyMap tags = f.properties(); + + CPPUNIT_ASSERT_EQUAL(StringList("Test Artist"), tags["ARTIST"]); + + tags["TRACKNUMBER"] = StringList("2/4"); + tags["DISCNUMBER"] = StringList("3/5"); + tags["BPM"] = StringList("123"); + tags["ARTIST"] = StringList("Foo Bar"); + tags["COMPILATION"] = StringList("1"); + f.setProperties(tags); + + tags = f.properties(); + + CPPUNIT_ASSERT(f.tag()->itemListMap().contains("trkn")); + CPPUNIT_ASSERT_EQUAL(2, f.tag()->itemListMap()["trkn"].toIntPair().first); + CPPUNIT_ASSERT_EQUAL(4, f.tag()->itemListMap()["trkn"].toIntPair().second); + CPPUNIT_ASSERT_EQUAL(StringList("2/4"), tags["TRACKNUMBER"]); + + CPPUNIT_ASSERT(f.tag()->itemListMap().contains("disk")); + CPPUNIT_ASSERT_EQUAL(3, f.tag()->itemListMap()["disk"].toIntPair().first); + CPPUNIT_ASSERT_EQUAL(5, f.tag()->itemListMap()["disk"].toIntPair().second); + CPPUNIT_ASSERT_EQUAL(StringList("3/5"), tags["DISCNUMBER"]); + + CPPUNIT_ASSERT(f.tag()->itemListMap().contains("tmpo")); + CPPUNIT_ASSERT_EQUAL(123, f.tag()->itemListMap()["tmpo"].toInt()); + CPPUNIT_ASSERT_EQUAL(StringList("123"), tags["BPM"]); + + CPPUNIT_ASSERT(f.tag()->itemListMap().contains("\251ART")); + CPPUNIT_ASSERT_EQUAL(StringList("Foo Bar"), f.tag()->itemListMap()["\251ART"].toStringList()); + CPPUNIT_ASSERT_EQUAL(StringList("Foo Bar"), tags["ARTIST"]); + + CPPUNIT_ASSERT(f.tag()->itemListMap().contains("cpil")); + CPPUNIT_ASSERT_EQUAL(true, f.tag()->itemListMap()["cpil"].toBool()); + CPPUNIT_ASSERT_EQUAL(StringList("1"), tags["COMPILATION"]); + + tags["COMPILATION"] = StringList("0"); + f.setProperties(tags); + + tags = f.properties(); + + CPPUNIT_ASSERT(f.tag()->itemListMap().contains("cpil")); + CPPUNIT_ASSERT_EQUAL(false, f.tag()->itemListMap()["cpil"].toBool()); + CPPUNIT_ASSERT_EQUAL(StringList("0"), tags["COMPILATION"]); + } + }; CPPUNIT_TEST_SUITE_REGISTRATION(TestMP4); diff --git a/tests/test_mp4coverart.cpp b/tests/test_mp4coverart.cpp index 75c6ad03..812a2e5d 100644 --- a/tests/test_mp4coverart.cpp +++ b/tests/test_mp4coverart.cpp @@ -1,8 +1,8 @@ -#include #include #include #include #include +#include #include "utils.h" using namespace std; diff --git a/tests/test_mp4item.cpp b/tests/test_mp4item.cpp index 68ba0c8d..37d3f719 100644 --- a/tests/test_mp4item.cpp +++ b/tests/test_mp4item.cpp @@ -1,9 +1,9 @@ -#include #include #include #include #include #include +#include #include "utils.h" using namespace std; diff --git a/tests/test_mpeg.cpp b/tests/test_mpeg.cpp index 973803f9..ad6acc49 100644 --- a/tests/test_mpeg.cpp +++ b/tests/test_mpeg.cpp @@ -1,8 +1,9 @@ -#include #include #include +#include #include #include +#include #include "utils.h" using namespace std; @@ -32,9 +33,12 @@ public: String xxx = ByteVector(254, 'X'); MPEG::File f(newname.c_str()); + CPPUNIT_ASSERT_EQUAL(false, f.hasID3v2Tag()); + f.tag()->setTitle(xxx); f.tag()->setArtist("Artist A"); f.save(MPEG::File::AllTags, true, 4); + CPPUNIT_ASSERT_EQUAL(true, f.hasID3v2Tag()); MPEG::File f2(newname.c_str()); CPPUNIT_ASSERT_EQUAL(TagLib::uint(4), f2.ID3v2Tag()->header()->majorVersion()); @@ -66,9 +70,12 @@ public: String xxx = ByteVector(254, 'X'); MPEG::File f(newname.c_str()); + CPPUNIT_ASSERT_EQUAL(false, f.hasID3v2Tag()); + f.tag()->setTitle(xxx); f.tag()->setArtist("Artist A"); f.save(MPEG::File::AllTags, true, 3); + CPPUNIT_ASSERT_EQUAL(true, f.hasID3v2Tag()); MPEG::File f2(newname.c_str()); CPPUNIT_ASSERT_EQUAL(TagLib::uint(3), f2.ID3v2Tag()->header()->majorVersion()); diff --git a/tests/test_ogg.cpp b/tests/test_ogg.cpp index fb7a262a..c89586aa 100644 --- a/tests/test_ogg.cpp +++ b/tests/test_ogg.cpp @@ -1,4 +1,3 @@ -#include #include #include #include @@ -8,6 +7,7 @@ #include #include #include +#include #include "utils.h" using namespace std; @@ -88,12 +88,12 @@ public: CPPUNIT_ASSERT_EQUAL(TagLib::uint(2), tags["UNUSUALTAG"].size()); CPPUNIT_ASSERT_EQUAL(String("usual value"), tags["UNUSUALTAG"][0]); CPPUNIT_ASSERT_EQUAL(String("another value"), tags["UNUSUALTAG"][1]); - CPPUNIT_ASSERT_EQUAL(String(L"öäüoΣø"), tags["UNICODETAG"][0]); + CPPUNIT_ASSERT_EQUAL(String("öäüoΣø", String::UTF8), tags["UNICODETAG"][0]); - tags["UNICODETAG"][0] = L"νεω ναλυε"; + tags["UNICODETAG"][0] = String("νεω ναλυε", String::UTF8); tags.erase("UNUSUALTAG"); f->tag()->setProperties(tags); - CPPUNIT_ASSERT_EQUAL(String(L"νεω ναλυε"), f->tag()->properties()["UNICODETAG"][0]); + CPPUNIT_ASSERT_EQUAL(String("νεω ναλυε", String::UTF8), f->tag()->properties()["UNICODETAG"][0]); CPPUNIT_ASSERT_EQUAL(false, f->tag()->properties().contains("UNUSUALTAG")); delete f; diff --git a/tests/test_oggflac.cpp b/tests/test_oggflac.cpp index c1837175..1cdb24b0 100644 --- a/tests/test_oggflac.cpp +++ b/tests/test_oggflac.cpp @@ -1,4 +1,3 @@ -#include #include #include #include @@ -6,6 +5,7 @@ #include #include #include +#include #include "utils.h" using namespace std; diff --git a/tests/test_opus.cpp b/tests/test_opus.cpp new file mode 100644 index 00000000..769d3985 --- /dev/null +++ b/tests/test_opus.cpp @@ -0,0 +1,61 @@ +#include +#include +#include +#include +#include +#include +#include "utils.h" + +using namespace std; +using namespace TagLib; + +class TestOpus : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(TestOpus); + CPPUNIT_TEST(testProperties); + CPPUNIT_TEST(testReadComments); + CPPUNIT_TEST(testWriteComments); + CPPUNIT_TEST_SUITE_END(); + +public: + + void testProperties() + { + Ogg::Opus::File f(TEST_FILE_PATH_C("correctness_gain_silent_output.opus")); + CPPUNIT_ASSERT_EQUAL(7, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(48000, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(48000, ((Ogg::Opus::Properties *)f.audioProperties())->inputSampleRate()); + } + + void testReadComments() + { + Ogg::Opus::File f(TEST_FILE_PATH_C("correctness_gain_silent_output.opus")); + CPPUNIT_ASSERT_EQUAL(StringList("Xiph.Org Opus testvectormaker"), f.tag()->fieldListMap()["ENCODER"]); + CPPUNIT_ASSERT(f.tag()->fieldListMap().contains("TESTDESCRIPTION")); + CPPUNIT_ASSERT(!f.tag()->fieldListMap().contains("ARTIST")); + CPPUNIT_ASSERT_EQUAL(String("libopus 0.9.11-66-g64c2dd7"), f.tag()->vendorID()); + } + + void testWriteComments() + { + ScopedFileCopy copy("correctness_gain_silent_output", ".opus"); + string filename = copy.fileName(); + + Ogg::Opus::File *f = new Ogg::Opus::File(filename.c_str()); + f->tag()->setArtist("Your Tester"); + f->save(); + delete f; + + f = new Ogg::Opus::File(filename.c_str()); + CPPUNIT_ASSERT_EQUAL(StringList("Xiph.Org Opus testvectormaker"), f->tag()->fieldListMap()["ENCODER"]); + CPPUNIT_ASSERT(f->tag()->fieldListMap().contains("TESTDESCRIPTION")); + CPPUNIT_ASSERT_EQUAL(StringList("Your Tester"), f->tag()->fieldListMap()["ARTIST"]); + CPPUNIT_ASSERT_EQUAL(String("libopus 0.9.11-66-g64c2dd7"), f->tag()->vendorID()); + delete f; + } + +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestOpus); diff --git a/tests/test_riff.cpp b/tests/test_riff.cpp index 43b2f8bb..8e120d06 100644 --- a/tests/test_riff.cpp +++ b/tests/test_riff.cpp @@ -1,9 +1,9 @@ -#include #include #include #include #include #include +#include #include "utils.h" using namespace std; diff --git a/tests/test_s3m.cpp b/tests/test_s3m.cpp index a6ecc084..24a4c6e4 100644 --- a/tests/test_s3m.cpp +++ b/tests/test_s3m.cpp @@ -19,8 +19,8 @@ * MA 02110-1301 USA * ***************************************************************************/ -#include #include +#include #include "utils.h" using namespace std; diff --git a/tests/test_string.cpp b/tests/test_string.cpp index b6ff972e..1e37d7a2 100644 --- a/tests/test_string.cpp +++ b/tests/test_string.cpp @@ -22,9 +22,9 @@ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include #include #include +#include using namespace std; using namespace TagLib; @@ -41,6 +41,7 @@ class TestString : public CppUnit::TestFixture CPPUNIT_TEST(testAppendCharDetach); CPPUNIT_TEST(testAppendStringDetach); CPPUNIT_TEST(testToInt); + CPPUNIT_TEST(testSubstr); CPPUNIT_TEST_SUITE_END(); public: @@ -193,6 +194,13 @@ public: CPPUNIT_ASSERT_EQUAL(String("-123aa").toInt(), -123); } + void testSubstr() + { + CPPUNIT_ASSERT_EQUAL(String("01"), String("0123456").substr(0, 2)); + CPPUNIT_ASSERT_EQUAL(String("12"), String("0123456").substr(1, 2)); + CPPUNIT_ASSERT_EQUAL(String("123456"), String("0123456").substr(1, 200)); + } + }; CPPUNIT_TEST_SUITE_REGISTRATION(TestString); diff --git a/tests/test_synchdata.cpp b/tests/test_synchdata.cpp index 1113ee43..2e3021ea 100644 --- a/tests/test_synchdata.cpp +++ b/tests/test_synchdata.cpp @@ -22,8 +22,8 @@ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include #include +#include using namespace std; using namespace TagLib; diff --git a/tests/test_xiphcomment.cpp b/tests/test_xiphcomment.cpp index a98aea61..6526229b 100644 --- a/tests/test_xiphcomment.cpp +++ b/tests/test_xiphcomment.cpp @@ -1,9 +1,9 @@ -#include #include #include #include #include #include +#include #include "utils.h" using namespace std; diff --git a/tests/test_xm.cpp b/tests/test_xm.cpp index 70b3967e..28086533 100644 --- a/tests/test_xm.cpp +++ b/tests/test_xm.cpp @@ -19,8 +19,8 @@ * MA 02110-1301 USA * ***************************************************************************/ -#include #include +#include #include "utils.h" using namespace std; diff --git a/tests/utils.h b/tests/utils.h index 39e15ce9..b69bfa50 100644 --- a/tests/utils.h +++ b/tests/utils.h @@ -7,6 +7,7 @@ #include #include #include +#include #endif #include #include