diff --git a/taglib/CMakeLists.txt b/taglib/CMakeLists.txt index a7777a5b9..ca62f3a46 100644 --- a/taglib/CMakeLists.txt +++ b/taglib/CMakeLists.txt @@ -55,7 +55,6 @@ set(tag_HDRS mpeg/xingheader.h mpeg/id3v1/id3v1tag.h mpeg/id3v1/id3v1genres.h - mpeg/id3v2/id3v2dicttools.h mpeg/id3v2/id3v2extendedheader.h mpeg/id3v2/id3v2frame.h mpeg/id3v2/id3v2header.h @@ -139,7 +138,6 @@ set(id3v1_SRCS ) set(id3v2_SRCS - mpeg/id3v2/id3v2dicttools.cpp mpeg/id3v2/id3v2framefactory.cpp mpeg/id3v2/id3v2synchdata.cpp mpeg/id3v2/id3v2tag.cpp diff --git a/taglib/mpeg/id3v2/frames/commentsframe.cpp b/taglib/mpeg/id3v2/frames/commentsframe.cpp index 377a7ee3c..5730b7531 100644 --- a/taglib/mpeg/id3v2/frames/commentsframe.cpp +++ b/taglib/mpeg/id3v2/frames/commentsframe.cpp @@ -109,6 +109,19 @@ void CommentsFrame::setTextEncoding(String::Type encoding) d->textEncoding = encoding; } +PropertyMap CommentsFrame::asDescription() const +{ + String key = PropertyMap::prepareKey(description()); + PropertyMap map; + if(key.isEmpty()) + key = "COMMENT"; + if(key.isNull()) + map.unsupportedData().append(L"COMM/" + description()); + else + map.insert(key, text()); + return map; +} + CommentsFrame *CommentsFrame::findByDescription(const ID3v2::Tag *tag, const String &d) // static { ID3v2::FrameList comments = tag->frameList("COMM"); diff --git a/taglib/mpeg/id3v2/frames/commentsframe.h b/taglib/mpeg/id3v2/frames/commentsframe.h index def01dcfc..30d21ca1e 100644 --- a/taglib/mpeg/id3v2/frames/commentsframe.h +++ b/taglib/mpeg/id3v2/frames/commentsframe.h @@ -136,6 +136,17 @@ namespace TagLib { */ void setTextEncoding(String::Type encoding); + /*! + * Parses this frame as PropertyMap. + * - description() will be used as key + * - if description() is empty, the key will be "COMMENT" + * - if description() is not a valid PropertyMap key, the frame will be + * marked unsupported by an entry "COMM/" in the unsupportedData() + * attribute of the returned map. + * - The single value will be the frame's text(). + */ + PropertyMap asDescription() const; + /*! * Comments each have a unique description. This searches for a comment * frame with the decription \a d and returns a pointer to it. If no diff --git a/taglib/mpeg/id3v2/frames/textidentificationframe.cpp b/taglib/mpeg/id3v2/frames/textidentificationframe.cpp index 06489ea34..44528f33e 100644 --- a/taglib/mpeg/id3v2/frames/textidentificationframe.cpp +++ b/taglib/mpeg/id3v2/frames/textidentificationframe.cpp @@ -92,6 +92,40 @@ void TextIdentificationFrame::setTextEncoding(String::Type encoding) d->textEncoding = encoding; } +PropertyMap TextIdentificationFrame::asProperties() const +{ + if(frameID() == "TIPL") + return makeTIPLProperties(); + if(frameID() == "TMCL") + return makeTMCLProperties(); + PropertyMap map; + String tagName = frameIDToTagName(frameID()); + if(tagName.isNull()) { + map.unsupportedData().append(frameID()); + return map; + } + StringList values = fieldList(); + if(tagName == "GENRE") { + // Special case: Support ID3v1-style genre numbers. They are not officially supported in + // ID3v2, however it seems that still a lot of programs use them. + for(StringList::Iterator it = values.begin(); it != values.end(); ++it) { + bool ok = false; + int test = it->toInt(&ok); // test if the genre value is an integer + if(ok) + *it = ID3v1::genre(test); + } + } else if(tagName == "DATE") { + for (StringList::Iterator it = values.begin(); it != values.end(); ++it) { + // ID3v2 specifies ISO8601 timestamps which contain a 'T' as separator between date and time. + // Since this is unusual in other formats, the T is removed. + int tpos = it->find("T"); + if (tpos != -1) + (*it)[tpos] = ' '; + } + } + return KeyValuePair(tagName, values); +} + //////////////////////////////////////////////////////////////////////////////// // TextIdentificationFrame protected members //////////////////////////////////////////////////////////////////////////////// @@ -170,6 +204,63 @@ TextIdentificationFrame::TextIdentificationFrame(const ByteVector &data, Header parseFields(fieldData(data)); } +// array of allowed TIPL prefixes and their corresponding key value +static const uint involvedPeopleSize = 5; +static const char* involvedPeople[2] = { + {"ARRANGER", "ARRANGER"}, + {"ENGINEER", "ENGINEER"}, + {"PRODUCER", "PRODUCER"}, + {"DJ-MIX", "DJMIXER"}, + {"MIX", "MIXER"} +}; + +PropertyMap TextIdentificationFrame::makeTIPLProperties() const +{ + PropertyMap map; + if(fieldList().size() % 2 != 0){ + // according to the ID3 spec, TIPL must contain an even number of entries + map.unsupportedData().append(frameID()); + return map; + } + for(StringList::ConstIterator it = fieldList().begin(); it != fieldList().end(); ++it) { + bool found = false; + for(uint i = 0; i < involvedPeopleSize; ++i) + if(*it == involvedPeople[i][0]) { + map.insert(involvedPeople[i][1], (++it).split(",")); + found = true; + break; + } + if(!found){ + // invalid involved role -> mark whole frame as unsupported in order to be consisten with writing + map.clear(); + map.unsupportedData().append(frameID()); + return map; + } + } + return map; +} + +PropertyMap TextIdentificationFrame::makeTMCLProperties() const +{ + PropertyMap map; + if(fieldList().size() % 2 != 0){ + // according to the ID3 spec, TMCL must contain an even number of entries + map.unsupportedData().append(frameID()); + return map; + } + for(StringList::ConstIterator it = fieldList().begin(); it != fieldList().end(); ++it) { + String key = PropertyMap::prepareKey(*it); + if(key.isNull()) { + // instrument is not a valid key -> frame unsupported + map.clear(); + map.unsupportedData().append(frameID()); + return map; + } + map.insert(key, (++it).split(",")); + } + return map; +} + //////////////////////////////////////////////////////////////////////////////// // UserTextIdentificationFrame public members //////////////////////////////////////////////////////////////////////////////// @@ -241,22 +332,17 @@ void UserTextIdentificationFrame::setDescription(const String &s) 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. + // 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()); + map.unsupportedData().append(L"TXXX/" + description()); else - map.insert(key, l); + for(StringList::ConstIterator it = fieldList().begin(); it != fieldList().end(); ++it) + if(*it != description()) + map.insert(key, *it); return map; } diff --git a/taglib/mpeg/id3v2/frames/textidentificationframe.h b/taglib/mpeg/id3v2/frames/textidentificationframe.h index d4049d716..f93484383 100644 --- a/taglib/mpeg/id3v2/frames/textidentificationframe.h +++ b/taglib/mpeg/id3v2/frames/textidentificationframe.h @@ -173,6 +173,8 @@ namespace TagLib { */ StringList fieldList() const; + PropertyMap asProperties() const; + protected: // Reimplementations. @@ -188,6 +190,16 @@ namespace TagLib { TextIdentificationFrame(const TextIdentificationFrame &); TextIdentificationFrame &operator=(const TextIdentificationFrame &); + /*! + * Parses the special structure of a TIPL frame + * Only the whitelisted roles "ARRANGER", "ENGINEER", "PRODUCER", + * "DJMIXER" (ID3: "DJ-MIX") and "MIXER" (ID3: "MIX") are allowed. + */ + PropertyMap makeTIPLProperties() const; + /*! + * Parses the special structure of a TMCL frame. + */ + PropertyMap makeTMCLProperties() const; class TextIdentificationFramePrivate; TextIdentificationFramePrivate *d; }; @@ -237,7 +249,17 @@ namespace TagLib { void setText(const StringList &fields); /*! - * Reimplement function. + * A UserTextIdentificationFrame is parsed into a PropertyMap as follows: + * - the key is the frame's description, uppercased + * - if the description contains '::', only the substring after that + * separator is considered as key (compatibility with exfalso) + * - if the above rules don't yield a valid key (e.g. containing non-ASCII + * characters), the returned map will contain an entry "TXXX/" + * in its unsupportedData() list. + * - The values will be copies of the fieldList(). + * - If the description() appears as value in fieldList(), it will be omitted + * in the value list, in order to be compatible with TagLib which copies + * the description() into the fieldList(). */ PropertyMap asProperties() const; diff --git a/taglib/mpeg/id3v2/frames/unsynchronizedlyricsframe.cpp b/taglib/mpeg/id3v2/frames/unsynchronizedlyricsframe.cpp index 0a8927e7f..b3a9b3daf 100644 --- a/taglib/mpeg/id3v2/frames/unsynchronizedlyricsframe.cpp +++ b/taglib/mpeg/id3v2/frames/unsynchronizedlyricsframe.cpp @@ -111,6 +111,13 @@ void UnsynchronizedLyricsFrame::setTextEncoding(String::Type encoding) d->textEncoding = encoding; } +PropertyMap UnsynchronizedLyricsFrame::asProperties() const +{ + PropertyMap map; + map.insert("LYRICS", text()); + return map; +} + //////////////////////////////////////////////////////////////////////////////// // protected members //////////////////////////////////////////////////////////////////////////////// diff --git a/taglib/mpeg/id3v2/frames/unsynchronizedlyricsframe.h b/taglib/mpeg/id3v2/frames/unsynchronizedlyricsframe.h index 0f8260e47..f13134c4b 100644 --- a/taglib/mpeg/id3v2/frames/unsynchronizedlyricsframe.h +++ b/taglib/mpeg/id3v2/frames/unsynchronizedlyricsframe.h @@ -134,6 +134,13 @@ namespace TagLib { */ void setTextEncoding(String::Type encoding); + + /*! + * Parses this frame as PropertyMap. The returned map will contain a single key + * "LYRICS" with the text() as single value. + */ + PropertyMap asProperties() const; + protected: // Reimplementations. diff --git a/taglib/mpeg/id3v2/frames/urllinkframe.cpp b/taglib/mpeg/id3v2/frames/urllinkframe.cpp index 09edec40d..32bfda972 100644 --- a/taglib/mpeg/id3v2/frames/urllinkframe.cpp +++ b/taglib/mpeg/id3v2/frames/urllinkframe.cpp @@ -78,6 +78,18 @@ String UrlLinkFrame::toString() const return url(); } +PropertyMap UrlLinkFrame::asProperties() const +{ + String key = frameIDToKey(frameID()); + PropertyMap map; + if(key.isNull()) + // unknown W*** frame - this normally shouldn't happen + map.unsupportedData().append(frameID()); + else + map.insert(key, url()); + return map; +} + void UrlLinkFrame::parseFields(const ByteVector &data) { d->url = String(data); @@ -139,6 +151,19 @@ void UserUrlLinkFrame::setDescription(const String &s) d->description = s; } +PropertyMap UserUrlLinkFrame::asProperties() const +{ + String key = PropertyMap::prepareKey(description()); + PropertyMap map; + if(key.isEmpty()) + key = "URL"; + if(key.isNull()) + map.unsupportedData().append(L"WXXX/" + description()); + else + map.insert(key, url()); + return map; +} + void UserUrlLinkFrame::parseFields(const ByteVector &data) { if(data.size() < 2) { diff --git a/taglib/mpeg/id3v2/frames/urllinkframe.h b/taglib/mpeg/id3v2/frames/urllinkframe.h index f89faad0a..4eea36b3e 100644 --- a/taglib/mpeg/id3v2/frames/urllinkframe.h +++ b/taglib/mpeg/id3v2/frames/urllinkframe.h @@ -68,6 +68,7 @@ namespace TagLib { virtual void setText(const String &s); virtual String toString() const; + PropertyMap asProperties() const; protected: virtual void parseFields(const ByteVector &data); @@ -150,6 +151,16 @@ namespace TagLib { */ void setDescription(const String &s); + /*! + * Parses the UserUrlLinkFrame as PropertyMap. The description() is taken as key, + * and the URL as single value. + * - if description() is empty, the key will be "URL". + * - otherwise, if description() is not a valid key (e.g. containing non-ASCII + * characters), the returned map will contain an entry "WXXX/" + * in its unsupportedData() list. + */ + PropertyMap asProperties() const; + protected: virtual void parseFields(const ByteVector &data); virtual ByteVector renderFields() const; diff --git a/taglib/mpeg/id3v2/id3v2frame.cpp b/taglib/mpeg/id3v2/id3v2frame.cpp index 3fcc8c809..9ec536e0f 100644 --- a/taglib/mpeg/id3v2/id3v2frame.cpp +++ b/taglib/mpeg/id3v2/id3v2frame.cpp @@ -38,6 +38,7 @@ #include "id3v2frame.h" #include "id3v2synchdata.h" +#include "tpropertymap.h" using namespace TagLib; using namespace ID3v2; @@ -262,7 +263,7 @@ String::Type Frame::checkTextEncoding(const StringList &fields, String::Type enc return checkEncoding(fields, encoding, header()->version()); } -static const uint frameTranslationSize = 55; +static const uint frameTranslationSize = 53; static const char *frameTranslation[][2] = { // Text information frames { "TALB", "ALBUM"}, @@ -283,14 +284,14 @@ static const char *frameTranslation[][2] = { { "TENC", "ENCODEDBY" }, { "TEXT", "LYRICIST" }, { "TFLT", "FILETYPE" }, - { "TIPL", "INVOLVEDPEOPLE" }, + //{ "TIPL", "INVOLVEDPEOPLE" }, handled separately { "TIT1", "CONTENTGROUP" }, { "TIT2", "TITLE"}, { "TIT3", "SUBTITLE" }, { "TKEY", "INITIALKEY" }, { "TLAN", "LANGUAGE" }, { "TLEN", "LENGTH" }, - { "TMCL", "MUSICIANCREDITS" }, + //{ "TMCL", "MUSICIANCREDITS" }, handled separately { "TMED", "MEDIATYPE" }, { "TMOO", "MOOD" }, { "TOAL", "ORIGINALALBUM" }, diff --git a/taglib/mpeg/id3v2/id3v2frame.h b/taglib/mpeg/id3v2/id3v2frame.h index 8901295a0..8efe68708 100644 --- a/taglib/mpeg/id3v2/id3v2frame.h +++ b/taglib/mpeg/id3v2/id3v2frame.h @@ -33,6 +33,7 @@ namespace TagLib { class StringList; + class PropertyMap; namespace ID3v2 { diff --git a/taglib/mpeg/id3v2/id3v2tag.cpp b/taglib/mpeg/id3v2/id3v2tag.cpp index 397fe07f1..0d7f5c8d1 100644 --- a/taglib/mpeg/id3v2/id3v2tag.cpp +++ b/taglib/mpeg/id3v2/id3v2tag.cpp @@ -33,6 +33,14 @@ #include "id3v2synchdata.h" #include "tbytevector.h" #include "id3v1genres.h" +#include "tpropertymap.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; @@ -329,23 +337,12 @@ void ID3v2::Tag::removeFrames(const ByteVector &id) PropertyMap ID3v2::Tag::properties() const { 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); - } else if (deprecated(id)) { - debug("toDict() found deprecated id3 frame: " + id); - } else { - // in the future, something like dict[frame->tagName()].append(frame->values()) - // might replace the following lines. - KeyValuePair kvp = parseFrame(*it); - dict[kvp.first].append(kvp.second); - } - } - return dict; + for(FrameList::ConstIterator it = frameList().begin(); it != frameList().end(); ++it) + properties.merge((*it)->asProperties()); + return properties; } -void ID3v2::Tag::fromDict(const TagDict &dict) +PropertyMap ID3v2::Tag::setProperties(const PropertyMap &properties) { FrameList toRemove; // first find out what frames to remove; we do not remove in-place diff --git a/taglib/mpeg/mpegfile.cpp b/taglib/mpeg/mpegfile.cpp index 3a7be29e0..28b8fca74 100644 --- a/taglib/mpeg/mpegfile.cpp +++ b/taglib/mpeg/mpegfile.cpp @@ -35,6 +35,7 @@ #include "mpegfile.h" #include "mpegheader.h" +#include "tpropertymap.h" using namespace TagLib; diff --git a/taglib/tagunion.cpp b/taglib/tagunion.cpp index 2ecdd6d99..52d7136b4 100644 --- a/taglib/tagunion.cpp +++ b/taglib/tagunion.cpp @@ -171,21 +171,6 @@ void TagUnion::setTrack(uint i) { setUnion(Track, i); } -TagDict TagUnion::toDict() const -{ - for (int i = 0; i < 3; ++i) - if (d->tags[i]) - return d->tags[i]->toDict(); - TagDict dict; - return dict; -} - -void TagUnion::fromDict(const TagDict &dict) -{ - for (int i = 0; i < 3; ++i) - if (d->tags[i]) - d->tags[i]->fromDict(dict); -} bool TagUnion::isEmpty() const { diff --git a/taglib/tagunion.h b/taglib/tagunion.h index 20771fe8e..e94d523a3 100644 --- a/taglib/tagunion.h +++ b/taglib/tagunion.h @@ -73,9 +73,6 @@ namespace TagLib { virtual void setTrack(uint i); virtual bool isEmpty() const; - virtual TagDict toDict() const; - virtual void fromDict(const TagDict &); - template T *access(int index, bool create) { if(!create || tag(index)) diff --git a/taglib/toolkit/tpropertymap.h b/taglib/toolkit/tpropertymap.h index e41c190bb..1fbcb5845 100644 --- a/taglib/toolkit/tpropertymap.h +++ b/taglib/toolkit/tpropertymap.h @@ -35,7 +35,7 @@ namespace TagLib { * This map implements a generic representation of textual audio metadata * ("tags") realized as pairs of a case-insensitive key * and a nonempty list of corresponding values, each value being an an arbitrary - * Unicode String. + * unicode String. * The key has the same restrictions as in the vorbis comment specification, * i.e. it must contain at least one character; all printable ASCII characters * except '=' and '~' are allowed.