diff --git a/NEWS b/NEWS index b5a27901..1b3751bd 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,15 @@ +========================== + + * Added support for ID3v2 PCST and WFED frames. + * Added String::clear(). + * Better handling of duplicate ID3v2 tags in all kinds of files. + * Better handling of duplicate tags in WAV files. + * Fixed crash when calling File::properties() after strip(). + * Fixed possible file corruptions when saving ASF files. + * Marked ByteVector::null and ByteVector::isNull() deprecated. + * Marked String::null and ByteVector::isNull() deprecated. + * Many smaller bug fixes and performance improvements. + TagLib 1.10 (Nov 11, 2015) ========================== diff --git a/taglib/ape/apefile.cpp b/taglib/ape/apefile.cpp index d4df9cb9..232e0c2b 100644 --- a/taglib/ape/apefile.cpp +++ b/taglib/ape/apefile.cpp @@ -125,9 +125,10 @@ TagLib::Tag *APE::File::tag() const PropertyMap APE::File::setProperties(const PropertyMap &properties) { - if(d->hasID3v1) - d->tag.access(ApeID3v1Index, false)->setProperties(properties); - return d->tag.access(ApeAPEIndex, true)->setProperties(properties); + if(ID3v1Tag()) + ID3v1Tag()->setProperties(properties); + + return APETag(true)->setProperties(properties); } APE::AudioProperties *APE::File::audioProperties() const diff --git a/taglib/ape/apefile.h b/taglib/ape/apefile.h index 0fc7ff51..5620fc4f 100644 --- a/taglib/ape/apefile.h +++ b/taglib/ape/apefile.h @@ -120,7 +120,7 @@ namespace TagLib { * Creates an APEv2 tag if necessary. A potentially existing ID3v1 * tag will be updated as well. */ - PropertyMap setProperties(const PropertyMap &); + virtual PropertyMap setProperties(const PropertyMap &); /*! * Returns the APE::Properties for this file. If no audio properties diff --git a/taglib/asf/asffile.cpp b/taglib/asf/asffile.cpp index af975209..631244f9 100644 --- a/taglib/asf/asffile.cpp +++ b/taglib/asf/asffile.cpp @@ -50,7 +50,7 @@ public: class MetadataLibraryObject; FilePrivate(): - size(0), + headerSize(0), tag(0), properties(0), contentDescriptionObject(0), @@ -68,7 +68,7 @@ public: delete properties; } - unsigned long long size; + unsigned long long headerSize; ASF::Tag *tag; ASF::AudioProperties *properties; @@ -503,21 +503,6 @@ 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::AudioProperties *ASF::File::audioProperties() const { return d->properties; @@ -556,6 +541,10 @@ bool ASF::File::save() d->headerExtensionObject->objects.append(d->metadataLibraryObject); } + d->extendedContentDescriptionObject->attributeData.clear(); + d->metadataObject->attributeData.clear(); + d->metadataLibraryObject->attributeData.clear(); + const AttributeListMap allAttributes = d->tag->attributeListMap(); for(AttributeListMap::ConstIterator it = allAttributes.begin(); it != allAttributes.end(); ++it) { @@ -590,8 +579,15 @@ bool ASF::File::save() for(List::ConstIterator it = d->objects.begin(); it != d->objects.end(); ++it) { data.append((*it)->render(this)); } - data = headerGuid + ByteVector::fromUInt64LE(data.size() + 30) + ByteVector::fromUInt32LE(d->objects.size()) + ByteVector("\x01\x02", 2) + data; - insert(data, 0, static_cast(d->size)); + + seek(16); + writeBlock(ByteVector::fromUInt64LE(data.size() + 30)); + writeBlock(ByteVector::fromUInt32LE(d->objects.size())); + writeBlock(ByteVector("\x01\x02", 2)); + + insert(data, 30, static_cast(d->headerSize - 30)); + + d->headerSize = data.size() + 30; return true; } @@ -616,7 +612,7 @@ void ASF::File::read() d->properties = new ASF::AudioProperties(); bool ok; - d->size = readQWORD(this, &ok); + d->headerSize = readQWORD(this, &ok); if(!ok) { setValid(false); return; diff --git a/taglib/asf/asffile.h b/taglib/asf/asffile.h index 1722e86c..dc50b886 100644 --- a/taglib/asf/asffile.h +++ b/taglib/asf/asffile.h @@ -91,22 +91,6 @@ 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. */ @@ -116,9 +100,6 @@ namespace TagLib { * Save the file. * * This returns true if the save was successful. - * - * \warning In the current implementation, it's dangerous to call save() - * repeatedly. At worst it will corrupt the file. */ virtual bool save(); diff --git a/taglib/asf/asftag.cpp b/taglib/asf/asftag.cpp index b58871c0..5b61a024 100644 --- a/taglib/asf/asftag.cpp +++ b/taglib/asf/asftag.cpp @@ -253,18 +253,21 @@ namespace { "Acoustid/Id", "ACOUSTID_ID" }, { "Acoustid/Fingerprint", "ACOUSTID_FINGERPRINT" }, }; + const size_t keyTranslationSize = sizeof(keyTranslation) / sizeof(keyTranslation[0]); + + String translateKey(const String &key) + { + for(size_t i = 0; i < keyTranslationSize; ++i) { + if(key == keyTranslation[i][0]) + return keyTranslation[i][1]; + } + + return String(); + } } 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()) { @@ -282,8 +285,8 @@ PropertyMap ASF::Tag::properties() const ASF::AttributeListMap::ConstIterator it = d->attributeListMap.begin(); for(; it != d->attributeListMap.end(); ++it) { - if(keyMap.contains(it->first)) { - String key = keyMap[it->first]; + const String key = translateKey(it->first); + if(!key.isEmpty()) { AttributeList::ConstIterator it2 = it->second.begin(); for(; it2 != it->second.end(); ++it2) { if(key == "TRACKNUMBER") { diff --git a/taglib/dsf/dsffile.cpp b/taglib/dsf/dsffile.cpp index 81f570e3..179faf01 100644 --- a/taglib/dsf/dsffile.cpp +++ b/taglib/dsf/dsffile.cpp @@ -86,16 +86,6 @@ ID3v2::Tag *DSF::File::tag() const return d->tag; } -PropertyMap DSF::File::properties() const -{ - return d->tag->properties(); -} - -PropertyMap DSF::File::setProperties(const PropertyMap &properties) -{ - return d->tag->setProperties(properties); -} - DSF::AudioProperties *DSF::File::audioProperties() const { return d->properties; diff --git a/taglib/dsf/dsffile.h b/taglib/dsf/dsffile.h index 60c25174..1afa2c31 100644 --- a/taglib/dsf/dsffile.h +++ b/taglib/dsf/dsffile.h @@ -80,18 +80,6 @@ namespace TagLib { */ virtual ID3v2::Tag *tag() const; - /*! - * Implements the unified property interface -- export function. - * This method forwards to ID3v2::Tag::properties(). - */ - PropertyMap properties() const; - - /*! - * Implements the unified property interface -- import function. - * This method forwards to ID3v2::Tag::setProperties(). - */ - PropertyMap setProperties(const PropertyMap &); - /*! * Returns the DSF::AudioProperties for this file. If no audio properties * were read then this will return a null pointer. diff --git a/taglib/flac/flacfile.cpp b/taglib/flac/flacfile.cpp index 4578999e..faf1cc80 100644 --- a/taglib/flac/flacfile.cpp +++ b/taglib/flac/flacfile.cpp @@ -135,7 +135,7 @@ TagLib::Tag *FLAC::File::tag() const PropertyMap FLAC::File::setProperties(const PropertyMap &properties) { - return d->tag.access(FlacXiphIndex, true)->setProperties(properties); + return xiphComment(true)->setProperties(properties); } FLAC::AudioProperties *FLAC::File::audioProperties() const diff --git a/taglib/flac/flacfile.h b/taglib/flac/flacfile.h index 0372f502..002a3c5e 100644 --- a/taglib/flac/flacfile.h +++ b/taglib/flac/flacfile.h @@ -119,7 +119,7 @@ namespace TagLib { * Ignores any changes to ID3v1 or ID3v2 comments since they are not allowed * in the FLAC specification. */ - PropertyMap setProperties(const PropertyMap &); + virtual PropertyMap setProperties(const PropertyMap &); /*! * Returns the FLAC::Properties for this file. If no audio properties diff --git a/taglib/mp4/mp4properties.cpp b/taglib/mp4/mp4properties.cpp index c674b4fb..01a08376 100644 --- a/taglib/mp4/mp4properties.cpp +++ b/taglib/mp4/mp4properties.cpp @@ -207,7 +207,7 @@ MP4::AudioProperties::read(File *file, Atoms *atoms) length = data.toUInt32BE(24); } if(unit > 0 && length > 0) - d->length = static_cast(length * 1000.0 / unit); + d->length = static_cast(length * 1000.0 / unit + 0.5); MP4::Atom *atom = trak->find("mdia", "minf", "stbl", "stsd"); if(!atom) { diff --git a/taglib/mp4/mp4tag.cpp b/taglib/mp4/mp4tag.cpp index d0ec43d4..7d1dff6f 100644 --- a/taglib/mp4/mp4tag.cpp +++ b/taglib/mp4/mp4tag.cpp @@ -824,71 +824,76 @@ MP4::Tag::toString() const return desc.toString("\n"); } +namespace +{ + const char *keyTranslation[][2] = { + { "\251nam", "TITLE" }, + { "\251ART", "ARTIST" }, + { "\251alb", "ALBUM" }, + { "\251cmt", "COMMENT" }, + { "\251gen", "GENRE" }, + { "\251day", "DATE" }, + { "\251wrt", "COMPOSER" }, + { "\251grp", "GROUPING" }, + { "aART", "ALBUMARTIST" }, + { "trkn", "TRACKNUMBER" }, + { "disk", "DISCNUMBER" }, + { "cpil", "COMPILATION" }, + { "tmpo", "BPM" }, + { "cprt", "COPYRIGHT" }, + { "\251lyr", "LYRICS" }, + { "\251too", "ENCODEDBY" }, + { "soal", "ALBUMSORT" }, + { "soaa", "ALBUMARTISTSORT" }, + { "soar", "ARTISTSORT" }, + { "sonm", "TITLESORT" }, + { "soco", "COMPOSERSORT" }, + { "sosn", "SHOWSORT" }, + { "----:com.apple.iTunes:MusicBrainz Track Id", "MUSICBRAINZ_TRACKID" }, + { "----:com.apple.iTunes:MusicBrainz Artist Id", "MUSICBRAINZ_ARTISTID" }, + { "----:com.apple.iTunes:MusicBrainz Album Id", "MUSICBRAINZ_ALBUMID" }, + { "----:com.apple.iTunes:MusicBrainz Album Artist Id", "MUSICBRAINZ_ALBUMARTISTID" }, + { "----:com.apple.iTunes:MusicBrainz Release Group Id", "MUSICBRAINZ_RELEASEGROUPID" }, + { "----:com.apple.iTunes:MusicBrainz Work Id", "MUSICBRAINZ_WORKID" }, + { "----:com.apple.iTunes:ASIN", "ASIN" }, + { "----:com.apple.iTunes:LABEL", "LABEL" }, + { "----:com.apple.iTunes:LYRICIST", "LYRICIST" }, + { "----:com.apple.iTunes:CONDUCTOR", "CONDUCTOR" }, + { "----:com.apple.iTunes:REMIXER", "REMIXER" }, + { "----:com.apple.iTunes:ENGINEER", "ENGINEER" }, + { "----:com.apple.iTunes:PRODUCER", "PRODUCER" }, + { "----:com.apple.iTunes:DJMIXER", "DJMIXER" }, + { "----:com.apple.iTunes:MIXER", "MIXER" }, + { "----:com.apple.iTunes:SUBTITLE", "SUBTITLE" }, + { "----:com.apple.iTunes:DISCSUBTITLE", "DISCSUBTITLE" }, + { "----:com.apple.iTunes:MOOD", "MOOD" }, + { "----:com.apple.iTunes:ISRC", "ISRC" }, + { "----:com.apple.iTunes:CATALOGNUMBER", "CATALOGNUMBER" }, + { "----:com.apple.iTunes:BARCODE", "BARCODE" }, + { "----:com.apple.iTunes:SCRIPT", "SCRIPT" }, + { "----:com.apple.iTunes:LANGUAGE", "LANGUAGE" }, + { "----:com.apple.iTunes:LICENSE", "LICENSE" }, + { "----:com.apple.iTunes:MEDIA", "MEDIA" }, + }; + const size_t keyTranslationSize = sizeof(keyTranslation) / sizeof(keyTranslation[0]); -static const char *keyTranslation[][2] = { - { "\251nam", "TITLE" }, - { "\251ART", "ARTIST" }, - { "\251alb", "ALBUM" }, - { "\251cmt", "COMMENT" }, - { "\251gen", "GENRE" }, - { "\251day", "DATE" }, - { "\251wrt", "COMPOSER" }, - { "\251grp", "GROUPING" }, - { "aART", "ALBUMARTIST" }, - { "trkn", "TRACKNUMBER" }, - { "disk", "DISCNUMBER" }, - { "cpil", "COMPILATION" }, - { "tmpo", "BPM" }, - { "cprt", "COPYRIGHT" }, - { "\251lyr", "LYRICS" }, - { "\251too", "ENCODEDBY" }, - { "soal", "ALBUMSORT" }, - { "soaa", "ALBUMARTISTSORT" }, - { "soar", "ARTISTSORT" }, - { "sonm", "TITLESORT" }, - { "soco", "COMPOSERSORT" }, - { "sosn", "SHOWSORT" }, - { "----:com.apple.iTunes:MusicBrainz Track Id", "MUSICBRAINZ_TRACKID" }, - { "----:com.apple.iTunes:MusicBrainz Artist Id", "MUSICBRAINZ_ARTISTID" }, - { "----:com.apple.iTunes:MusicBrainz Album Id", "MUSICBRAINZ_ALBUMID" }, - { "----:com.apple.iTunes:MusicBrainz Album Artist Id", "MUSICBRAINZ_ALBUMARTISTID" }, - { "----:com.apple.iTunes:MusicBrainz Release Group Id", "MUSICBRAINZ_RELEASEGROUPID" }, - { "----:com.apple.iTunes:MusicBrainz Work Id", "MUSICBRAINZ_WORKID" }, - { "----:com.apple.iTunes:ASIN", "ASIN" }, - { "----:com.apple.iTunes:LABEL", "LABEL" }, - { "----:com.apple.iTunes:LYRICIST", "LYRICIST" }, - { "----:com.apple.iTunes:CONDUCTOR", "CONDUCTOR" }, - { "----:com.apple.iTunes:REMIXER", "REMIXER" }, - { "----:com.apple.iTunes:ENGINEER", "ENGINEER" }, - { "----:com.apple.iTunes:PRODUCER", "PRODUCER" }, - { "----:com.apple.iTunes:DJMIXER", "DJMIXER" }, - { "----:com.apple.iTunes:MIXER", "MIXER" }, - { "----:com.apple.iTunes:SUBTITLE", "SUBTITLE" }, - { "----:com.apple.iTunes:DISCSUBTITLE", "DISCSUBTITLE" }, - { "----:com.apple.iTunes:MOOD", "MOOD" }, - { "----:com.apple.iTunes:ISRC", "ISRC" }, - { "----:com.apple.iTunes:CATALOGNUMBER", "CATALOGNUMBER" }, - { "----:com.apple.iTunes:BARCODE", "BARCODE" }, - { "----:com.apple.iTunes:SCRIPT", "SCRIPT" }, - { "----:com.apple.iTunes:LANGUAGE", "LANGUAGE" }, - { "----:com.apple.iTunes:LICENSE", "LICENSE" }, - { "----:com.apple.iTunes:MEDIA", "MEDIA" }, -}; + String translateKey(const String &key) + { + for(size_t i = 0; i < keyTranslationSize; ++i) { + if(key == keyTranslation[i][0]) + return keyTranslation[i][1]; + } + + return String(); + } +} 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; for(MP4::ItemMap::ConstIterator it = d->items.begin(); it != d->items.end(); ++it) { - if(keyMap.contains(it->first)) { - String key = keyMap[it->first]; + const String key = translateKey(it->first); + if(!key.isEmpty()) { if(key == "TRACKNUMBER" || key == "DISCNUMBER") { MP4::Item::IntPair ip = it->second.toIntPair(); String value = String::number(ip.first); diff --git a/taglib/mpc/mpcfile.cpp b/taglib/mpc/mpcfile.cpp index dab10d69..e24d7083 100644 --- a/taglib/mpc/mpcfile.cpp +++ b/taglib/mpc/mpcfile.cpp @@ -116,9 +116,10 @@ TagLib::Tag *MPC::File::tag() const PropertyMap MPC::File::setProperties(const PropertyMap &properties) { - if(d->hasID3v1) - d->tag.access(MPCID3v1Index, false)->setProperties(properties); - return d->tag.access(MPCAPEIndex, true)->setProperties(properties); + if(ID3v1Tag()) + ID3v1Tag()->setProperties(properties); + + return APETag(true)->setProperties(properties); } MPC::AudioProperties *MPC::File::audioProperties() const diff --git a/taglib/mpc/mpcfile.h b/taglib/mpc/mpcfile.h index d80452b1..9cdaef1e 100644 --- a/taglib/mpc/mpcfile.h +++ b/taglib/mpc/mpcfile.h @@ -120,7 +120,7 @@ namespace TagLib { * Affects only the APEv2 tag which will be created if necessary. * If an ID3v1 tag exists, it will be updated as well. */ - PropertyMap setProperties(const PropertyMap &); + virtual PropertyMap setProperties(const PropertyMap &); /*! * Returns the MPC::Properties for this file. If no audio properties diff --git a/taglib/mpeg/id3v1/id3v1genres.cpp b/taglib/mpeg/id3v1/id3v1genres.cpp index 074c8bff..def91910 100644 --- a/taglib/mpeg/id3v1/id3v1genres.cpp +++ b/taglib/mpeg/id3v1/id3v1genres.cpp @@ -27,237 +27,239 @@ using namespace TagLib; -namespace TagLib { - namespace ID3v1 { - - static const int genresSize = 192; - static const String genres[] = { - "Blues", - "Classic Rock", - "Country", - "Dance", - "Disco", - "Funk", - "Grunge", - "Hip-Hop", - "Jazz", - "Metal", - "New Age", - "Oldies", - "Other", - "Pop", - "R&B", - "Rap", - "Reggae", - "Rock", - "Techno", - "Industrial", - "Alternative", - "Ska", - "Death Metal", - "Pranks", - "Soundtrack", - "Euro-Techno", - "Ambient", - "Trip-Hop", - "Vocal", - "Jazz+Funk", - "Fusion", - "Trance", - "Classical", - "Instrumental", - "Acid", - "House", - "Game", - "Sound Clip", - "Gospel", - "Noise", - "Alternative Rock", - "Bass", - "Soul", - "Punk", - "Space", - "Meditative", - "Instrumental Pop", - "Instrumental Rock", - "Ethnic", - "Gothic", - "Darkwave", - "Techno-Industrial", - "Electronic", - "Pop-Folk", - "Eurodance", - "Dream", - "Southern Rock", - "Comedy", - "Cult", - "Gangsta", - "Top 40", - "Christian Rap", - "Pop/Funk", - "Jungle", - "Native American", - "Cabaret", - "New Wave", - "Psychedelic", - "Rave", - "Showtunes", - "Trailer", - "Lo-Fi", - "Tribal", - "Acid Punk", - "Acid Jazz", - "Polka", - "Retro", - "Musical", - "Rock & Roll", - "Hard Rock", - "Folk", - "Folk/Rock", - "National Folk", - "Swing", - "Fusion", - "Bebob", - "Latin", - "Revival", - "Celtic", - "Bluegrass", - "Avantgarde", - "Gothic Rock", - "Progressive Rock", - "Psychedelic Rock", - "Symphonic Rock", - "Slow Rock", - "Big Band", - "Chorus", - "Easy Listening", - "Acoustic", - "Humour", - "Speech", - "Chanson", - "Opera", - "Chamber Music", - "Sonata", - "Symphony", - "Booty Bass", - "Primus", - "Porn Groove", - "Satire", - "Slow Jam", - "Club", - "Tango", - "Samba", - "Folklore", - "Ballad", - "Power Ballad", - "Rhythmic Soul", - "Freestyle", - "Duet", - "Punk Rock", - "Drum Solo", - "A Cappella", - "Euro-House", - "Dance Hall", - "Goa", - "Drum & Bass", - "Club-House", - "Hardcore", - "Terror", - "Indie", - "BritPop", - "Negerpunk", - "Polsk Punk", - "Beat", - "Christian Gangsta Rap", - "Heavy Metal", - "Black Metal", - "Crossover", - "Contemporary Christian", - "Christian Rock", - "Merengue", - "Salsa", - "Thrash Metal", - "Anime", - "Jpop", - "Synthpop", - "Abstract", - "Art Rock", - "Baroque", - "Bhangra", - "Big Beat", - "Breakbeat", - "Chillout", - "Downtempo", - "Dub", - "EBM", - "Eclectic", - "Electro", - "Electroclash", - "Emo", - "Experimental", - "Garage", - "Global", - "IDM", - "Illbient", - "Industro-Goth", - "Jam Band", - "Krautrock", - "Leftfield", - "Lounge", - "Math Rock", - "New Romantic", - "Nu-Breakz", - "Post-Punk", - "Post-Rock", - "Psytrance", - "Shoegaze", - "Space Rock", - "Trop Rock", - "World Music", - "Neoclassical", - "Audiobook", - "Audio Theatre", - "Neue Deutsche Welle", - "Podcast", - "Indie Rock", - "G-Funk", - "Dubstep", - "Garage Rock", - "Psybient" - }; - } +namespace +{ + const wchar *genres[] = { + L"Blues", + L"Classic Rock", + L"Country", + L"Dance", + L"Disco", + L"Funk", + L"Grunge", + L"Hip-Hop", + L"Jazz", + L"Metal", + L"New Age", + L"Oldies", + L"Other", + L"Pop", + L"R&B", + L"Rap", + L"Reggae", + L"Rock", + L"Techno", + L"Industrial", + L"Alternative", + L"Ska", + L"Death Metal", + L"Pranks", + L"Soundtrack", + L"Euro-Techno", + L"Ambient", + L"Trip-Hop", + L"Vocal", + L"Jazz+Funk", + L"Fusion", + L"Trance", + L"Classical", + L"Instrumental", + L"Acid", + L"House", + L"Game", + L"Sound Clip", + L"Gospel", + L"Noise", + L"Alternative Rock", + L"Bass", + L"Soul", + L"Punk", + L"Space", + L"Meditative", + L"Instrumental Pop", + L"Instrumental Rock", + L"Ethnic", + L"Gothic", + L"Darkwave", + L"Techno-Industrial", + L"Electronic", + L"Pop-Folk", + L"Eurodance", + L"Dream", + L"Southern Rock", + L"Comedy", + L"Cult", + L"Gangsta", + L"Top 40", + L"Christian Rap", + L"Pop/Funk", + L"Jungle", + L"Native American", + L"Cabaret", + L"New Wave", + L"Psychedelic", + L"Rave", + L"Showtunes", + L"Trailer", + L"Lo-Fi", + L"Tribal", + L"Acid Punk", + L"Acid Jazz", + L"Polka", + L"Retro", + L"Musical", + L"Rock & Roll", + L"Hard Rock", + L"Folk", + L"Folk/Rock", + L"National Folk", + L"Swing", + L"Fusion", + L"Bebob", + L"Latin", + L"Revival", + L"Celtic", + L"Bluegrass", + L"Avantgarde", + L"Gothic Rock", + L"Progressive Rock", + L"Psychedelic Rock", + L"Symphonic Rock", + L"Slow Rock", + L"Big Band", + L"Chorus", + L"Easy Listening", + L"Acoustic", + L"Humour", + L"Speech", + L"Chanson", + L"Opera", + L"Chamber Music", + L"Sonata", + L"Symphony", + L"Booty Bass", + L"Primus", + L"Porn Groove", + L"Satire", + L"Slow Jam", + L"Club", + L"Tango", + L"Samba", + L"Folklore", + L"Ballad", + L"Power Ballad", + L"Rhythmic Soul", + L"Freestyle", + L"Duet", + L"Punk Rock", + L"Drum Solo", + L"A Cappella", + L"Euro-House", + L"Dance Hall", + L"Goa", + L"Drum & Bass", + L"Club-House", + L"Hardcore", + L"Terror", + L"Indie", + L"BritPop", + L"Negerpunk", + L"Polsk Punk", + L"Beat", + L"Christian Gangsta Rap", + L"Heavy Metal", + L"Black Metal", + L"Crossover", + L"Contemporary Christian", + L"Christian Rock", + L"Merengue", + L"Salsa", + L"Thrash Metal", + L"Anime", + L"Jpop", + L"Synthpop", + L"Abstract", + L"Art Rock", + L"Baroque", + L"Bhangra", + L"Big Beat", + L"Breakbeat", + L"Chillout", + L"Downtempo", + L"Dub", + L"EBM", + L"Eclectic", + L"Electro", + L"Electroclash", + L"Emo", + L"Experimental", + L"Garage", + L"Global", + L"IDM", + L"Illbient", + L"Industro-Goth", + L"Jam Band", + L"Krautrock", + L"Leftfield", + L"Lounge", + L"Math Rock", + L"New Romantic", + L"Nu-Breakz", + L"Post-Punk", + L"Post-Rock", + L"Psytrance", + L"Shoegaze", + L"Space Rock", + L"Trop Rock", + L"World Music", + L"Neoclassical", + L"Audiobook", + L"Audio Theatre", + L"Neue Deutsche Welle", + L"Podcast", + L"Indie Rock", + L"G-Funk", + L"Dubstep", + L"Garage Rock", + L"Psybient" + }; + const int genresSize = sizeof(genres) / sizeof(genres[0]); } StringList ID3v1::genreList() { - static StringList l; - if(l.isEmpty()) { - for(int i = 0; i < genresSize; i++) - l.append(genres[i]); + StringList l; + for(int i = 0; i < genresSize; i++) { + l.append(genres[i]); } + return l; } ID3v1::GenreMap ID3v1::genreMap() { - static GenreMap m; - if(m.isEmpty()) { - for(int i = 0; i < genresSize; i++) - m.insert(genres[i], i); + GenreMap m; + for(int i = 0; i < genresSize; i++) { + m.insert(genres[i], i); } + return m; } String ID3v1::genre(int i) { if(i >= 0 && i < genresSize) - return genres[i] + String::null; // always make a copy - return String::null; + return String(genres[i]); // always make a copy + else + return String(); } int ID3v1::genreIndex(const String &name) { - if(genreMap().contains(name)) - return genreMap()[name]; + for(int i = 0; i < genresSize; ++i) { + if(name == genres[i]) + return i; + } + return 255; } diff --git a/taglib/mpeg/id3v2/id3v2frame.cpp b/taglib/mpeg/id3v2/id3v2frame.cpp index 9b7cfb19..a30501a5 100644 --- a/taglib/mpeg/id3v2/id3v2frame.cpp +++ b/taglib/mpeg/id3v2/id3v2frame.cpp @@ -358,98 +358,101 @@ String::Type Frame::checkTextEncoding(const StringList &fields, String::Type enc return checkEncoding(fields, encoding, header()->version()); } -static const size_t frameTranslationSize = 51; -static const char *frameTranslation[][2] = { - // Text information frames - { "TALB", "ALBUM"}, - { "TBPM", "BPM" }, - { "TCOM", "COMPOSER" }, - { "TCON", "GENRE" }, - { "TCOP", "COPYRIGHT" }, - { "TDEN", "ENCODINGTIME" }, - { "TDLY", "PLAYLISTDELAY" }, - { "TDOR", "ORIGINALDATE" }, - { "TDRC", "DATE" }, - // { "TRDA", "DATE" }, // id3 v2.3, replaced by TDRC in v2.4 - // { "TDAT", "DATE" }, // id3 v2.3, replaced by TDRC in v2.4 - // { "TYER", "DATE" }, // id3 v2.3, replaced by TDRC in v2.4 - // { "TIME", "DATE" }, // id3 v2.3, replaced by TDRC in v2.4 - { "TDRL", "RELEASEDATE" }, - { "TDTG", "TAGGINGDATE" }, - { "TENC", "ENCODEDBY" }, - { "TEXT", "LYRICIST" }, - { "TFLT", "FILETYPE" }, - //{ "TIPL", "INVOLVEDPEOPLE" }, handled separately - { "TIT1", "CONTENTGROUP" }, - { "TIT2", "TITLE"}, - { "TIT3", "SUBTITLE" }, - { "TKEY", "INITIALKEY" }, - { "TLAN", "LANGUAGE" }, - { "TLEN", "LENGTH" }, - //{ "TMCL", "MUSICIANCREDITS" }, handled separately - { "TMED", "MEDIA" }, - { "TMOO", "MOOD" }, - { "TOAL", "ORIGINALALBUM" }, - { "TOFN", "ORIGINALFILENAME" }, - { "TOLY", "ORIGINALLYRICIST" }, - { "TOPE", "ORIGINALARTIST" }, - { "TOWN", "OWNER" }, - { "TPE1", "ARTIST"}, - { "TPE2", "ALBUMARTIST" }, // id3's spec says 'PERFORMER', but most programs use 'ALBUMARTIST' - { "TPE3", "CONDUCTOR" }, - { "TPE4", "REMIXER" }, // could also be ARRANGER - { "TPOS", "DISCNUMBER" }, - { "TPRO", "PRODUCEDNOTICE" }, - { "TPUB", "LABEL" }, - { "TRCK", "TRACKNUMBER" }, - { "TRSN", "RADIOSTATION" }, - { "TRSO", "RADIOSTATIONOWNER" }, - { "TSOA", "ALBUMSORT" }, - { "TSOP", "ARTISTSORT" }, - { "TSOT", "TITLESORT" }, - { "TSO2", "ALBUMARTISTSORT" }, // non-standard, used by iTunes - { "TSRC", "ISRC" }, - { "TSSE", "ENCODING" }, - // URL frames - { "WCOP", "COPYRIGHTURL" }, - { "WOAF", "FILEWEBPAGE" }, - { "WOAR", "ARTISTWEBPAGE" }, - { "WOAS", "AUDIOSOURCEWEBPAGE" }, - { "WORS", "RADIOSTATIONWEBPAGE" }, - { "WPAY", "PAYMENTWEBPAGE" }, - { "WPUB", "PUBLISHERWEBPAGE" }, - //{ "WXXX", "URL"}, handled specially - // Other frames - { "COMM", "COMMENT" }, - //{ "USLT", "LYRICS" }, handled specially - // Apple iTunes proprietary frames - { "PCST", "PODCAST" }, - { "TCAT", "PODCASTCATEGORY" }, - { "TDES", "PODCASTDESC" }, - { "TGID", "PODCASTID" }, - { "WFED", "PODCASTURL" }, -}; +namespace +{ + const char *frameTranslation[][2] = { + // Text information frames + { "TALB", "ALBUM"}, + { "TBPM", "BPM" }, + { "TCOM", "COMPOSER" }, + { "TCON", "GENRE" }, + { "TCOP", "COPYRIGHT" }, + { "TDEN", "ENCODINGTIME" }, + { "TDLY", "PLAYLISTDELAY" }, + { "TDOR", "ORIGINALDATE" }, + { "TDRC", "DATE" }, + // { "TRDA", "DATE" }, // id3 v2.3, replaced by TDRC in v2.4 + // { "TDAT", "DATE" }, // id3 v2.3, replaced by TDRC in v2.4 + // { "TYER", "DATE" }, // id3 v2.3, replaced by TDRC in v2.4 + // { "TIME", "DATE" }, // id3 v2.3, replaced by TDRC in v2.4 + { "TDRL", "RELEASEDATE" }, + { "TDTG", "TAGGINGDATE" }, + { "TENC", "ENCODEDBY" }, + { "TEXT", "LYRICIST" }, + { "TFLT", "FILETYPE" }, + //{ "TIPL", "INVOLVEDPEOPLE" }, handled separately + { "TIT1", "CONTENTGROUP" }, + { "TIT2", "TITLE"}, + { "TIT3", "SUBTITLE" }, + { "TKEY", "INITIALKEY" }, + { "TLAN", "LANGUAGE" }, + { "TLEN", "LENGTH" }, + //{ "TMCL", "MUSICIANCREDITS" }, handled separately + { "TMED", "MEDIA" }, + { "TMOO", "MOOD" }, + { "TOAL", "ORIGINALALBUM" }, + { "TOFN", "ORIGINALFILENAME" }, + { "TOLY", "ORIGINALLYRICIST" }, + { "TOPE", "ORIGINALARTIST" }, + { "TOWN", "OWNER" }, + { "TPE1", "ARTIST"}, + { "TPE2", "ALBUMARTIST" }, // id3's spec says 'PERFORMER', but most programs use 'ALBUMARTIST' + { "TPE3", "CONDUCTOR" }, + { "TPE4", "REMIXER" }, // could also be ARRANGER + { "TPOS", "DISCNUMBER" }, + { "TPRO", "PRODUCEDNOTICE" }, + { "TPUB", "LABEL" }, + { "TRCK", "TRACKNUMBER" }, + { "TRSN", "RADIOSTATION" }, + { "TRSO", "RADIOSTATIONOWNER" }, + { "TSOA", "ALBUMSORT" }, + { "TSOP", "ARTISTSORT" }, + { "TSOT", "TITLESORT" }, + { "TSO2", "ALBUMARTISTSORT" }, // non-standard, used by iTunes + { "TSRC", "ISRC" }, + { "TSSE", "ENCODING" }, + // URL frames + { "WCOP", "COPYRIGHTURL" }, + { "WOAF", "FILEWEBPAGE" }, + { "WOAR", "ARTISTWEBPAGE" }, + { "WOAS", "AUDIOSOURCEWEBPAGE" }, + { "WORS", "RADIOSTATIONWEBPAGE" }, + { "WPAY", "PAYMENTWEBPAGE" }, + { "WPUB", "PUBLISHERWEBPAGE" }, + //{ "WXXX", "URL"}, handled specially + // Other frames + { "COMM", "COMMENT" }, + //{ "USLT", "LYRICS" }, handled specially + // Apple iTunes proprietary frames + { "PCST", "PODCAST" }, + { "TCAT", "PODCASTCATEGORY" }, + { "TDES", "PODCASTDESC" }, + { "TGID", "PODCASTID" }, + { "WFED", "PODCASTURL" }, + }; + const size_t frameTranslationSize = sizeof(frameTranslation) / sizeof(frameTranslation[0]); -static const size_t txxxFrameTranslationSize = 8; -static const char *txxxFrameTranslation[][2] = { - { "MUSICBRAINZ ALBUM ID", "MUSICBRAINZ_ALBUMID" }, - { "MUSICBRAINZ ARTIST ID", "MUSICBRAINZ_ARTISTID" }, - { "MUSICBRAINZ ALBUM ARTIST ID", "MUSICBRAINZ_ALBUMARTISTID" }, - { "MUSICBRAINZ RELEASE GROUP ID", "MUSICBRAINZ_RELEASEGROUPID" }, - { "MUSICBRAINZ WORK ID", "MUSICBRAINZ_WORKID" }, - { "ACOUSTID ID", "ACOUSTID_ID" }, - { "ACOUSTID FINGERPRINT", "ACOUSTID_FINGERPRINT" }, - { "MUSICIP PUID", "MUSICIP_PUID" }, -}; + 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" }, + }; + const size_t txxxFrameTranslationSize = sizeof(txxxFrameTranslation) / sizeof(txxxFrameTranslation[0]); -// list of deprecated frames and their successors -static const size_t deprecatedFramesSize = 4; -static const char *deprecatedFrames[][2] = { - {"TRDA", "TDRC"}, // 2.3 -> 2.4 (http://en.wikipedia.org/wiki/ID3) - {"TDAT", "TDRC"}, // 2.3 -> 2.4 - {"TYER", "TDRC"}, // 2.3 -> 2.4 - {"TIME", "TDRC"}, // 2.3 -> 2.4 -}; + // list of deprecated frames and their successors + const char *deprecatedFrames[][2] = { + {"TRDA", "TDRC"}, // 2.3 -> 2.4 (http://en.wikipedia.org/wiki/ID3) + {"TDAT", "TDRC"}, // 2.3 -> 2.4 + {"TYER", "TDRC"}, // 2.3 -> 2.4 + {"TIME", "TDRC"}, // 2.3 -> 2.4 + }; + const size_t deprecatedFramesSize = sizeof(deprecatedFrames) / sizeof(deprecatedFrames[0]);; +} String Frame::frameIDToKey(const ByteVector &id) { diff --git a/taglib/mpeg/id3v2/id3v2tag.cpp b/taglib/mpeg/id3v2/id3v2tag.cpp index 651c8014..96ed8d02 100644 --- a/taglib/mpeg/id3v2/id3v2tag.cpp +++ b/taglib/mpeg/id3v2/id3v2tag.cpp @@ -24,22 +24,22 @@ ***************************************************************************/ #ifdef HAVE_CONFIG_H -#include "config.h" +# include "config.h" #endif #include -#include "tfile.h" +#include +#include +#include +#include #include "id3v2tag.h" #include "id3v2header.h" #include "id3v2extendedheader.h" #include "id3v2footer.h" #include "id3v2synchdata.h" -#include "tbytevector.h" #include "id3v1genres.h" -#include "tpropertymap.h" -#include "tdebug.h" #include "frames/textidentificationframe.h" #include "frames/commentsframe.h" @@ -562,7 +562,6 @@ void ID3v2::Tag::downgradeFrames(FrameList *frames, FrameList *newFrames) const } } - ByteVector ID3v2::Tag::render(int version) const { // We need to render the "tag data" first so that we have to correct size to @@ -664,18 +663,43 @@ void ID3v2::Tag::setLatin1StringHandler(const TagLib::StringHandler *handler) void ID3v2::Tag::read() { - if(d->file && d->file->isOpen()) { + if(!d->file) + return; - d->file->seek(d->tagOffset); - d->header.setData(d->file->readBlock(Header::size())); + if(!d->file->isOpen()) + return; - // if the tag size is 0, then this is an invalid tag (tags must contain at - // least one frame) + d->file->seek(d->tagOffset); + d->header.setData(d->file->readBlock(Header::size())); - if(d->header.tagSize() == 0) - return; + // If the tag size is 0, then this is an invalid tag (tags must contain at + // least one frame) + if(d->header.tagSize() != 0) parse(d->file->readBlock(d->header.tagSize())); + + // Look for duplicate ID3v2 tags and treat them as an extra blank of this one. + // It leads to overwriting them with zero when saving the tag. + + // This is a workaround for some faulty files that have duplicate ID3v2 tags. + // Unfortunately, TagLib itself may write such duplicate tags until v1.10. + + uint extraSize = 0; + + while(true) { + + d->file->seek(d->tagOffset + d->header.completeTagSize() + extraSize); + + const ByteVector data = d->file->readBlock(Header::size()); + if(data.size() < Header::size() || !data.startsWith(Header::fileIdentifier())) + break; + + extraSize += Header(data).completeTagSize(); + } + + if(extraSize != 0) { + debug("ID3v2::Tag::read() - Duplicate ID3v2 tags found."); + d->header.setTagSize(d->header.tagSize() + extraSize); } } diff --git a/taglib/mpeg/mpegfile.cpp b/taglib/mpeg/mpegfile.cpp index 785dce03..26a1226c 100644 --- a/taglib/mpeg/mpegfile.cpp +++ b/taglib/mpeg/mpegfile.cpp @@ -149,10 +149,12 @@ TagLib::Tag *MPEG::File::tag() const PropertyMap MPEG::File::setProperties(const PropertyMap &properties) { - if(d->hasID3v1) - // update ID3v1 tag if it exists, but ignore the return value - d->tag.access(ID3v1Index, false)->setProperties(properties); - return d->tag.access(ID3v2Index, true)->setProperties(properties); + // update ID3v1 tag if it exists, but ignore the return value + + if(ID3v1Tag()) + ID3v1Tag()->setProperties(properties); + + return ID3v2Tag(true)->setProperties(properties); } MPEG::AudioProperties *MPEG::File::audioProperties() const @@ -409,24 +411,9 @@ offset_t MPEG::File::firstFrameOffset() { offset_t position = 0; - if(hasID3v2Tag()) { + if(hasID3v2Tag()) position = d->ID3v2Location + ID3v2Tag()->header()->completeTagSize(); - // Skip duplicate ID3v2 tags. - - // Workaround for some faulty files that have duplicate ID3v2 tags. - // Combination of EAC and LAME creates such files when configured incorrectly. - - offset_t location; - while((location = findID3v2(position)) >= 0) { - seek(location); - const ID3v2::Header header(readBlock(ID3v2::Header::size())); - position = location + header.completeTagSize(); - - debug("MPEG::File::firstFrameOffset() - Duplicate ID3v2 tag found."); - } - } - return nextFrameOffset(position); } @@ -467,7 +454,7 @@ void MPEG::File::read(bool readProperties) { // Look for an ID3v2 tag - d->ID3v2Location = findID3v2(0); + d->ID3v2Location = findID3v2(); if(d->ID3v2Location >= 0) { @@ -510,118 +497,40 @@ void MPEG::File::read(bool readProperties) ID3v1Tag(true); } -offset_t MPEG::File::findID3v2(offset_t offset) +offset_t MPEG::File::findID3v2() { - // This method is based on the contents of TagLib::File::find(), but because - // of some subtleties -- specifically the need to look for the bit pattern of - // an MPEG sync, it has been modified for use here. + if(!isValid()) + return -1; - if(isValid() && ID3v2::Header::fileIdentifier().size() <= bufferSize()) { + // An ID3v2 tag or MPEG frame is most likely be at the beginning of the file. - // The position in the file that the current buffer starts at. + const ByteVector headerID = ID3v2::Header::fileIdentifier(); - offset_t bufferOffset = 0; + seek(0); - // These variables are used to keep track of a partial match that happens at - // the end of a buffer. + const ByteVector data = readBlock(headerID.size()); + if(data.size() < headerID.size()) + return -1; - size_t previousPartialMatch = ByteVector::npos(); - bool previousPartialSynchMatch = false; + if(data == headerID) + return 0; - // Save the location of the current read pointer. We will restore the - // position using seek() before all returns. + if(firstSyncByte(data[0]) && secondSynchByte(data[1])) + return -1; - const offset_t originalPosition = tell(); + // Look for the entire file, if neither an MEPG frame or ID3v2 tag was found + // at the beginning of the file. + // We don't care about the inefficiency of the code, since this is a seldom case. - // Start the search at the offset. + const offset_t tagOffset = find(headerID); + if(tagOffset < 0) + return -1; - seek(offset); + const offset_t frameOffset = firstFrameOffset(); + if(frameOffset < tagOffset) + return -1; - // This loop is the crux of the find method. There are three cases that we - // want to account for: - // (1) The previously searched buffer contained a partial match of the search - // pattern and we want to see if the next one starts with the remainder of - // that pattern. - // - // (2) The search pattern is wholly contained within the current buffer. - // - // (3) The current buffer ends with a partial match of the pattern. We will - // note this for use in the next iteration, where we will check for the rest - // of the pattern. - - while(true) - { - ByteVector buffer = readBlock(bufferSize()); - if(buffer.isEmpty()) - break; - - // (1) previous partial match - - if(previousPartialSynchMatch && secondSynchByte(buffer[0])) - return -1; - - if(previousPartialMatch != ByteVector::npos() && bufferSize() > previousPartialMatch) { - const size_t patternOffset = (bufferSize() - previousPartialMatch); - if(buffer.containsAt(ID3v2::Header::fileIdentifier(), 0, patternOffset)) { - seek(originalPosition); - return offset + bufferOffset - bufferSize() + previousPartialMatch; - } - } - - // (2) pattern contained in current buffer - - const size_t location = buffer.find(ID3v2::Header::fileIdentifier()); - if(location != ByteVector::npos()) { - seek(originalPosition); - return offset + bufferOffset + location; - } - - size_t firstSynchByte = buffer.find(char(uchar(255))); - - // Here we have to loop because there could be several of the first - // (11111111) byte, and we want to check all such instances until we find - // a full match (11111111 111) or hit the end of the buffer. - - while(firstSynchByte != ByteVector::npos()) { - - // if this *is not* at the end of the buffer - - if(firstSynchByte < buffer.size() - 1) { - if(secondSynchByte(buffer[firstSynchByte + 1])) { - // We've found the frame synch pattern. - seek(originalPosition); - return -1; - } - else { - - // We found 11111111 at the end of the current buffer indicating a - // partial match of the synch pattern. The find() below should - // return -1 and break out of the loop. - - previousPartialSynchMatch = true; - } - } - - // Check in the rest of the buffer. - - firstSynchByte = buffer.find(char(uchar(255)), firstSynchByte + 1); - } - - // (3) partial match - - previousPartialMatch = buffer.endsWithPartialMatch(ID3v2::Header::fileIdentifier()); - - bufferOffset += bufferSize(); - } - - // Since we hit the end of the file, reset the status before continuing. - - clear(); - - seek(originalPosition); - } - - return -1; + return tagOffset; } offset_t MPEG::File::findID3v1() diff --git a/taglib/mpeg/mpegfile.h b/taglib/mpeg/mpegfile.h index 57a64098..801f7027 100644 --- a/taglib/mpeg/mpegfile.h +++ b/taglib/mpeg/mpegfile.h @@ -144,7 +144,7 @@ namespace TagLib { * limitations of that format. * The returned PropertyMap refers to the ID3v2 tag only. */ - PropertyMap setProperties(const PropertyMap &); + virtual PropertyMap setProperties(const PropertyMap &); /*! * Returns the MPEG::Properties for this file. If no audio properties @@ -329,7 +329,7 @@ namespace TagLib { File &operator=(const File &); void read(bool readProperties); - offset_t findID3v2(offset_t offset); + offset_t findID3v2(); offset_t findID3v1(); void findAPE(); diff --git a/taglib/ogg/flac/oggflacfile.cpp b/taglib/ogg/flac/oggflacfile.cpp index b86f6241..bf0e8821 100644 --- a/taglib/ogg/flac/oggflacfile.cpp +++ b/taglib/ogg/flac/oggflacfile.cpp @@ -95,22 +95,11 @@ Ogg::XiphComment *Ogg::FLAC::File::tag() const return d->comment; } -PropertyMap Ogg::FLAC::File::properties() const -{ - return d->comment->properties(); -} - -PropertyMap Ogg::FLAC::File::setProperties(const PropertyMap &properties) -{ - return d->comment->setProperties(properties); -} - FLAC::AudioProperties *Ogg::FLAC::File::audioProperties() const { return d->properties; } - bool Ogg::FLAC::File::save() { d->xiphCommentData = d->comment->render(false); diff --git a/taglib/ogg/flac/oggflacfile.h b/taglib/ogg/flac/oggflacfile.h index 38fd51ec..69b1b9ec 100644 --- a/taglib/ogg/flac/oggflacfile.h +++ b/taglib/ogg/flac/oggflacfile.h @@ -110,20 +110,6 @@ namespace TagLib { */ virtual AudioProperties *audioProperties() const; - - /*! - * Implements the unified property interface -- export function. - * This forwards directly to XiphComment::properties(). - */ - PropertyMap properties() const; - - /*! - * Implements the unified tag dictionary interface -- import function. - * Like properties(), this is a forwarder to the file's XiphComment. - */ - PropertyMap setProperties(const PropertyMap &); - - /*! * Save the file. This will primarily save and update the XiphComment. * Returns true if the save is successful. diff --git a/taglib/ogg/opus/opusfile.cpp b/taglib/ogg/opus/opusfile.cpp index e4d14474..554d3bf7 100644 --- a/taglib/ogg/opus/opusfile.cpp +++ b/taglib/ogg/opus/opusfile.cpp @@ -83,16 +83,6 @@ Ogg::XiphComment *Opus::File::tag() const return d->comment; } -PropertyMap Opus::File::properties() const -{ - return d->comment->properties(); -} - -PropertyMap Opus::File::setProperties(const PropertyMap &properties) -{ - return d->comment->setProperties(properties); -} - Opus::AudioProperties *Opus::File::audioProperties() const { return d->properties; diff --git a/taglib/ogg/opus/opusfile.h b/taglib/ogg/opus/opusfile.h index 5aa7a111..4384fea5 100644 --- a/taglib/ogg/opus/opusfile.h +++ b/taglib/ogg/opus/opusfile.h @@ -88,18 +88,6 @@ namespace TagLib { */ virtual Ogg::XiphComment *tag() const; - /*! - * Implements the unified property interface -- export function. - * This forwards directly to XiphComment::properties(). - */ - PropertyMap properties() const; - - /*! - * Implements the unified tag dictionary interface -- import function. - * Like properties(), this is a forwarder to the file's XiphComment. - */ - PropertyMap setProperties(const PropertyMap &); - /*! * Returns the Opus::Properties for this file. If no audio properties * were read then this will return a null pointer. diff --git a/taglib/ogg/speex/speexfile.cpp b/taglib/ogg/speex/speexfile.cpp index 0f9ef335..b0572282 100644 --- a/taglib/ogg/speex/speexfile.cpp +++ b/taglib/ogg/speex/speexfile.cpp @@ -83,16 +83,6 @@ Ogg::XiphComment *Speex::File::tag() const return d->comment; } -PropertyMap Speex::File::properties() const -{ - return d->comment->properties(); -} - -PropertyMap Speex::File::setProperties(const PropertyMap &properties) -{ - return d->comment->setProperties(properties); -} - Speex::AudioProperties *Speex::File::audioProperties() const { return d->properties; diff --git a/taglib/ogg/speex/speexfile.h b/taglib/ogg/speex/speexfile.h index ab3b0d54..193e5d6f 100644 --- a/taglib/ogg/speex/speexfile.h +++ b/taglib/ogg/speex/speexfile.h @@ -88,18 +88,6 @@ namespace TagLib { */ virtual Ogg::XiphComment *tag() const; - /*! - * Implements the unified property interface -- export function. - * This forwards directly to XiphComment::properties(). - */ - PropertyMap properties() const; - - /*! - * Implements the unified tag dictionary interface -- import function. - * Like properties(), this is a forwarder to the file's XiphComment. - */ - PropertyMap setProperties(const PropertyMap &); - /*! * Returns the Speex::Properties for this file. If no audio properties * were read then this will return a null pointer. diff --git a/taglib/ogg/vorbis/vorbisfile.cpp b/taglib/ogg/vorbis/vorbisfile.cpp index 9fcf72a1..d1b822dd 100644 --- a/taglib/ogg/vorbis/vorbisfile.cpp +++ b/taglib/ogg/vorbis/vorbisfile.cpp @@ -89,16 +89,6 @@ Ogg::XiphComment *Ogg::Vorbis::File::tag() const return d->comment; } -PropertyMap Ogg::Vorbis::File::properties() const -{ - return d->comment->properties(); -} - -PropertyMap Ogg::Vorbis::File::setProperties(const PropertyMap &properties) -{ - return d->comment->setProperties(properties); -} - Ogg::Vorbis::AudioProperties *Ogg::Vorbis::File::audioProperties() const { return d->properties; diff --git a/taglib/ogg/vorbis/vorbisfile.h b/taglib/ogg/vorbis/vorbisfile.h index e05ebe35..cd130258 100644 --- a/taglib/ogg/vorbis/vorbisfile.h +++ b/taglib/ogg/vorbis/vorbisfile.h @@ -86,19 +86,6 @@ namespace TagLib { */ virtual Ogg::XiphComment *tag() const; - - /*! - * Implements the unified property interface -- export function. - * This forwards directly to XiphComment::properties(). - */ - PropertyMap properties() const; - - /*! - * Implements the unified tag dictionary interface -- import function. - * Like properties(), this is a forwarder to the file's XiphComment. - */ - PropertyMap setProperties(const PropertyMap &); - /*! * Returns the Vorbis::Properties for this file. If no audio properties * were read then this will return a null pointer. diff --git a/taglib/riff/aiff/aifffile.cpp b/taglib/riff/aiff/aifffile.cpp index 831c2193..165618e9 100644 --- a/taglib/riff/aiff/aifffile.cpp +++ b/taglib/riff/aiff/aifffile.cpp @@ -85,21 +85,6 @@ ID3v2::Tag *RIFF::AIFF::File::tag() const return d->tag; } -PropertyMap RIFF::AIFF::File::properties() const -{ - return d->tag->properties(); -} - -void RIFF::AIFF::File::removeUnsupportedProperties(const StringList &unsupported) -{ - d->tag->removeUnsupportedProperties(unsupported); -} - -PropertyMap RIFF::AIFF::File::setProperties(const PropertyMap &properties) -{ - return d->tag->setProperties(properties); -} - RIFF::AIFF::AudioProperties *RIFF::AIFF::File::audioProperties() const { return d->properties; diff --git a/taglib/riff/aiff/aifffile.h b/taglib/riff/aiff/aifffile.h index 5578de60..88bcc413 100644 --- a/taglib/riff/aiff/aifffile.h +++ b/taglib/riff/aiff/aifffile.h @@ -94,20 +94,6 @@ namespace TagLib { */ virtual ID3v2::Tag *tag() const; - /*! - * Implements the unified property interface -- export function. - * This method forwards to ID3v2::Tag::properties(). - */ - PropertyMap properties() const; - - void removeUnsupportedProperties(const StringList &properties); - - /*! - * Implements the unified property interface -- import function. - * This method forwards to ID3v2::Tag::setProperties(). - */ - PropertyMap setProperties(const PropertyMap &); - /*! * Returns the AIFF::Properties for this file. If no audio properties * were read then this will return a null pointer. diff --git a/taglib/riff/wav/wavfile.cpp b/taglib/riff/wav/wavfile.cpp index b446fe23..4cf61bfd 100644 --- a/taglib/riff/wav/wavfile.cpp +++ b/taglib/riff/wav/wavfile.cpp @@ -45,11 +45,8 @@ class RIFF::WAV::File::FilePrivate public: FilePrivate() : properties(0), - tagChunkID("ID3 "), hasID3v2(false), - hasInfo(false) - { - } + hasInfo(false) {} ~FilePrivate() { @@ -57,9 +54,6 @@ public: } AudioProperties *properties; - - ByteVector tagChunkID; - DoubleTagUnion tag; bool hasID3v2; @@ -106,19 +100,21 @@ RIFF::Info::Tag *RIFF::WAV::File::InfoTag() const return d->tag.access(InfoIndex, false); } -PropertyMap RIFF::WAV::File::properties() const +void RIFF::WAV::File::strip(TagTypes tags) { - return tag()->properties(); -} + removeTagChunks(tags); -void RIFF::WAV::File::removeUnsupportedProperties(const StringList &unsupported) -{ - tag()->removeUnsupportedProperties(unsupported); + if(tags & ID3v2) + d->tag.set(ID3v2Index, new ID3v2::Tag()); + + if(tags & Info) + d->tag.set(InfoIndex, new RIFF::Info::Tag()); } PropertyMap RIFF::WAV::File::setProperties(const PropertyMap &properties) { - return tag()->setProperties(properties); + InfoTag()->setProperties(properties); + return ID3v2Tag()->setProperties(properties); } RIFF::WAV::AudioProperties *RIFF::WAV::File::audioProperties() const @@ -146,28 +142,20 @@ bool RIFF::WAV::File::save(TagTypes tags, bool stripOthers, int id3v2Version) if(stripOthers) strip(static_cast(AllTags & ~tags)); - const ID3v2::Tag *id3v2tag = d->tag.access(ID3v2Index, false); if(tags & ID3v2) { - if(d->hasID3v2) { - removeChunk(d->tagChunkID); - d->hasID3v2 = false; - } + removeTagChunks(ID3v2); - if(!id3v2tag->isEmpty()) { - setChunkData(d->tagChunkID, id3v2tag->render(id3v2Version)); + if(ID3v2Tag() && !ID3v2Tag()->isEmpty()) { + setChunkData("ID3 ", ID3v2Tag()->render(id3v2Version)); d->hasID3v2 = true; } } - const Info::Tag *infotag = d->tag.access(InfoIndex, false); if(tags & Info) { - if(d->hasInfo) { - removeChunk(findInfoTagChunk()); - d->hasInfo = false; - } + removeTagChunks(Info); - if(!infotag->isEmpty()) { - setChunkData("LIST", infotag->render(), true); + if(InfoTag() && !InfoTag()->isEmpty()) { + setChunkData("LIST", InfoTag()->render(), true); d->hasInfo = true; } } @@ -195,7 +183,6 @@ void RIFF::WAV::File::read(bool readProperties) const ByteVector name = chunkName(i); if(name == "ID3 " || name == "id3 ") { if(!d->tag[ID3v2Index]) { - d->tagChunkID = name; d->tag.set(ID3v2Index, new ID3v2::Tag(this, chunkOffset(i))); d->hasID3v2 = true; } @@ -227,29 +214,21 @@ void RIFF::WAV::File::read(bool readProperties) d->properties = new AudioProperties(this); } -void RIFF::WAV::File::strip(TagTypes tags) +void RIFF::WAV::File::removeTagChunks(TagTypes tags) { - if(tags & ID3v2) { - removeChunk(d->tagChunkID); + if((tags & ID3v2) && d->hasID3v2) { + removeChunk("ID3 "); + removeChunk("id3 "); + d->hasID3v2 = false; } - if(tags & Info){ - TagLib::uint chunkId = findInfoTagChunk(); - if(chunkId != TagLib::uint(-1)) { - removeChunk(chunkId); - d->hasInfo = false; + if((tags & Info) && d->hasInfo) { + for(int i = static_cast(chunkCount()) - 1; i >= 0; --i) { + if(chunkName(i) == "LIST" && chunkData(i).startsWith("INFO")) + removeChunk(i); } + + d->hasInfo = false; } } - -TagLib::uint RIFF::WAV::File::findInfoTagChunk() -{ - for(uint i = 0; i < chunkCount(); ++i) { - if(chunkName(i) == "LIST" && chunkData(i).startsWith("INFO")) { - return i; - } - } - - return TagLib::uint(-1); -} diff --git a/taglib/riff/wav/wavfile.h b/taglib/riff/wav/wavfile.h index 3fb73a57..70b7e9a8 100644 --- a/taglib/riff/wav/wavfile.h +++ b/taglib/riff/wav/wavfile.h @@ -124,18 +124,19 @@ namespace TagLib { Info::Tag *InfoTag() const; /*! - * Implements the unified property interface -- export function. - * This method forwards to ID3v2::Tag::properties(). + * This will strip the tags that match the OR-ed together TagTypes from the + * file. By default it strips all tags. It returns true if the tags are + * successfully stripped. + * + * \note This will update the file immediately. */ - PropertyMap properties() const; - - void removeUnsupportedProperties(const StringList &properties); + void strip(TagTypes tags = AllTags); /*! * Implements the unified property interface -- import function. * This method forwards to ID3v2::Tag::setProperties(). */ - PropertyMap setProperties(const PropertyMap &); + virtual PropertyMap setProperties(const PropertyMap &); /*! * Returns the WAV::Properties for this file. If no audio properties @@ -169,13 +170,7 @@ namespace TagLib { File &operator=(const File &); void read(bool readProperties); - - void strip(TagTypes tags); - - /*! - * Returns the index of the chunk that its name is "LIST" and list type is "INFO". - */ - uint findInfoTagChunk(); + void removeTagChunks(TagTypes tags); friend class AudioProperties; diff --git a/taglib/tagunion.cpp b/taglib/tagunion.cpp index b313e2c8..c47bc2c8 100644 --- a/taglib/tagunion.cpp +++ b/taglib/tagunion.cpp @@ -93,7 +93,7 @@ namespace TagLib PropertyMap TagUnion::properties() const { for(size_t i = 0; i < COUNT; ++i) { - if(d->tags[i]) + if(d->tags[i] && !d->tags[i]->isEmpty()) return d->tags[i]->properties(); } diff --git a/taglib/tagunion.h b/taglib/tagunion.h index 0b41355e..e1a7544b 100644 --- a/taglib/tagunion.h +++ b/taglib/tagunion.h @@ -58,9 +58,9 @@ namespace TagLib { void set(size_t index, Tag *tag); virtual PropertyMap properties() const; - + virtual void removeUnsupportedProperties(const StringList& properties); - + virtual String title() const; virtual String artist() const; virtual String album() const; @@ -92,8 +92,8 @@ namespace TagLib { TagUnionPrivate *d; }; - // If you add a new typedef here, add a corresponding explicit instantiation - // at the end of tagunion.cpp as well. + // If you add a new typedef here, add a corresponding explicit instantiation + // at the end of tagunion.cpp as well. typedef TagUnion<2> DoubleTagUnion; typedef TagUnion<3> TripleTagUnion; diff --git a/taglib/trueaudio/trueaudiofile.cpp b/taglib/trueaudio/trueaudiofile.cpp index 119cb0c2..a070f536 100644 --- a/taglib/trueaudio/trueaudiofile.cpp +++ b/taglib/trueaudio/trueaudiofile.cpp @@ -132,9 +132,10 @@ TagLib::Tag *TrueAudio::File::tag() const PropertyMap TrueAudio::File::setProperties(const PropertyMap &properties) { - if(d->hasID3v1) - d->tag.access(TrueAudioID3v1Index, false)->setProperties(properties); - return d->tag.access(TrueAudioID3v2Index, true)->setProperties(properties); + if(ID3v1Tag()) + ID3v1Tag()->setProperties(properties); + + return ID3v2Tag(true)->setProperties(properties); } TrueAudio::AudioProperties *TrueAudio::File::audioProperties() const diff --git a/taglib/trueaudio/trueaudiofile.h b/taglib/trueaudio/trueaudiofile.h index bfdd163a..70e66b04 100644 --- a/taglib/trueaudio/trueaudiofile.h +++ b/taglib/trueaudio/trueaudiofile.h @@ -143,7 +143,7 @@ namespace TagLib { * Creates in ID3v2 tag if necessary. If an ID3v1 tag exists, it will * be updated as well, within the limitations of ID3v1. */ - PropertyMap setProperties(const PropertyMap &); + virtual PropertyMap setProperties(const PropertyMap &); /*! * Returns the TrueAudio::Properties for this file. If no audio properties diff --git a/taglib/wavpack/wavpackfile.cpp b/taglib/wavpack/wavpackfile.cpp index 66b51e67..abc67917 100644 --- a/taglib/wavpack/wavpackfile.cpp +++ b/taglib/wavpack/wavpackfile.cpp @@ -110,9 +110,10 @@ TagLib::Tag *WavPack::File::tag() const PropertyMap WavPack::File::setProperties(const PropertyMap &properties) { - if(d->hasID3v1) - d->tag.access(WavID3v1Index, false)->setProperties(properties); - return d->tag.access(WavAPEIndex, true)->setProperties(properties); + if(ID3v1Tag()) + ID3v1Tag()->setProperties(properties); + + return APETag(true)->setProperties(properties); } WavPack::AudioProperties *WavPack::File::audioProperties() const diff --git a/taglib/wavpack/wavpackfile.h b/taglib/wavpack/wavpackfile.h index 2e0c7ddc..ebed7a9d 100644 --- a/taglib/wavpack/wavpackfile.h +++ b/taglib/wavpack/wavpackfile.h @@ -114,7 +114,7 @@ namespace TagLib { * Creates an APE tag if it does not exists and calls setProperties() on * that. Any existing ID3v1 tag will be updated as well. */ - PropertyMap setProperties(const PropertyMap&); + virtual PropertyMap setProperties(const PropertyMap&); /*! * Returns the MPC::Properties for this file. If no audio properties diff --git a/tests/test_ape.cpp b/tests/test_ape.cpp index 3ab3197b..64869c4c 100644 --- a/tests/test_ape.cpp +++ b/tests/test_ape.cpp @@ -1,8 +1,10 @@ #include #include -#include +#include +#include #include #include +#include #include #include #include "utils.h" @@ -20,6 +22,7 @@ class TestAPE : public CppUnit::TestFixture CPPUNIT_TEST(testProperties390); CPPUNIT_TEST(testFuzzedFile1); CPPUNIT_TEST(testFuzzedFile2); + CPPUNIT_TEST(testStripAndProperties); CPPUNIT_TEST_SUITE_END(); public: @@ -111,6 +114,26 @@ public: CPPUNIT_ASSERT(f.isValid()); } + void testStripAndProperties() + { + ScopedFileCopy copy("mac-399", ".ape"); + + { + APE::File f(copy.fileName().c_str()); + f.APETag(true)->setTitle("APE"); + f.ID3v1Tag(true)->setTitle("ID3v1"); + f.save(); + } + { + APE::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT_EQUAL(String("APE"), f.properties()["TITLE"].front()); + f.strip(APE::File::APE); + CPPUNIT_ASSERT_EQUAL(String("ID3v1"), f.properties()["TITLE"].front()); + f.strip(APE::File::ID3v1); + CPPUNIT_ASSERT(f.properties().isEmpty()); + } + } + }; CPPUNIT_TEST_SUITE_REGISTRATION(TestAPE); diff --git a/tests/test_asf.cpp b/tests/test_asf.cpp index 465e5a73..7ab12030 100644 --- a/tests/test_asf.cpp +++ b/tests/test_asf.cpp @@ -25,6 +25,7 @@ class TestASF : public CppUnit::TestFixture CPPUNIT_TEST(testSavePicture); CPPUNIT_TEST(testSaveMultiplePictures); CPPUNIT_TEST(testProperties); + CPPUNIT_TEST(testRepeatedSave); CPPUNIT_TEST_SUITE_END(); public: @@ -270,6 +271,21 @@ public: CPPUNIT_ASSERT_EQUAL(StringList("3"), tags["DISCNUMBER"]); } + void testRepeatedSave() + { + ScopedFileCopy copy("silence-1", ".wma"); + + { + ASF::File f(copy.fileName().c_str()); + f.tag()->setTitle(std::string(128 * 1024, 'X').c_str()); + f.save(); + CPPUNIT_ASSERT_EQUAL((offset_t)297578, f.length()); + f.tag()->setTitle(std::string(16 * 1024, 'X').c_str()); + f.save(); + CPPUNIT_ASSERT_EQUAL((offset_t)68202, f.length()); + } + } + }; CPPUNIT_TEST_SUITE_REGISTRATION(TestASF); diff --git a/tests/test_id3v1.cpp b/tests/test_id3v1.cpp index 7db2dbbf..e778419a 100644 --- a/tests/test_id3v1.cpp +++ b/tests/test_id3v1.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include "utils.h" @@ -13,6 +14,7 @@ class TestID3v1 : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(TestID3v1); CPPUNIT_TEST(testStripWhiteSpace); + CPPUNIT_TEST(testGenres); CPPUNIT_TEST_SUITE_END(); public: @@ -27,7 +29,7 @@ public: f.ID3v1Tag(true)->setArtist("Artist "); f.save(); } - + { MPEG::File f(newname.c_str()); CPPUNIT_ASSERT(f.ID3v1Tag(false)); @@ -35,6 +37,12 @@ public: } } + void testGenres() + { + CPPUNIT_ASSERT_EQUAL(String("Darkwave"), ID3v1::genre(50)); + CPPUNIT_ASSERT_EQUAL(100, ID3v1::genreIndex("Humour")); + } + }; CPPUNIT_TEST_SUITE_REGISTRATION(TestID3v1); diff --git a/tests/test_id3v2.cpp b/tests/test_id3v2.cpp index f7a91751..8e885541 100644 --- a/tests/test_id3v2.cpp +++ b/tests/test_id3v2.cpp @@ -95,6 +95,7 @@ class TestID3v2 : public CppUnit::TestFixture CPPUNIT_TEST(testRenderTableOfContentsFrame); CPPUNIT_TEST(testShrinkPadding); CPPUNIT_TEST(testEmptyFrame); + CPPUNIT_TEST(testDuplicateTags); CPPUNIT_TEST_SUITE_END(); public: @@ -1128,6 +1129,41 @@ public: } } + void testDuplicateTags() + { + ScopedFileCopy copy("duplicate_id3v2", ".mp3"); + + ByteVector audioStream; + { + MPEG::File f(copy.fileName().c_str()); + f.seek(f.ID3v2Tag()->header()->completeTagSize()); + audioStream = f.readBlock(2089); + + // duplicate_id3v2.mp3 has duplicate ID3v2 tags. + // Sample rate will be 32000 if we can't skip the second tag. + + CPPUNIT_ASSERT(f.hasID3v2Tag()); + CPPUNIT_ASSERT_EQUAL((TagLib::uint)8049, f.ID3v2Tag()->header()->completeTagSize()); + + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + + f.ID3v2Tag()->setArtist("Artist A"); + f.save(MPEG::File::ID3v2, true); + } + { + MPEG::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT(f.hasID3v2Tag()); + CPPUNIT_ASSERT_EQUAL((offset_t)3594, f.length()); + CPPUNIT_ASSERT_EQUAL((TagLib::uint)1505, f.ID3v2Tag()->header()->completeTagSize()); + CPPUNIT_ASSERT_EQUAL(String("Artist A"), f.ID3v2Tag()->artist()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + + f.seek(f.ID3v2Tag()->header()->completeTagSize()); + CPPUNIT_ASSERT_EQUAL(f.readBlock(2089), audioStream); + + } + } + }; CPPUNIT_TEST_SUITE_REGISTRATION(TestID3v2); diff --git a/tests/test_mp4.cpp b/tests/test_mp4.cpp index 7eaea895..59c23869 100644 --- a/tests/test_mp4.cpp +++ b/tests/test_mp4.cpp @@ -40,7 +40,7 @@ public: CPPUNIT_ASSERT(f.audioProperties()); CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->length()); CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); - CPPUNIT_ASSERT_EQUAL(3707, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(3708, f.audioProperties()->lengthInMilliseconds()); CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->bitrate()); CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); diff --git a/tests/test_mpc.cpp b/tests/test_mpc.cpp index 50a62d22..ec1e9f1c 100644 --- a/tests/test_mpc.cpp +++ b/tests/test_mpc.cpp @@ -1,8 +1,10 @@ #include #include -#include +#include +#include #include #include +#include #include #include #include "utils.h" @@ -21,6 +23,7 @@ class TestMPC : public CppUnit::TestFixture CPPUNIT_TEST(testFuzzedFile2); CPPUNIT_TEST(testFuzzedFile3); CPPUNIT_TEST(testFuzzedFile4); + CPPUNIT_TEST(testStripAndProperties); CPPUNIT_TEST_SUITE_END(); public: @@ -109,6 +112,26 @@ public: CPPUNIT_ASSERT(f.isValid()); } + void testStripAndProperties() + { + ScopedFileCopy copy("click", ".mpc"); + + { + MPC::File f(copy.fileName().c_str()); + f.APETag(true)->setTitle("APE"); + f.ID3v1Tag(true)->setTitle("ID3v1"); + f.save(); + } + { + MPC::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT_EQUAL(String("APE"), f.properties()["TITLE"].front()); + f.strip(MPC::File::APE); + CPPUNIT_ASSERT_EQUAL(String("ID3v1"), f.properties()["TITLE"].front()); + f.strip(MPC::File::ID3v1); + CPPUNIT_ASSERT(f.properties().isEmpty()); + } + } + }; CPPUNIT_TEST_SUITE_REGISTRATION(TestMPC); diff --git a/tests/test_mpeg.cpp b/tests/test_mpeg.cpp index 56450846..cb1c74e4 100644 --- a/tests/test_mpeg.cpp +++ b/tests/test_mpeg.cpp @@ -1,8 +1,11 @@ #include #include #include +#include #include #include +#include +#include #include #include #include @@ -26,6 +29,7 @@ class TestMPEG : public CppUnit::TestFixture CPPUNIT_TEST(testDuplicateID3v2); CPPUNIT_TEST(testFuzzedFile); CPPUNIT_TEST(testFrameOffset); + CPPUNIT_TEST(testStripAndProperties); CPPUNIT_TEST_SUITE_END(); public: @@ -212,6 +216,29 @@ public: } } + void testStripAndProperties() + { + ScopedFileCopy copy("xing", ".mp3"); + + { + MPEG::File f(copy.fileName().c_str()); + f.ID3v2Tag(true)->setTitle("ID3v2"); + f.APETag(true)->setTitle("APE"); + f.ID3v1Tag(true)->setTitle("ID3v1"); + f.save(); + } + { + MPEG::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT_EQUAL(String("ID3v2"), f.properties()["TITLE"].front()); + f.strip(MPEG::File::ID3v2); + CPPUNIT_ASSERT_EQUAL(String("APE"), f.properties()["TITLE"].front()); + f.strip(MPEG::File::APE); + CPPUNIT_ASSERT_EQUAL(String("ID3v1"), f.properties()["TITLE"].front()); + f.strip(MPEG::File::ID3v1); + CPPUNIT_ASSERT(f.properties().isEmpty()); + } + } + }; CPPUNIT_TEST_SUITE_REGISTRATION(TestMPEG); diff --git a/tests/test_trueaudio.cpp b/tests/test_trueaudio.cpp index 00b81ede..8468e0ec 100644 --- a/tests/test_trueaudio.cpp +++ b/tests/test_trueaudio.cpp @@ -1,5 +1,8 @@ #include #include +#include +#include +#include #include #include #include "utils.h" @@ -12,6 +15,7 @@ class TestTrueAudio : public CppUnit::TestFixture CPPUNIT_TEST_SUITE(TestTrueAudio); CPPUNIT_TEST(testReadPropertiesWithoutID3v2); CPPUNIT_TEST(testReadPropertiesWithTags); + CPPUNIT_TEST(testStripAndProperties); CPPUNIT_TEST_SUITE_END(); public: @@ -46,6 +50,26 @@ public: CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->ttaVersion()); } + void testStripAndProperties() + { + ScopedFileCopy copy("empty", ".tta"); + + { + TrueAudio::File f(copy.fileName().c_str()); + f.ID3v2Tag(true)->setTitle("ID3v2"); + f.ID3v1Tag(true)->setTitle("ID3v1"); + f.save(); + } + { + TrueAudio::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT_EQUAL(String("ID3v2"), f.properties()["TITLE"].front()); + f.strip(TrueAudio::File::ID3v2); + CPPUNIT_ASSERT_EQUAL(String("ID3v1"), f.properties()["TITLE"].front()); + f.strip(TrueAudio::File::ID3v1); + CPPUNIT_ASSERT(f.properties().isEmpty()); + } + } + }; CPPUNIT_TEST_SUITE_REGISTRATION(TestTrueAudio); diff --git a/tests/test_wav.cpp b/tests/test_wav.cpp index bcb91753..d209fdae 100644 --- a/tests/test_wav.cpp +++ b/tests/test_wav.cpp @@ -1,9 +1,9 @@ #include #include -#include #include #include #include +#include #include #include #include "utils.h" @@ -24,6 +24,7 @@ class TestWAV : public CppUnit::TestFixture CPPUNIT_TEST(testDuplicateTags); CPPUNIT_TEST(testFuzzedFile1); CPPUNIT_TEST(testFuzzedFile2); + CPPUNIT_TEST(testStripAndProperties); CPPUNIT_TEST_SUITE_END(); public: @@ -187,7 +188,10 @@ public: void testDuplicateTags() { - RIFF::WAV::File f(TEST_FILE_PATH_C("duplicate_tags.wav")); + ScopedFileCopy copy("duplicate_tags", ".wav"); + + RIFF::WAV::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT_EQUAL((offset_t)17052, f.length()); // duplicate_tags.wav has duplicate ID3v2/INFO tags. // title() returns "Title2" if can't skip the second tag. @@ -197,6 +201,10 @@ public: CPPUNIT_ASSERT(f.hasInfoTag()); CPPUNIT_ASSERT_EQUAL(String("Title1"), f.InfoTag()->title()); + + f.save(); + CPPUNIT_ASSERT_EQUAL((offset_t)15898, f.length()); + CPPUNIT_ASSERT_EQUAL((offset_t)-1, f.find("Title2")); } void testFuzzedFile1() @@ -211,6 +219,26 @@ public: CPPUNIT_ASSERT(f2.isValid()); } + void testStripAndProperties() + { + ScopedFileCopy copy("empty", ".wav"); + + { + RIFF::WAV::File f(copy.fileName().c_str()); + f.ID3v2Tag()->setTitle("ID3v2"); + f.InfoTag()->setTitle("INFO"); + f.save(); + } + { + RIFF::WAV::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT_EQUAL(String("ID3v2"), f.properties()["TITLE"].front()); + f.strip(RIFF::WAV::File::ID3v2); + CPPUNIT_ASSERT_EQUAL(String("INFO"), f.properties()["TITLE"].front()); + f.strip(RIFF::WAV::File::Info); + CPPUNIT_ASSERT(f.properties().isEmpty()); + } + } + }; CPPUNIT_TEST_SUITE_REGISTRATION(TestWAV); diff --git a/tests/test_wavpack.cpp b/tests/test_wavpack.cpp index 5befbff3..eddc601d 100644 --- a/tests/test_wavpack.cpp +++ b/tests/test_wavpack.cpp @@ -1,7 +1,9 @@ #include #include -#include +#include +#include #include +#include #include #include #include "utils.h" @@ -16,6 +18,7 @@ class TestWavPack : public CppUnit::TestFixture CPPUNIT_TEST(testMultiChannelProperties); CPPUNIT_TEST(testTaggedProperties); CPPUNIT_TEST(testFuzzedFile); + CPPUNIT_TEST(testStripAndProperties); CPPUNIT_TEST_SUITE_END(); public: @@ -73,6 +76,27 @@ public: WavPack::File f(TEST_FILE_PATH_C("infloop.wv")); CPPUNIT_ASSERT(f.isValid()); } + + void testStripAndProperties() + { + ScopedFileCopy copy("click", ".wv"); + + { + WavPack::File f(copy.fileName().c_str()); + f.APETag(true)->setTitle("APE"); + f.ID3v1Tag(true)->setTitle("ID3v1"); + f.save(); + } + { + WavPack::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT_EQUAL(String("APE"), f.properties()["TITLE"].front()); + f.strip(WavPack::File::APE); + CPPUNIT_ASSERT_EQUAL(String("ID3v1"), f.properties()["TITLE"].front()); + f.strip(WavPack::File::ID3v1); + CPPUNIT_ASSERT(f.properties().isEmpty()); + } + } + }; CPPUNIT_TEST_SUITE_REGISTRATION(TestWavPack);