From a5e45f196b472db5e01b92ec84bdf5d59ac0fc56 Mon Sep 17 00:00:00 2001 From: Michael Helmling Date: Sat, 21 Jan 2012 23:05:59 +0100 Subject: [PATCH] Started to work on ID3v2. --- taglib/mpc/mpcfile.cpp | 35 +++-- taglib/mpc/mpcfile.h | 14 +- .../id3v2/frames/textidentificationframe.cpp | 22 +++ .../id3v2/frames/textidentificationframe.h | 5 + taglib/mpeg/id3v2/id3v2frame.cpp | 136 ++++++++++++++++++ taglib/mpeg/id3v2/id3v2frame.h | 21 +++ taglib/mpeg/id3v2/id3v2tag.cpp | 24 +--- taglib/mpeg/id3v2/id3v2tag.h | 24 ++-- taglib/mpeg/mpegfile.cpp | 43 +++--- taglib/mpeg/mpegfile.h | 12 +- taglib/toolkit/tpropertymap.cpp | 48 ++++--- taglib/toolkit/tpropertymap.h | 16 ++- 12 files changed, 307 insertions(+), 93 deletions(-) diff --git a/taglib/mpc/mpcfile.cpp b/taglib/mpc/mpcfile.cpp index 2482a90c..6b372a60 100644 --- a/taglib/mpc/mpcfile.cpp +++ b/taglib/mpc/mpcfile.cpp @@ -113,27 +113,34 @@ TagLib::Tag *MPC::File::tag() const return &d->tag; } -TagLib::TagDict MPC::File::toDict(void) const +PropertyMap MPC::File::properties() const { - // once Tag::toDict() is virtual, this case distinction could actually be done - // within TagUnion. - if (d->hasAPE) - return d->tag.access(APEIndex, false)->toDict(); - if (d->hasID3v1) - return d->tag.access(ID3v1Index, false)->toDict(); - return TagLib::TagDict(); + if(d->hasAPE) + return d->tag.access(APEIndex, false)->properties(); + if(d->hasID3v1) + return d->tag.access(ID3v1Index, false)->properties(); + return PropertyMap(); +} + +void MPC::File::removeUnsupportedProperties(const StringList &properties) +{ + if(d->hasAPE) + d->tag.access(APEIndex, false)->removeUnsupportedProperties(properties); + if(d->hasID3v1) + d->tag.access(ID3v1Index, false)->removeUnsupportedProperties(properties); } -void MPC::File::fromDict(const TagDict &dict) +PropertyMap MPC::File::setProperties(const PropertyMap &properties) { - if (d->hasAPE) - d->tag.access(APEIndex, false)->fromDict(dict); - else if (d->hasID3v1) - d->tag.access(ID3v1Index, false)->fromDict(dict); + if(d->hasAPE) + return d->tag.access(APEIndex, false)->setProperties(properties); + else if(d->hasID3v1) + return d->tag.access(ID3v1Index, false)->setProperties(properties); else - d->tag.access(APEIndex, true)->fromDict(dict); + return d->tag.access(APE, true)->setProperties(properties); } + MPC::Properties *MPC::File::audioProperties() const { return d->properties; diff --git a/taglib/mpc/mpcfile.h b/taglib/mpc/mpcfile.h index 6ff91e71..c906ae67 100644 --- a/taglib/mpc/mpcfile.h +++ b/taglib/mpc/mpcfile.h @@ -109,18 +109,20 @@ namespace TagLib { virtual TagLib::Tag *tag() const; /*! - * Implements the unified tag dictionary interface -- export function. + * Implements the unified property interface -- export function. * If the file contains both an APE and an ID3v1 tag, only the APE - * tag will be converted to the TagDict. + * tag will be converted to the PropertyMap. */ - TagDict toDict() const; + PropertyMap properties() const; + + void removeUnsupportedProperties(const StringList &properties); /*! - * Implements the unified tag dictionary interface -- import function. + * Implements the unified property interface -- import function. * As with the export, only one tag is taken into account. If the file - * has no tag at all, APE will be created. + * has no tag at all, an APE tag will be created. */ - void fromDict(const TagDict &); + PropertyMap setProperties(const PropertyMap &); /*! * Returns the MPC::Properties for this file. If no audio properties diff --git a/taglib/mpeg/id3v2/frames/textidentificationframe.cpp b/taglib/mpeg/id3v2/frames/textidentificationframe.cpp index cf724922..06489ea3 100644 --- a/taglib/mpeg/id3v2/frames/textidentificationframe.cpp +++ b/taglib/mpeg/id3v2/frames/textidentificationframe.cpp @@ -238,6 +238,28 @@ void UserTextIdentificationFrame::setDescription(const String &s) TextIdentificationFrame::setText(l); } +PropertyMap UserTextIdentificationFrame::asProperties() const +{ + String tagName = description(); + StringList l(fieldList()); + // this is done because taglib stores the description also as first entry + // in the field list. (why?) + StringList::Iterator tagIt = l.find(tagName); + if(tagIt != l.end()) + l.erase(tagIt); + // Quodlibet/Exfalso use QuodLibet:: if you set an arbitrary ID3 + // tag. + int pos = tagName.find("::"); + tagName = (pos != -1) ? tagName.substr(pos+2).upper() : tagName.upper(); + PropertyMap map; + String key = map.prepareKey(tagName); + if(key.isNull()) // this frame's description is not a valid PropertyMap key -> add to unsupported list + map.unsupportedData().append("TXXX/" + description()); + else + map.insert(key, l); + return map; +} + UserTextIdentificationFrame *UserTextIdentificationFrame::find( ID3v2::Tag *tag, const String &description) // static { diff --git a/taglib/mpeg/id3v2/frames/textidentificationframe.h b/taglib/mpeg/id3v2/frames/textidentificationframe.h index 418ef970..d4049d71 100644 --- a/taglib/mpeg/id3v2/frames/textidentificationframe.h +++ b/taglib/mpeg/id3v2/frames/textidentificationframe.h @@ -236,6 +236,11 @@ namespace TagLib { void setText(const String &text); void setText(const StringList &fields); + /*! + * Reimplement function. + */ + PropertyMap asProperties() const; + /*! * Searches for the user defined text frame with the description \a description * in \a tag. This returns null if no matching frames were found. diff --git a/taglib/mpeg/id3v2/id3v2frame.cpp b/taglib/mpeg/id3v2/id3v2frame.cpp index 7979cf50..3fcc8c80 100644 --- a/taglib/mpeg/id3v2/id3v2frame.cpp +++ b/taglib/mpeg/id3v2/id3v2frame.cpp @@ -262,6 +262,142 @@ String::Type Frame::checkTextEncoding(const StringList &fields, String::Type enc return checkEncoding(fields, encoding, header()->version()); } +static const uint frameTranslationSize = 55; +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" }, + { "TIT1", "CONTENTGROUP" }, + { "TIT2", "TITLE"}, + { "TIT3", "SUBTITLE" }, + { "TKEY", "INITIALKEY" }, + { "TLAN", "LANGUAGE" }, + { "TLEN", "LENGTH" }, + { "TMCL", "MUSICIANCREDITS" }, + { "TMED", "MEDIATYPE" }, + { "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", "PUBLISHER" }, + { "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"}, + // Other frames + { "COMM", "COMMENT" }, + { "USLT", "LYRICS" }, +}; + +Map &idMap() +{ + static Map m; + if(m.isEmpty()) + for(size_t i = 0; i < frameTranslationSize; ++i) + m[frameTranslation[i][0]] = frameTranslation[i][1]; + return m; +} + +// list of deprecated frames and their successors +static const uint deprecatedFramesSize = 4; +static const char *deprecatedFrames[][2] = { + {"TRDA", "TDRC"}, // 2.3 -> 2.4 (http://en.wikipedia.org/wiki/ID3) + {"TDAT", "TDRC"}, // 2.3 -> 2.4 + {"TYER", "TDRC"}, // 2.3 -> 2.4 + {"TIME", "TDRC"}, // 2.3 -> 2.4 +}; + +FrameIDMap &deprecationMap() +{ + static FrameIDMap depMap; + if(depMap.isEmpty()) + for(uint i = 0; i < deprecatedFramesSize; ++i) + depMap[deprecatedFrames[i][0]] = deprecatedFrames[i][1]; + return depMap; +} + +String Frame::frameIDToKey(const ByteVector &id) +{ + Map &m = idMap(); + if(m.contains(id)) + return m[id]; + if(deprecationMap().contains(id)) + return m[deprecationMap()[id]]; + return String::null; +} + +ByteVector Frame::keyToFrameID(const String &s) +{ + static Map m; + if(m.isEmpty()) + for(size_t i = 0; i < frameTranslationSize; ++i) + m[frameTranslation[i][1]] = frameTranslation[i][0]; + if(m.contains(s.upper())) + return m[s]; + return ByteVector::null; +} + +PropertyMap Frame::asProperties() const +{ + const ByteVector &id = frameID(); + // workaround until this function is virtual + if(id == "TXXX") + return dynamic_cast< const UserTextIdentificationFrame* >(this)->asProperties(); + else if(id[0] == 'T') + return dynamic_cast< const TextIdentificationFrame* >(this)->asProperties(); + else if(id == "WXXX") + return dynamic_cast< const UserUrlLinkFrame* >(this)->asProperties(); + else if(id[0] == 'W') + return dynamic_cast< const UrlLinkFrame* >(this)->asProperties(); + else if(id == "COMM") + return dynamic_cast< const CommentsFrame* >(this)->asProperties(); + else if(id == "USLT") + return dynamic_cast< const UnsynchronizedLyricsFrame* >(this)->asProperties(); + else { + PropertyMap m; + m.unsupportedData().append(id); + return m; + } +} //////////////////////////////////////////////////////////////////////////////// // Frame::Header class //////////////////////////////////////////////////////////////////////////////// diff --git a/taglib/mpeg/id3v2/id3v2frame.h b/taglib/mpeg/id3v2/id3v2frame.h index 2b6bcd88..8901295a 100644 --- a/taglib/mpeg/id3v2/id3v2frame.h +++ b/taglib/mpeg/id3v2/id3v2frame.h @@ -222,6 +222,27 @@ namespace TagLib { String::Type checkTextEncoding(const StringList &fields, String::Type encoding) const; + + /*! + * Parses the contents of this frame as PropertyMap. If that fails, the returend + * PropertyMap will be empty, and its unsupportedData() will contain this frame's + * ID. + * BIC: Will be a virtual function in future releases. + */ + PropertyMap asProperties() const; + + /*! + * Returns an appropriate ID3 frame ID for the given free-form tag key. This method + * will return ByteVector::null if no specialized translation is found. + */ + static ByteVector keyToFrameID(const String &); + + /*! + * Returns a free-form tag name for the given ID3 frame ID. Note that this does not work + * for general frame IDs such as TXXX or WXXX; in such a case String::null is returned. + */ + static String frameIDToKey(const ByteVector &); + private: Frame(const Frame &); Frame &operator=(const Frame &); diff --git a/taglib/mpeg/id3v2/id3v2tag.cpp b/taglib/mpeg/id3v2/id3v2tag.cpp index dd3e97a7..397fe07f 100644 --- a/taglib/mpeg/id3v2/id3v2tag.cpp +++ b/taglib/mpeg/id3v2/id3v2tag.cpp @@ -31,17 +31,9 @@ #include "id3v2extendedheader.h" #include "id3v2footer.h" #include "id3v2synchdata.h" -#include "id3v2dicttools.h" #include "tbytevector.h" #include "id3v1genres.h" -#include "frames/textidentificationframe.h" -#include "frames/commentsframe.h" -#include "frames/urllinkframe.h" -#include "frames/uniquefileidentifierframe.h" -#include "frames/unsynchronizedlyricsframe.h" -#include "frames/unknownframe.h" - using namespace TagLib; using namespace ID3v2; @@ -334,25 +326,19 @@ void ID3v2::Tag::removeFrames(const ByteVector &id) removeFrame(*it, true); } -TagDict ID3v2::Tag::toDict(StringList *ignoreInfo) const +PropertyMap ID3v2::Tag::properties() const { - TagDict dict; - FrameList::ConstIterator frameIt = frameList().begin(); - for (; frameIt != frameList().end(); ++frameIt) { - ByteVector id = (*frameIt)->frameID(); - + PropertyMap properties; + for(FrameList::ConstIterator it = frameList().begin(); it != frameList().end(); ++it) { + ByteVector id = (*it)->frameID(); if (ignored(id)) { debug("toDict() found ignored id3 frame: " + id); - if (ignoreInfo) - ignoreInfo->append(String(id) + " frame is not supported"); } else if (deprecated(id)) { debug("toDict() found deprecated id3 frame: " + id); - if (ignoreInfo) - ignoreInfo->append(String(id) + " frame is deprecated"); } else { // in the future, something like dict[frame->tagName()].append(frame->values()) // might replace the following lines. - KeyValuePair kvp = parseFrame(*frameIt); + KeyValuePair kvp = parseFrame(*it); dict[kvp.first].append(kvp.second); } } diff --git a/taglib/mpeg/id3v2/id3v2tag.h b/taglib/mpeg/id3v2/id3v2tag.h index 33d9a33c..56c055a3 100644 --- a/taglib/mpeg/id3v2/id3v2tag.h +++ b/taglib/mpeg/id3v2/id3v2tag.h @@ -261,22 +261,26 @@ namespace TagLib { void removeFrames(const ByteVector &id); /*! - * Implements the unified tag dictionary interface -- export function. + * Implements the unified property interface -- export function. * This function does some work to translate the hard-specified ID3v2 - * frame types into a free-form string-to-stringlist dictionary. + * frame types into a free-form string-to-stringlist PropertyMap. * - * If the optional pointer to a StringList is given, that list will - * be filled with a descriptive text for each ID3v2 frame that could - * not be incorporated into the dict interface (binary data, unsupported - * frames, ...). */ - TagDict toDict(StringList *ignoredInfo = 0) const; + PropertyMap properties() const; /*! - * Implements the unified tag dictionary interface -- import function. - * See the comments in toDict(). + * Removes unsupported frames given by \a properties. The elements of + * \a properties must be taken from properties().unsupportedData() and + * are the four-byte frame IDs of ID3 frames which are not compatible + * with the PropertyMap schema. */ - void fromDict(const TagDict &); + void removeUnsupportedProperties(const StringList &properties); + + /*! + * Implements the unified property interface -- import function. + * See the comments in properties(). + */ + PropertyMap setProperties(const PropertyMap &); /*! * Render the tag back to binary data, suitable to be written to disk. diff --git a/taglib/mpeg/mpegfile.cpp b/taglib/mpeg/mpegfile.cpp index 645409fa..3a7be29e 100644 --- a/taglib/mpeg/mpegfile.cpp +++ b/taglib/mpeg/mpegfile.cpp @@ -133,29 +133,38 @@ TagLib::Tag *MPEG::File::tag() const return &d->tag; } -TagLib::TagDict MPEG::File::toDict(void) const +PropertyMap MPEG::File::properties() const { - // once Tag::toDict() is virtual, this case distinction could actually be done + // once Tag::properties() is virtual, this case distinction could actually be done // within TagUnion. - if (d->hasID3v2) - return d->tag.access(ID3v2Index, false)->toDict(); - if (d->hasAPE) - return d->tag.access(APEIndex, false)->toDict(); - if (d->hasID3v1) - return d->tag.access(ID3v1Index, false)->toDict(); - return TagLib::TagDict(); + if(d->hasID3v2) + return d->tag.access(ID3v2Index, false)->properties(); + if(d->hasAPE) + return d->tag.access(APEIndex, false)->properties(); + if(d->hasID3v1) + return d->tag.access(ID3v1Index, false)->properties(); + return PropertyMap(); } -void MPEG::File::fromDict(const TagDict &dict) +void MPEG::File::removeUnsupportedProperties(const StringList &properties) { - if (d->hasID3v2) - d->tag.access(ID3v2Index, false)->fromDict(dict); - else if (d->hasAPE) - d->tag.access(APEIndex, false)->fromDict(dict); - else if (d->hasID3v1) - d->tag.access(ID3v1Index, false)->fromDict(dict); + if(d->hasID3v2) + d->tag.access(ID3v2Index, false)->removeUnsupportedProperties(properties); + else if(d->hasAPE) + d->tag.access(APEIndex, false)->removeUnsupportedProperties(properties); + else if(d->hasID3v1) + d->tag.access(ID3v1Index, false)->removeUnsupportedProperties(properties); +} +PropertyMap MPEG::File::setProperties(const PropertyMap &properties) +{ + if(d->hasID3v2) + return d->tag.access(ID3v2Index, false)->setProperties(properties); + else if(d->hasAPE) + return d->tag.access(APEIndex, false)->setProperties(properties); + else if(d->hasID3v1) + return d->tag.access(ID3v1Index, false)->setProperties(properties); else - d->tag.access(ID3v2Index, true)->fromDict(dict); + return d->tag.access(ID3v2Index, true)->setProperties(properties); } MPEG::Properties *MPEG::File::audioProperties() const diff --git a/taglib/mpeg/mpegfile.h b/taglib/mpeg/mpegfile.h index 75da7b0a..dbd0e017 100644 --- a/taglib/mpeg/mpegfile.h +++ b/taglib/mpeg/mpegfile.h @@ -130,19 +130,21 @@ namespace TagLib { virtual Tag *tag() const; /*! - * Implements the unified tag dictionary interface -- export function. - * If the file contains more than one tag (e.g. ID3v2 and v1), only the + * Implements the unified property interface -- export function. + * If the file contains more than one tag, only the * first one (in the order ID3v2, APE, ID3v1) will be converted to the - * TagDict. + * PropertyMap. */ - TagDict toDict() const; + PropertyMap properties() const; + + void removeUnsupportedProperties(const StringList &properties); /*! * Implements the unified tag dictionary interface -- import function. * As with the export, only one tag is taken into account. If the file * has no tag at all, ID3v2 will be created. */ - void fromDict(const TagDict &); + PropertyMap setProperties(const PropertyMap &); /*! * Returns the MPEG::Properties for this file. If no audio properties diff --git a/taglib/toolkit/tpropertymap.cpp b/taglib/toolkit/tpropertymap.cpp index 2e043a33..66fb38cb 100644 --- a/taglib/toolkit/tpropertymap.cpp +++ b/taglib/toolkit/tpropertymap.cpp @@ -23,13 +23,12 @@ using namespace TagLib; -typedef Map supertype; -PropertyMap::PropertyMap() : Map() +PropertyMap::PropertyMap() : SimplePropertyMap() { } -PropertyMap::PropertyMap(const PropertyMap &m) : Map(m) +PropertyMap::PropertyMap(const PropertyMap &m) : SimplePropertyMap(m) { } @@ -43,11 +42,11 @@ bool PropertyMap::insert(const String &key, const StringList &values) if(realKey.isNull()) return false; - Iterator result = supertype::find(realKey); + Iterator result = SimplePropertyMap::find(realKey); if(result == end()) - supertype::insert(realKey, values); + SimplePropertyMap::insert(realKey, values); else - supertype::operator[](realKey).append(values); + SimplePropertyMap::operator[](realKey).append(values); return true; } @@ -56,8 +55,8 @@ bool PropertyMap::replace(const String &key, const StringList &values) String realKey = prepareKey(key); if(realKey.isNull()) return false; - supertype::erase(realKey); - supertype::insert(realKey, values); + SimplePropertyMap::erase(realKey); + SimplePropertyMap::insert(realKey, values); return true; } @@ -66,7 +65,7 @@ PropertyMap::Iterator PropertyMap::find(const String &key) String realKey = prepareKey(key); if(realKey.isNull()) return end(); - return supertype::find(realKey); + return SimplePropertyMap::find(realKey); } PropertyMap::ConstIterator PropertyMap::find(const String &key) const @@ -74,40 +73,46 @@ PropertyMap::ConstIterator PropertyMap::find(const String &key) const String realKey = prepareKey(key); if(realKey.isNull()) return end(); - return supertype::find(realKey); + return SimplePropertyMap::find(realKey); } bool PropertyMap::contains(const String &key) const { String realKey = prepareKey(key); // we consider keys with empty value list as not present - if(realKey.isNull() || supertype::operator[](realKey).isEmpty()) + if(realKey.isNull() || SimplePropertyMap::operator[](realKey).isEmpty()) return false; - return supertype::contains(realKey); + return SimplePropertyMap::contains(realKey); } -/*! - * Erase the \a key and its values from the map. - */ PropertyMap &PropertyMap::erase(const String &key) { String realKey = prepareKey(key); - if (realKey.isNull()) + if(realKey.isNull()) return *this; - supertype::erase(realKey); + SimplePropertyMap::erase(realKey); + return *this; +} + +PropertyMap &PropertyMap::merge(const PropertyMap &other) +{ + for(PropertyMap::ConstIterator it = other.begin(); it != other.end(); ++it) { + insert(it->first, it->second); + } + unsupported.append(other.unsupported); return *this; } const StringList &PropertyMap::operator[](const String &key) const { String realKey = prepareKey(key); - return supertype::operator[](realKey); + return SimplePropertyMap::operator[](realKey); } StringList &PropertyMap::operator[](const String &key) { String realKey = prepareKey(key); - return supertype::operator[](realKey); + return SimplePropertyMap::operator[](realKey); } void PropertyMap::removeEmpty() @@ -125,6 +130,11 @@ StringList &PropertyMap::unsupportedData() return unsupported; } +const StringList &PropertyMap::unsupportedData() const +{ + return unsupported; +} + static String PropertyMap::prepareKey(const String &proposed) { if(proposed.isEmpty()) return String::null; diff --git a/taglib/toolkit/tpropertymap.h b/taglib/toolkit/tpropertymap.h index 4f7bf555..e41c190b 100644 --- a/taglib/toolkit/tpropertymap.h +++ b/taglib/toolkit/tpropertymap.h @@ -27,6 +27,7 @@ namespace TagLib { + typedef Map SimplePropertyMap; //! A map for format-independent tag representations. @@ -46,12 +47,12 @@ namespace TagLib { * */ - class TAGLIB_EXPORT PropertyMap: public Map + class TAGLIB_EXPORT PropertyMap: public SimplePropertyMap { public: - typedef Map::Iterator Iterator; - typedef Map::ConstIterator ConstIterator; + typedef SimplePropertyMap::Iterator Iterator; + typedef SimplePropertyMap::ConstIterator ConstIterator; PropertyMap(); @@ -95,6 +96,14 @@ namespace TagLib { */ PropertyMap &erase(const String &key); + /*! + * Merge the contents of \a other into this PropertyMap. + * If a key is contained in both maps, the values of the second + * are appended to that of the first. + * The unsupportedData() lists are concatenated as well. + */ + PropertyMap &merge(const PropertyMap &other); + /*! * Returns a reference to the value associated with \a key. * @@ -121,6 +130,7 @@ namespace TagLib { * same PropertyMap as argument. */ StringList &unsupportedData(); + const StringList &unsupportedData() const; /*! * Removes all entries which have an empty value list. -- 2.40.0